summaryrefslogtreecommitdiffstats
path: root/src/kash
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:21:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:21:29 +0000
commit29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc (patch)
tree63ef546b10a81d461e5cf5ed9e98a68cd7dee1aa /src/kash
parentInitial commit. (diff)
downloadkbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.tar.xz
kbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.zip
Adding upstream version 1:0.1.9998svn3589+dfsg.upstream/1%0.1.9998svn3589+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/kash')
-rw-r--r--src/kash/Makefile.kmk272
-rw-r--r--src/kash/TOUR357
-rw-r--r--src/kash/alias.c311
-rw-r--r--src/kash/alias.h53
-rw-r--r--src/kash/arith.y209
-rw-r--r--src/kash/arith_lex.l172
-rw-r--r--src/kash/bld_signames.c172
-rw-r--r--src/kash/bltin/Makefile.kup0
-rw-r--r--src/kash/bltin/echo.1109
-rw-r--r--src/kash/bltin/echo.c120
-rw-r--r--src/kash/bltin/kill.c236
-rw-r--r--src/kash/bltin/printf.c659
-rw-r--r--src/kash/bltin/test.c483
-rw-r--r--src/kash/builtins.def92
-rw-r--r--src/kash/cd.c445
-rw-r--r--src/kash/cd.h43
-rw-r--r--src/kash/error.c394
-rw-r--r--src/kash/error.h133
-rw-r--r--src/kash/eval.c1485
-rw-r--r--src/kash/eval.h64
-rw-r--r--src/kash/exec.c1352
-rw-r--r--src/kash/exec.h96
-rw-r--r--src/kash/expand.c1617
-rw-r--r--src/kash/expand.h78
-rw-r--r--src/kash/funcs/cmv50
-rw-r--r--src/kash/funcs/dirs74
-rw-r--r--src/kash/funcs/kill50
-rw-r--r--src/kash/funcs/login39
-rw-r--r--src/kash/funcs/newgrp38
-rw-r--r--src/kash/funcs/popd74
-rw-r--r--src/kash/funcs/pushd74
-rw-r--r--src/kash/funcs/suspend42
-rw-r--r--src/kash/generated/arith.c748
-rw-r--r--src/kash/generated/arith.h25
-rw-r--r--src/kash/generated/arith_lex.c1731
-rw-r--r--src/kash/generated/builtins.c63
-rw-r--r--src/kash/generated/builtins.h57
-rw-r--r--src/kash/generated/init.c292
-rw-r--r--src/kash/generated/nodes.c366
-rw-r--r--src/kash/generated/nodes.h207
-rw-r--r--src/kash/generated/token.h112
-rw-r--r--src/kash/histedit.c546
-rw-r--r--src/kash/init.h39
-rw-r--r--src/kash/input.c574
-rw-r--r--src/kash/input.h62
-rw-r--r--src/kash/jobs.c1521
-rw-r--r--src/kash/jobs.h114
-rw-r--r--src/kash/machdep.h56
-rw-r--r--src/kash/mail.c119
-rw-r--r--src/kash/mail.h37
-rw-r--r--src/kash/main.c494
-rw-r--r--src/kash/main.h44
-rw-r--r--src/kash/memalloc.c725
-rw-r--r--src/kash/memalloc.h147
-rw-r--r--src/kash/miscbltin.c443
-rw-r--r--src/kash/miscbltin.h31
-rwxr-xr-xsrc/kash/mkbuiltins136
-rwxr-xr-xsrc/kash/mkinit.sh199
-rwxr-xr-xsrc/kash/mknodes.sh232
-rwxr-xr-xsrc/kash/mktokens98
-rw-r--r--src/kash/myhistedit.h49
-rw-r--r--src/kash/mystring.c132
-rw-r--r--src/kash/mystring.h55
-rw-r--r--src/kash/nodes.c.pat185
-rw-r--r--src/kash/nodetypes141
-rw-r--r--src/kash/options.c635
-rw-r--r--src/kash/options.h145
-rw-r--r--src/kash/output.c502
-rw-r--r--src/kash/output.h92
-rw-r--r--src/kash/parser.c1966
-rw-r--r--src/kash/parser.h88
-rw-r--r--src/kash/redir.c484
-rw-r--r--src/kash/redir.h54
-rw-r--r--src/kash/setmode.c472
-rw-r--r--src/kash/sh.11949
-rw-r--r--src/kash/shell.h102
-rw-r--r--src/kash/shfile.c2656
-rw-r--r--src/kash/shfile.h229
-rw-r--r--src/kash/shfork-win.c290
-rw-r--r--src/kash/shforkA-win.asm338
-rw-r--r--src/kash/shheap.c596
-rw-r--r--src/kash/shheap.h45
-rw-r--r--src/kash/shinstance.c2389
-rw-r--r--src/kash/shinstance.h606
-rw-r--r--src/kash/show.c534
-rw-r--r--src/kash/show.h50
-rw-r--r--src/kash/shthread.c151
-rw-r--r--src/kash/shthread.h89
-rw-r--r--src/kash/shtypes.h150
-rw-r--r--src/kash/strlcpy.c72
-rw-r--r--src/kash/strsignal.c14
-rw-r--r--src/kash/syntax.c209
-rw-r--r--src/kash/syntax.h91
-rw-r--r--src/kash/tests/Makefile.kmk75
-rwxr-xr-xsrc/kash/tests/common-include.sh14
-rwxr-xr-xsrc/kash/tests/netbsd/exit17
-rwxr-xr-xsrc/kash/tests/netbsd/var18
-rwxr-xr-xsrc/kash/tests/netbsd/waitjob8
-rw-r--r--src/kash/tests/pipe-118
-rw-r--r--src/kash/tests/pipe-230
-rwxr-xr-xsrc/kash/tests/redirect-118
-rwxr-xr-xsrc/kash/tests/redirect-219
-rwxr-xr-xsrc/kash/tests/redirect-320
-rwxr-xr-xsrc/kash/tests/tick-110
-rwxr-xr-xsrc/kash/tests/trap-exit-17
-rwxr-xr-xsrc/kash/tests/trap-int-16
-rwxr-xr-xsrc/kash/tests/trap-term-16
-rw-r--r--src/kash/trap.c471
-rw-r--r--src/kash/trap.h51
-rw-r--r--src/kash/tstDump.c75
-rw-r--r--src/kash/var.c1020
-rw-r--r--src/kash/var.h153
112 files changed, 36187 insertions, 0 deletions
diff --git a/src/kash/Makefile.kmk b/src/kash/Makefile.kmk
new file mode 100644
index 0000000..f0e6e66
--- /dev/null
+++ b/src/kash/Makefile.kmk
@@ -0,0 +1,272 @@
+# $Id: Makefile.kmk 3477 2020-09-17 21:52:16Z bird $
+## @file
+# Sub-makefile for kash.
+#
+
+#
+# Copyright (c) 2005-2020 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+SUB_DEPTH = ../..
+include $(KBUILD_PATH)/subheader.kmk
+
+KASH_WIN_FORKED_MODE =
+
+#
+# The program.
+#
+PROGRAMS += kash
+kash_TEMPLATE = BIN-THREADED
+kash_NAME = kmk_ash
+kash_ASTOOL = YASM
+kash_DEFS = lint SHELL SMALL KASH_SEPARATE_PARSER_ALLOCATOR
+if "$(KBUILD_TARGET)" != "win" || defined(KASH_WIN_FORKED_MODE)
+kash_DEFS += SH_FORKED_MODE
+else
+kash_DEFS += KASH_SEPARATE_PARSER_ALLOCATOR KASH_ASYNC_CLOSE_HANDLE
+endif
+kash_DEFS.debug = DEBUG=2 K_STRICT
+kash_DEFS.haiku = BSD
+kash_DEFS.linux = BSD
+kash_DEFS.solaris = BSD
+## @todo bring over PC_SLASHES?
+kash_DEFS.win = \
+ BSD YY_NO_UNISTD_H \
+ SH_DEAL_WITH_CRLF PC_PATH_SEP PC_DRIVE_LETTERS PC_EXE_EXTS EXEC_HASH_BANG_SCRIPT \
+ KASH_USE_FORKSHELL2
+kash_DEFS.os2 = \
+ HAVE_SYS_SIGNAME HAVE_SYSCTL_H HAVE_SETPROGNAME PC_OS2_LIBPATHS \
+ SH_DEAL_WITH_CRLF PC_PATH_SEP PC_DRIVE_LETTERS PC_EXE_EXTS EXEC_HASH_BANG_SCRIPT
+kash_DEFS.darwin = \
+ HAVE_SYS_SIGNAME HAVE_SYSCTL_H HAVE_SETPROGNAME
+kash_DEFS.dragonfly = \
+ HAVE_SYS_SIGNAME HAVE_SYSCTL_H HAVE_SETPROGNAME
+kash_DEFS.freebsd = \
+ HAVE_SYS_SIGNAME HAVE_SYSCTL_H HAVE_SETPROGNAME
+kash_DEFS.gnukfbsd = HAVE_SYSCTL_H
+kash_DEFS.netbsd = \
+ HAVE_SYS_SIGNAME HAVE_SYSCTL_H HAVE_SETPROGNAME
+kash_DEFS.openbsd = \
+ HAVE_SYS_SIGNAME HAVE_SYSCTL_H HAVE_SETPROGNAME
+kash_INCS = $(kash_0_OUTDIR) . # (the last is because of error.h)
+kash_ASFLAGS.win = -g cv8
+kash_ASFLAGS.win.x86 = -f win32
+kash_ASFLAGS.win.amd64 = -f win64
+if "$(USER)" == "bird" && "$(KBUILD_TARGET)" != "win"
+kash_CFLAGS += -std=gnu99
+endif
+kash_CFLAGS.win.amd64 = -GS-
+ifdef KASH_WIN_FORKED_MODE
+kash_LDFLAGS.win = -DYNAMICBASE:NO
+endif
+kash_SOURCES = \
+ main.c \
+ alias.c \
+ cd.c \
+ error.c \
+ eval.c \
+ exec.c \
+ expand.c \
+ histedit.c \
+ input.c \
+ jobs.c \
+ mail.c \
+ memalloc.c \
+ mystring.c \
+ options.c \
+ output.c \
+ parser.c \
+ redir.c \
+ show.c \
+ syntax.c \
+ trap.c \
+ var.c \
+ miscbltin.c \
+ bltin/echo.c \
+ bltin/kill.c \
+ bltin/test.c \
+ \
+ $(kash_0_OUTDIR)/builtins.c \
+ $(kash_0_OUTDIR)/init.c \
+ $(kash_0_OUTDIR)/nodes.c \
+ \
+ setmode.c \
+ shinstance.c \
+ shheap.c \
+ shthread.c \
+ shfile.c
+kash_SOURCES.gnuhurd = \
+ $(kash_0_OUTDIR)/sys_signame.c \
+ strlcpy.c
+kash_SOURCES.gnukfbsd = \
+ $(kash_0_OUTDIR)/sys_signame.c \
+ strlcpy.c
+kash_SOURCES.gnuknbsd = \
+ $(kash_0_OUTDIR)/sys_signame.c \
+ strlcpy.c
+kash_SOURCES.haiku = \
+ $(kash_0_OUTDIR)/sys_signame.c \
+ strlcpy.c
+kash_SOURCES.linux = \
+ $(kash_0_OUTDIR)/sys_signame.c \
+ strlcpy.c
+kash_SOURCES.solaris = \
+ $(kash_0_OUTDIR)/sys_signame.c \
+ strlcpy.c
+kash_SOURCES.win = \
+ $(kash_0_OUTDIR)/sys_signame.c \
+ strsignal.c \
+ strlcpy.c \
+ ../lib/nt/ntstat.c \
+ ../lib/nt/nthlpcore.c \
+ ../lib/nt/nthlpfs.c \
+ ../lib/nt/nt_child_inject_standard_handles.c
+ifdef KASH_WIN_FORKED_MODE
+kash_SOURCES.win += \
+ shfork-win.c \
+ shforkA-win.asm
+endif
+
+kash_INTERMEDIATES = \
+ $(kash_0_OUTDIR)/builtins.h \
+ $(kash_0_OUTDIR)/nodes.h \
+ $(kash_0_OUTDIR)/token.h
+kash_CLEAN = \
+ $(kash_INTERMEDIATES) \
+ $(kash_0_OUTDIR)/builtins.c \
+ $(kash_0_OUTDIR)/init.c \
+ $(kash_0_OUTDIR)/nodes.c
+
+kash_main.c_DEFS = KBUILD_SVN_REV=$(KBUILD_SVN_REV)
+
+##
+## The manual page.
+##
+#INSTALLS += kash.man
+#kash.man_TEMPLATE = usr.bin.man
+#kash.man_SOURCES = sh.1=>kash.1
+
+
+#
+# The signal name list:
+#
+$$(kash_0_OUTDIR)/sys_signame.c: $$(bld_signames_1_TARGET) | $$(dir $$@)
+ $< $@
+
+BLDPROGS += bld_signames
+bld_signames_TEMPLATE := BLD
+bld_signames_DEFS := SHELL SMALL
+bld_signames_SOURCES := bld_signames.c
+
+
+if1of ($(KBUILD_TARGET), win os2)
+ KASH_USE_PREGENERATED_CODE = 1
+endif
+
+ifdef KASH_USE_PREGENERATED_CODE
+
+#
+# Use the pregenerated code.
+#
+kash_SOURCES += \
+ $(kash_0_OUTDIR)/arith.c \
+ $(kash_0_OUTDIR)/arith_lex.c
+kash_INTERMEDIATES += \
+ $(kash_0_OUTDIR)/arith.h
+
+define def_copy_generated
+$$$$(kash_0_OUTDIR)/$(src): $(PATH_SUB_CURRENT)/generated/$(src)
+ $$(RM) -f $$@
+ $$(CP) -f $$^ $$@
+endef
+
+$(foreach src, arith.h arith.c arith_lex.c builtins.h builtins.c nodes.h nodes.c token.h init.c,\
+$(eval $(def_copy_generated)))
+
+else # !KASH_USE_PREGENERATED_CODE
+
+#
+# Generate the code on the fly.
+#
+
+USES += lex yacc
+kash_USES = lex yacc
+kash_LEXTOOL = FLEX
+kash_LEXFLAGS = -8
+#kash_YACCTOOL = BISON
+kash_YACCTOOL = YACC
+kash_YACCFLAGS = -ld
+kash_SOURCES += \
+ arith.y \
+ arith_lex.l
+
+#
+# ATTENTION! ATTENTION! ATTENTION!
+#
+# Older ash versions has trouble with some of these scripts, great.
+# Kudos to the NetBSD guys for this clever move. ;)
+#
+# So, when building for the frist time, setting BOOSTRAP_SHELL=/bin/bash is good idea.
+#
+BOOTSTRAP_SHELL ?= $(SHELL)
+
+$$(kash_0_OUTDIR)/builtins.h + $$(kash_0_OUTDIR)/builtins.c: \
+ $$(kash_DEFPATH)/mkbuiltins \
+ $$(kash_DEFPATH)/shell.h \
+ $$(kash_DEFPATH)/builtins.def \
+ | $$(dir $$@)
+ $(BOOTSTRAP_SHELL) $+ $(dir $@)
+ [ -f $(kash_0_OUTDIR)/builtins.h ]
+
+$$(kash_0_OUTDIR)/nodes.h + $$(kash_0_OUTDIR)/nodes.c: \
+ $$(kash_DEFPATH)/mknodes.sh \
+ $$(kash_DEFPATH)/nodetypes \
+ $$(kash_DEFPATH)/nodes.c.pat \
+ | $$(dir $$@)
+ $(BOOTSTRAP_SHELL) $+ $(dir $@)
+ [ -f $(dir $@)/nodes.h ]
+
+$$(kash_0_OUTDIR)/token.h: $$(kash_DEFPATH)/mktokens | $$(dir $$@)
+ $(BOOTSTRAP_SHELL) $+
+ $(MV) token.h $@
+
+$$(kash_0_OUTDIR)/init.c: \
+ $$(kash_DEFPATH)/mkinit.sh \
+ $$(abspathex $$(filter-out $$(kash_0_OUTDIR)/%,$$(kash_SOURCES)), $$(kash_DEFPATH)) \
+ | $$(dir $$@)
+ $(BOOTSTRAP_SHELL) $+
+ $(MV) init.c $@
+
+endif # !KASH_USE_PREGENERATED_CODE
+
+#
+# For debugging file handle inheritance on Windows.
+#
+if "$(KBUILD_TARGET)" == win && 0
+PROGRAMS += tstDump
+tstDump_TEMPLATE = BIN
+tstDump_SOURCES = tstDump.c
+endif
+
+# Include the sub-makefile.
+include $(PATH_SUB_CURRENT)/tests/Makefile.kmk
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
+
diff --git a/src/kash/TOUR b/src/kash/TOUR
new file mode 100644
index 0000000..f5c00c4
--- /dev/null
+++ b/src/kash/TOUR
@@ -0,0 +1,357 @@
+# $NetBSD: TOUR,v 1.8 1996/10/16 14:24:56 christos Exp $
+# @(#)TOUR 8.1 (Berkeley) 5/31/93
+
+NOTE -- This is the original TOUR paper distributed with ash and
+does not represent the current state of the shell. It is provided anyway
+since it provides helpful information for how the shell is structured,
+but be warned that things have changed -- the current shell is
+still under development.
+
+================================================================
+
+ A Tour through Ash
+
+ Copyright 1989 by Kenneth Almquist.
+
+
+DIRECTORIES: The subdirectory bltin contains commands which can
+be compiled stand-alone. The rest of the source is in the main
+ash directory.
+
+SOURCE CODE GENERATORS: Files whose names begin with "mk" are
+programs that generate source code. A complete list of these
+programs is:
+
+ program intput files generates
+ ------- ------------ ---------
+ mkbuiltins builtins builtins.h builtins.c
+ mkinit *.c init.c
+ mknodes nodetypes nodes.h nodes.c
+ mksignames - signames.h signames.c
+ mksyntax - syntax.h syntax.c
+ mktokens - token.h
+ bltin/mkexpr unary_op binary_op operators.h operators.c
+
+There are undoubtedly too many of these. Mkinit searches all the
+C source files for entries looking like:
+
+ INIT {
+ x = 1; /* executed during initialization */
+ }
+
+ RESET {
+ x = 2; /* executed when the shell does a longjmp
+ back to the main command loop */
+ }
+
+ SHELLPROC {
+ x = 3; /* executed when the shell runs a shell procedure */
+ }
+
+It pulls this code out into routines which are when particular
+events occur. The intent is to improve modularity by isolating
+the information about which modules need to be explicitly
+initialized/reset within the modules themselves.
+
+Mkinit recognizes several constructs for placing declarations in
+the init.c file.
+ INCLUDE "file.h"
+includes a file. The storage class MKINIT makes a declaration
+available in the init.c file, for example:
+ MKINIT int funcnest; /* depth of function calls */
+MKINIT alone on a line introduces a structure or union declara-
+tion:
+ MKINIT
+ struct redirtab {
+ short renamed[10];
+ };
+Preprocessor #define statements are copied to init.c without any
+special action to request this.
+
+INDENTATION: The ash source is indented in multiples of six
+spaces. The only study that I have heard of on the subject con-
+cluded that the optimal amount to indent is in the range of four
+to six spaces. I use six spaces since it is not too big a jump
+from the widely used eight spaces. If you really hate six space
+indentation, use the adjind (source included) program to change
+it to something else.
+
+EXCEPTIONS: Code for dealing with exceptions appears in
+exceptions.c. The C language doesn't include exception handling,
+so I implement it using setjmp and longjmp. The global variable
+exception contains the type of exception. EXERROR is raised by
+calling error. EXINT is an interrupt. EXSHELLPROC is an excep-
+tion which is raised when a shell procedure is invoked. The pur-
+pose of EXSHELLPROC is to perform the cleanup actions associated
+with other exceptions. After these cleanup actions, the shell
+can interpret a shell procedure itself without exec'ing a new
+copy of the shell.
+
+INTERRUPTS: In an interactive shell, an interrupt will cause an
+EXINT exception to return to the main command loop. (Exception:
+EXINT is not raised if the user traps interrupts using the trap
+command.) The INTOFF and INTON macros (defined in exception.h)
+provide uninterruptable critical sections. Between the execution
+of INTOFF and the execution of INTON, interrupt signals will be
+held for later delivery. INTOFF and INTON can be nested.
+
+MEMALLOC.C: Memalloc.c defines versions of malloc and realloc
+which call error when there is no memory left. It also defines a
+stack oriented memory allocation scheme. Allocating off a stack
+is probably more efficient than allocation using malloc, but the
+big advantage is that when an exception occurs all we have to do
+to free up the memory in use at the time of the exception is to
+restore the stack pointer. The stack is implemented using a
+linked list of blocks.
+
+STPUTC: If the stack were contiguous, it would be easy to store
+strings on the stack without knowing in advance how long the
+string was going to be:
+ p = stackptr;
+ *p++ = c; /* repeated as many times as needed */
+ stackptr = p;
+The folloing three macros (defined in memalloc.h) perform these
+operations, but grow the stack if you run off the end:
+ STARTSTACKSTR(p);
+ STPUTC(c, p); /* repeated as many times as needed */
+ grabstackstr(p);
+
+We now start a top-down look at the code:
+
+MAIN.C: The main routine performs some initialization, executes
+the user's profile if necessary, and calls cmdloop. Cmdloop is
+repeatedly parses and executes commands.
+
+OPTIONS.C: This file contains the option processing code. It is
+called from main to parse the shell arguments when the shell is
+invoked, and it also contains the set builtin. The -i and -j op-
+tions (the latter turns on job control) require changes in signal
+handling. The routines setjobctl (in jobs.c) and setinteractive
+(in trap.c) are called to handle changes to these options.
+
+PARSING: The parser code is all in parser.c. A recursive des-
+cent parser is used. Syntax tables (generated by mksyntax) are
+used to classify characters during lexical analysis. There are
+three tables: one for normal use, one for use when inside single
+quotes, and one for use when inside double quotes. The tables
+are machine dependent because they are indexed by character vari-
+ables and the range of a char varies from machine to machine.
+
+PARSE OUTPUT: The output of the parser consists of a tree of
+nodes. The various types of nodes are defined in the file node-
+types.
+
+Nodes of type NARG are used to represent both words and the con-
+tents of here documents. An early version of ash kept the con-
+tents of here documents in temporary files, but keeping here do-
+cuments in memory typically results in significantly better per-
+formance. It would have been nice to make it an option to use
+temporary files for here documents, for the benefit of small
+machines, but the code to keep track of when to delete the tem-
+porary files was complex and I never fixed all the bugs in it.
+(AT&T has been maintaining the Bourne shell for more than ten
+years, and to the best of my knowledge they still haven't gotten
+it to handle temporary files correctly in obscure cases.)
+
+The text field of a NARG structure points to the text of the
+word. The text consists of ordinary characters and a number of
+special codes defined in parser.h. The special codes are:
+
+ CTLVAR Variable substitution
+ CTLENDVAR End of variable substitution
+ CTLBACKQ Command substitution
+ CTLBACKQ|CTLQUOTE Command substitution inside double quotes
+ CTLESC Escape next character
+
+A variable substitution contains the following elements:
+
+ CTLVAR type name '=' [ alternative-text CTLENDVAR ]
+
+The type field is a single character specifying the type of sub-
+stitution. The possible types are:
+
+ VSNORMAL $var
+ VSMINUS ${var-text}
+ VSMINUS|VSNUL ${var:-text}
+ VSPLUS ${var+text}
+ VSPLUS|VSNUL ${var:+text}
+ VSQUESTION ${var?text}
+ VSQUESTION|VSNUL ${var:?text}
+ VSASSIGN ${var=text}
+ VSASSIGN|VSNUL ${var=text}
+
+In addition, the type field will have the VSQUOTE flag set if the
+variable is enclosed in double quotes. The name of the variable
+comes next, terminated by an equals sign. If the type is not
+VSNORMAL, then the text field in the substitution follows, ter-
+minated by a CTLENDVAR byte.
+
+Commands in back quotes are parsed and stored in a linked list.
+The locations of these commands in the string are indicated by
+CTLBACKQ and CTLBACKQ+CTLQUOTE characters, depending upon whether
+the back quotes were enclosed in double quotes.
+
+The character CTLESC escapes the next character, so that in case
+any of the CTL characters mentioned above appear in the input,
+they can be passed through transparently. CTLESC is also used to
+escape '*', '?', '[', and '!' characters which were quoted by the
+user and thus should not be used for file name generation.
+
+CTLESC characters have proved to be particularly tricky to get
+right. In the case of here documents which are not subject to
+variable and command substitution, the parser doesn't insert any
+CTLESC characters to begin with (so the contents of the text
+field can be written without any processing). Other here docu-
+ments, and words which are not subject to splitting and file name
+generation, have the CTLESC characters removed during the vari-
+able and command substitution phase. Words which are subject
+splitting and file name generation have the CTLESC characters re-
+moved as part of the file name phase.
+
+EXECUTION: Command execution is handled by the following files:
+ eval.c The top level routines.
+ redir.c Code to handle redirection of input and output.
+ jobs.c Code to handle forking, waiting, and job control.
+ exec.c Code to to path searches and the actual exec sys call.
+ expand.c Code to evaluate arguments.
+ var.c Maintains the variable symbol table. Called from expand.c.
+
+EVAL.C: Evaltree recursively executes a parse tree. The exit
+status is returned in the global variable exitstatus. The alter-
+native entry evalbackcmd is called to evaluate commands in back
+quotes. It saves the result in memory if the command is a buil-
+tin; otherwise it forks off a child to execute the command and
+connects the standard output of the child to a pipe.
+
+JOBS.C: To create a process, you call makejob to return a job
+structure, and then call forkshell (passing the job structure as
+an argument) to create the process. Waitforjob waits for a job
+to complete. These routines take care of process groups if job
+control is defined.
+
+REDIR.C: Ash allows file descriptors to be redirected and then
+restored without forking off a child process. This is accom-
+plished by duplicating the original file descriptors. The redir-
+tab structure records where the file descriptors have be dupli-
+cated to.
+
+EXEC.C: The routine find_command locates a command, and enters
+the command in the hash table if it is not already there. The
+third argument specifies whether it is to print an error message
+if the command is not found. (When a pipeline is set up,
+find_command is called for all the commands in the pipeline be-
+fore any forking is done, so to get the commands into the hash
+table of the parent process. But to make command hashing as
+transparent as possible, we silently ignore errors at that point
+and only print error messages if the command cannot be found
+later.)
+
+The routine shellexec is the interface to the exec system call.
+
+EXPAND.C: Arguments are processed in three passes. The first
+(performed by the routine argstr) performs variable and command
+substitution. The second (ifsbreakup) performs word splitting
+and the third (expandmeta) performs file name generation. If the
+"/u" directory is simulated, then when "/u/username" is replaced
+by the user's home directory, the flag "didudir" is set. This
+tells the cd command that it should print out the directory name,
+just as it would if the "/u" directory were implemented using
+symbolic links.
+
+VAR.C: Variables are stored in a hash table. Probably we should
+switch to extensible hashing. The variable name is stored in the
+same string as the value (using the format "name=value") so that
+no string copying is needed to create the environment of a com-
+mand. Variables which the shell references internally are preal-
+located so that the shell can reference the values of these vari-
+ables without doing a lookup.
+
+When a program is run, the code in eval.c sticks any environment
+variables which precede the command (as in "PATH=xxx command") in
+the variable table as the simplest way to strip duplicates, and
+then calls "environment" to get the value of the environment.
+There are two consequences of this. First, if an assignment to
+PATH precedes the command, the value of PATH before the assign-
+ment must be remembered and passed to shellexec. Second, if the
+program turns out to be a shell procedure, the strings from the
+environment variables which preceded the command must be pulled
+out of the table and replaced with strings obtained from malloc,
+since the former will automatically be freed when the stack (see
+the entry on memalloc.c) is emptied.
+
+BUILTIN COMMANDS: The procedures for handling these are scat-
+tered throughout the code, depending on which location appears
+most appropriate. They can be recognized because their names al-
+ways end in "cmd". The mapping from names to procedures is
+specified in the file builtins, which is processed by the mkbuil-
+tins command.
+
+A builtin command is invoked with argc and argv set up like a
+normal program. A builtin command is allowed to overwrite its
+arguments. Builtin routines can call nextopt to do option pars-
+ing. This is kind of like getopt, but you don't pass argc and
+argv to it. Builtin routines can also call error. This routine
+normally terminates the shell (or returns to the main command
+loop if the shell is interactive), but when called from a builtin
+command it causes the builtin command to terminate with an exit
+status of 2.
+
+The directory bltins contains commands which can be compiled in-
+dependently but can also be built into the shell for efficiency
+reasons. The makefile in this directory compiles these programs
+in the normal fashion (so that they can be run regardless of
+whether the invoker is ash), but also creates a library named
+bltinlib.a which can be linked with ash. The header file bltin.h
+takes care of most of the differences between the ash and the
+stand-alone environment. The user should call the main routine
+"main", and #define main to be the name of the routine to use
+when the program is linked into ash. This #define should appear
+before bltin.h is included; bltin.h will #undef main if the pro-
+gram is to be compiled stand-alone.
+
+CD.C: This file defines the cd and pwd builtins. The pwd com-
+mand runs /bin/pwd the first time it is invoked (unless the user
+has already done a cd to an absolute pathname), but then
+remembers the current directory and updates it when the cd com-
+mand is run, so subsequent pwd commands run very fast. The main
+complication in the cd command is in the docd command, which
+resolves symbolic links into actual names and informs the user
+where the user ended up if he crossed a symbolic link.
+
+SIGNALS: Trap.c implements the trap command. The routine set-
+signal figures out what action should be taken when a signal is
+received and invokes the signal system call to set the signal ac-
+tion appropriately. When a signal that a user has set a trap for
+is caught, the routine "onsig" sets a flag. The routine dotrap
+is called at appropriate points to actually handle the signal.
+When an interrupt is caught and no trap has been set for that
+signal, the routine "onint" in error.c is called.
+
+OUTPUT: Ash uses it's own output routines. There are three out-
+put structures allocated. "Output" represents the standard out-
+put, "errout" the standard error, and "memout" contains output
+which is to be stored in memory. This last is used when a buil-
+tin command appears in backquotes, to allow its output to be col-
+lected without doing any I/O through the UNIX operating system.
+The variables out1 and out2 normally point to output and errout,
+respectively, but they are set to point to memout when appropri-
+ate inside backquotes.
+
+INPUT: The basic input routine is pgetc, which reads from the
+current input file. There is a stack of input files; the current
+input file is the top file on this stack. The code allows the
+input to come from a string rather than a file. (This is for the
+-c option and the "." and eval builtin commands.) The global
+variable plinno is saved and restored when files are pushed and
+popped from the stack. The parser routines store the number of
+the current line in this variable.
+
+DEBUGGING: If DEBUG is defined in shell.h, then the shell will
+write debugging information to the file $HOME/trace. Most of
+this is done using the TRACE macro, which takes a set of printf
+arguments inside two sets of parenthesis. Example:
+"TRACE(("n=%d0, n))". The double parenthesis are necessary be-
+cause the preprocessor can't handle functions with a variable
+number of arguments. Defining DEBUG also causes the shell to
+generate a core dump if it is sent a quit signal. The tracing
+code is in show.c.
diff --git a/src/kash/alias.c b/src/kash/alias.c
new file mode 100644
index 0000000..8a3b3a8
--- /dev/null
+++ b/src/kash/alias.c
@@ -0,0 +1,311 @@
+/* $NetBSD: alias.c,v 1.12 2003/08/07 09:05:29 agc Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)alias.c 8.3 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: alias.c,v 1.12 2003/08/07 09:05:29 agc Exp $");
+#endif /* not lint */
+#endif
+
+#include <stdlib.h>
+#include "shell.h"
+#include "input.h"
+#include "output.h"
+#include "error.h"
+#include "memalloc.h"
+#include "mystring.h"
+#include "alias.h"
+#include "options.h" /* XXX for argptr (should remove?) */
+#include "var.h"
+#include "shinstance.h"
+
+/*#define ATABSIZE 39
+
+struct alias *atab[ATABSIZE];*/
+
+STATIC void setalias(shinstance *, char *, char *);
+STATIC int unalias(shinstance *, char *);
+STATIC struct alias **hashalias(shinstance *, char *);
+
+#ifndef SH_FORKED_MODE
+void
+subshellinitalias(shinstance *psh, shinstance *inherit)
+{
+ unsigned i;
+ unsigned left = inherit->aliases;
+ if (left == 0)
+ return;
+ for (i = 0; i < K_ELEMENTS(inherit->atab); i++)
+ {
+ struct alias const *asrc = inherit->atab[i];
+ if (asrc)
+ {
+ struct alias **ppdst = &psh->atab[i];
+ do
+ {
+ if (*asrc->name)
+ {
+ struct alias *dst = (struct alias *)ckmalloc(psh, sizeof(*dst));
+ dst->name = savestr(psh, asrc->name);
+ dst->val = savestr(psh, asrc->val);
+ dst->flag = asrc->flag;
+ *ppdst = dst;
+ ppdst = &dst->next;
+ }
+ left--;
+ asrc = asrc->next;
+ } while (asrc);
+ *ppdst = NULL;
+ if (left == 0)
+ break;
+ }
+ }
+}
+#endif /* !SH_FORKED_MODE */
+
+STATIC
+void
+setalias(shinstance *psh, char *name, char *val)
+{
+ struct alias *ap, **app;
+
+ app = hashalias(psh, name);
+ for (ap = *app; ap; ap = ap->next) {
+ if (equal(name, ap->name)) {
+ INTOFF;
+ ckfree(psh, ap->val);
+ ap->val = savestr(psh, val);
+ INTON;
+ return;
+ }
+ }
+ /* not found */
+ INTOFF;
+ ap = ckmalloc(psh, sizeof (struct alias));
+ ap->name = savestr(psh, name);
+ /*
+ * XXX - HACK: in order that the parser will not finish reading the
+ * alias value off the input before processing the next alias, we
+ * dummy up an extra space at the end of the alias. This is a crock
+ * and should be re-thought. The idea (if you feel inclined to help)
+ * is to avoid alias recursions. The mechanism used is: when
+ * expanding an alias, the value of the alias is pushed back on the
+ * input as a string and a pointer to the alias is stored with the
+ * string. The alias is marked as being in use. When the input
+ * routine finishes reading the string, it markes the alias not
+ * in use. The problem is synchronization with the parser. Since
+ * it reads ahead, the alias is marked not in use before the
+ * resulting token(s) is next checked for further alias sub. The
+ * H A C K is that we add a little fluff after the alias value
+ * so that the string will not be exhausted. This is a good
+ * idea ------- ***NOT***
+ */
+#ifdef notyet
+ ap->val = savestr(psh, val);
+#else /* hack */
+ {
+ size_t len = strlen(val);
+ ap->val = ckmalloc(psh, len + 2);
+ memcpy(ap->val, val, len);
+ ap->val[len] = ' '; /* fluff */
+ ap->val[len+1] = '\0';
+ }
+#endif
+ ap->next = *app;
+ *app = ap;
+ psh->aliases++;
+ INTON;
+}
+
+STATIC int
+unalias(shinstance *psh, char *name)
+{
+ struct alias *ap, **app;
+
+ app = hashalias(psh, name);
+
+ for (ap = *app; ap; app = &(ap->next), ap = ap->next) {
+ if (equal(name, ap->name)) {
+ /*
+ * if the alias is currently in use (i.e. its
+ * buffer is being used by the input routine) we
+ * just null out the name instead of freeing it.
+ * We could clear it out later, but this situation
+ * is so rare that it hardly seems worth it.
+ */
+ if (ap->flag & ALIASINUSE)
+ *ap->name = '\0';
+ else {
+ INTOFF;
+ *app = ap->next;
+ ckfree(psh, ap->name);
+ ckfree(psh, ap->val);
+ ckfree(psh, ap);
+ psh->aliases--;
+ INTON;
+ }
+ return (0);
+ }
+ }
+
+ return (1);
+}
+
+#ifdef mkinit
+MKINIT void rmaliases(shinstance *psh);
+
+SHELLPROC {
+ rmaliases(psh);
+}
+#endif
+
+void
+rmaliases(shinstance *psh)
+{
+ struct alias *ap, *tmp;
+ int i;
+
+ INTOFF;
+ for (i = 0; i < ATABSIZE; i++) {
+ ap = psh->atab[i];
+ psh->atab[i] = NULL;
+ while (ap) {
+ ckfree(psh, ap->name);
+ ckfree(psh, ap->val);
+ tmp = ap;
+ ap = ap->next;
+ ckfree(psh, tmp);
+ }
+ }
+ INTON;
+}
+
+struct alias *
+lookupalias(shinstance *psh, char *name, int check)
+{
+ struct alias *ap = *hashalias(psh, name);
+
+ for (; ap; ap = ap->next) {
+ if (equal(name, ap->name)) {
+ if (check && (ap->flag & ALIASINUSE))
+ return (NULL);
+ return (ap);
+ }
+ }
+
+ return (NULL);
+}
+
+char *
+get_alias_text(shinstance *psh, char *name)
+{
+ struct alias *ap;
+
+ ap = lookupalias(psh, name, 0);
+ if (ap == NULL)
+ return NULL;
+ return ap->val;
+}
+
+/*
+ * TODO - sort output
+ */
+int
+aliascmd(shinstance *psh, int argc, char **argv)
+{
+ char *n, *v;
+ int ret = 0;
+ struct alias *ap;
+
+ if (argc == 1) {
+ int i;
+
+ for (i = 0; i < ATABSIZE; i++)
+ for (ap = psh->atab[i]; ap; ap = ap->next) {
+ if (*ap->name != '\0') {
+ out1fmt(psh, "alias %s=", ap->name);
+ print_quoted(psh, ap->val);
+ out1c(psh, '\n');
+ }
+ }
+ return (0);
+ }
+ while ((n = *++argv) != NULL) {
+ if ((v = strchr(n+1, '=')) == NULL) { /* n+1: funny ksh stuff */
+ if ((ap = lookupalias(psh, n, 0)) == NULL) {
+ outfmt(psh->out2, "alias: %s not found\n", n);
+ ret = 1;
+ } else {
+ out1fmt(psh, "alias %s=", n);
+ print_quoted(psh, ap->val);
+ out1c(psh, '\n');
+ }
+ } else {
+ *v++ = '\0';
+ setalias(psh, n, v);
+ }
+ }
+
+ return (ret);
+}
+
+int
+unaliascmd(shinstance *psh, int argc, char **argv)
+{
+ int i;
+
+ while ((i = nextopt(psh, "a")) != '\0') {
+ if (i == 'a') {
+ rmaliases(psh);
+ return (0);
+ }
+ }
+ for (i = 0; *psh->argptr; psh->argptr++)
+ i = unalias(psh, *psh->argptr);
+
+ return (i);
+}
+
+STATIC struct alias **
+hashalias(shinstance *psh, char *p)
+{
+ unsigned int hashval;
+
+ hashval = *p << 4;
+ while (*p)
+ hashval+= *p++;
+ return &psh->atab[hashval % ATABSIZE];
+}
diff --git a/src/kash/alias.h b/src/kash/alias.h
new file mode 100644
index 0000000..ad980b9
--- /dev/null
+++ b/src/kash/alias.h
@@ -0,0 +1,53 @@
+/* $NetBSD: alias.h,v 1.6 2003/08/07 09:05:29 agc Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)alias.h 8.2 (Berkeley) 5/4/95
+ */
+
+#define ALIASINUSE 1
+
+struct alias {
+ struct alias *next;
+ char *name;
+ char *val;
+ int flag;
+};
+
+#ifndef SH_FORKED_MODE
+void subshellinitalias(shinstance *, shinstance *);
+#endif
+struct alias *lookupalias(struct shinstance *, char *, int);
+char *get_alias_text(struct shinstance *, char *);
+int aliascmd(struct shinstance *, int, char **);
+int unaliascmd(struct shinstance *, int, char **);
+void rmaliases(struct shinstance *);
diff --git a/src/kash/arith.y b/src/kash/arith.y
new file mode 100644
index 0000000..f53b3da
--- /dev/null
+++ b/src/kash/arith.y
@@ -0,0 +1,209 @@
+%{
+/* $NetBSD: arith.y,v 1.17 2003/09/17 17:33:36 jmmv Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)arith.y 8.3 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: arith.y,v 1.17 2003/09/17 17:33:36 jmmv Exp $");
+#endif /* not lint */
+#endif
+
+#include <stdlib.h>
+#include "expand.h"
+#include "shell.h"
+#include "error.h"
+#include "output.h"
+#include "memalloc.h"
+#include "shinstance.h"
+
+shinstance *arith_psh;
+const char *arith_buf, *arith_startbuf;
+
+void yyerror(const char *);
+#ifdef TESTARITH
+int main(int , char *[]);
+int error(char *);
+#else
+# undef malloc
+# define malloc(cb) sh_malloc(NULL, (cb))
+# undef realloc
+# define realloc(pv,cb) sh_realloc(NULL, (pv), (cb))
+# undef free
+# define free(pv) sh_free(NULL, (pv))
+#endif
+
+%}
+%token ARITH_NUM ARITH_LPAREN ARITH_RPAREN
+
+%left ARITH_OR
+%left ARITH_AND
+%left ARITH_BOR
+%left ARITH_BXOR
+%left ARITH_BAND
+%left ARITH_EQ ARITH_NE
+%left ARITH_LT ARITH_GT ARITH_GE ARITH_LE
+%left ARITH_LSHIFT ARITH_RSHIFT
+%left ARITH_ADD ARITH_SUB
+%left ARITH_MUL ARITH_DIV ARITH_REM
+%left ARITH_UNARYMINUS ARITH_UNARYPLUS ARITH_NOT ARITH_BNOT
+%%
+
+exp: expr {
+ return ($1);
+ }
+ ;
+
+
+expr: ARITH_LPAREN expr ARITH_RPAREN { $$ = $2; }
+ | expr ARITH_OR expr { $$ = $1 ? $1 : $3 ? $3 : 0; }
+ | expr ARITH_AND expr { $$ = $1 ? ( $3 ? $3 : 0 ) : 0; }
+ | expr ARITH_BOR expr { $$ = $1 | $3; }
+ | expr ARITH_BXOR expr { $$ = $1 ^ $3; }
+ | expr ARITH_BAND expr { $$ = $1 & $3; }
+ | expr ARITH_EQ expr { $$ = $1 == $3; }
+ | expr ARITH_GT expr { $$ = $1 > $3; }
+ | expr ARITH_GE expr { $$ = $1 >= $3; }
+ | expr ARITH_LT expr { $$ = $1 < $3; }
+ | expr ARITH_LE expr { $$ = $1 <= $3; }
+ | expr ARITH_NE expr { $$ = $1 != $3; }
+ | expr ARITH_LSHIFT expr { $$ = $1 << $3; }
+ | expr ARITH_RSHIFT expr { $$ = $1 >> $3; }
+ | expr ARITH_ADD expr { $$ = $1 + $3; }
+ | expr ARITH_SUB expr { $$ = $1 - $3; }
+ | expr ARITH_MUL expr { $$ = $1 * $3; }
+ | expr ARITH_DIV expr {
+ if ($3 == 0)
+ yyerror("division by zero");
+ $$ = $1 / $3;
+ }
+ | expr ARITH_REM expr {
+ if ($3 == 0)
+ yyerror("division by zero");
+ $$ = $1 % $3;
+ }
+ | ARITH_NOT expr { $$ = !($2); }
+ | ARITH_BNOT expr { $$ = ~($2); }
+ | ARITH_SUB expr %prec ARITH_UNARYMINUS { $$ = -($2); }
+ | ARITH_ADD expr %prec ARITH_UNARYPLUS { $$ = $2; }
+ | ARITH_NUM
+ ;
+%%
+int
+arith(shinstance *psh, const char *s)
+{
+ long result;
+
+ INTOFF;
+/* todo lock */
+ arith_psh = psh;
+ arith_buf = arith_startbuf = s;
+ result = yyparse();
+ arith_lex_reset(); /* reprime lex */
+ arith_psh = NULL;
+/* todo unlock */
+ INTON;
+
+ return (result);
+}
+
+
+/*
+ * The exp(1) builtin.
+ */
+int
+expcmd(shinstance *psh, int argc, char **argv)
+{
+ const char *p;
+ char *concat;
+ char **ap;
+ long i;
+
+ if (argc > 1) {
+ p = argv[1];
+ if (argc > 2) {
+ /*
+ * concatenate arguments
+ */
+ STARTSTACKSTR(psh, concat);
+ ap = argv + 2;
+ for (;;) {
+ while (*p)
+ STPUTC(psh, *p++, concat);
+ if ((p = *ap++) == NULL)
+ break;
+ STPUTC(psh, ' ', concat);
+ }
+ STPUTC(psh, '\0', concat);
+ p = grabstackstr(psh, concat);
+ }
+ } else
+ p = "";
+
+ i = arith(psh, p);
+
+ out1fmt(psh, "%ld\n", i);
+ return (! i);
+}
+
+/*************************/
+#ifdef TEST_ARITH
+#include <stdio.h>
+main(argc, argv)
+ char *argv[];
+{
+ printf("%d\n", exp(argv[1]));
+}
+error(s)
+ char *s;
+{
+ fprintf(stderr, "exp: %s\n", s);
+ exit(1);
+}
+#endif
+
+void
+yyerror(const char *s)
+{
+ shinstance *psh = arith_psh;
+#ifndef YYBISON /* yyerrok references yyerrstatus which is a local variable in yyparse().*/
+ yyerrok;
+#endif
+ yyclearin;
+ arith_lex_reset(); /* reprime lex */
+/** @todo unlock */
+ error(psh, "arithmetic expression: %s: \"%s\"", s, arith_startbuf);
+ /* NOTREACHED */
+}
diff --git a/src/kash/arith_lex.l b/src/kash/arith_lex.l
new file mode 100644
index 0000000..b61e15b
--- /dev/null
+++ b/src/kash/arith_lex.l
@@ -0,0 +1,172 @@
+%option never-interactive
+%option noyywrap
+%option noinput
+%option nounput
+%option noyyget_out
+%option noyy_push_state
+%option noyy_pop_state
+%option noyy_top_state
+%option noyy_scan_buffer
+%option noyy_scan_bytes
+%option noyy_scan_string
+%option noyyget_extra
+%option noyyset_extra
+%option noyyget_leng
+%option noyyget_text
+%option noyyget_lineno
+%option noyyset_lineno
+%option noyyget_in
+%option noyyset_in
+%option noyyget_out
+%option noyyset_out
+%option noyyget_lval
+%option noyyset_lval
+%option noyyget_lloc
+%option noyyset_lloc
+%option noyyget_debug
+%option noyyset_debug
+%option noyyalloc
+%option noyyrealloc
+%option noyyfree
+/** @todo %option reentrant */
+%{
+/* $NetBSD: arith_lex.l,v 1.13 2005/03/21 22:37:09 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)arith_lex.l 8.3 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: arith_lex.l,v 1.13 2005/03/21 22:37:09 dsl Exp $");
+#endif /* not lint */
+#endif
+
+#include <stdio.h>
+#include "arith.h"
+#include "error.h"
+#include "expand.h"
+#include "var.h"
+#include "shinstance.h"
+
+extern int yylval;
+extern shinstance *arith_psh;
+extern char *arith_buf, *arith_startbuf;
+#undef YY_INPUT
+#define YY_INPUT(buf,result,max) \
+ result = (*buf = *arith_buf++) ? 1 : YY_NULL;
+#define YY_NO_UNPUT
+
+/* Avoid unnecessary libc bits. */
+#undef ECHO
+#define ECHO \
+ do {} while (0)
+#undef stdin
+#define stdin \
+ NULL
+#undef stdout
+#define stdout \
+ NULL
+#undef fprintf
+#define fprintf(a, b, c) \
+ ((void)0)
+#undef exit
+#define exit(rc) \
+ do {} while (0)
+#define YY_FATAL_ERROR(msg) \
+ error(arith_psh, "arith: fatal error: %s", msg)
+%}
+
+%%
+[ \t\n] { ; }
+0x[0-9a-fA-F]+ { yylval = strtol(yytext, 0, 0); return(ARITH_NUM); }
+0[0-7]* { yylval = strtol(yytext, 0, 0); return(ARITH_NUM); }
+[1-9][0-9]* { yylval = strtol(yytext, 0, 0); return(ARITH_NUM); }
+[A-Za-z_][A-Za-z_0-9]* { char *v = lookupvar(arith_psh, yytext);
+ if (v) {
+ yylval = strtol(v, &v, 0);
+ if (*v == 0)
+ return ARITH_NUM;
+ }
+ error(arith_psh, "arith: syntax error: \"%s\"", arith_startbuf);
+ }
+"(" { return(ARITH_LPAREN); }
+")" { return(ARITH_RPAREN); }
+"||" { return(ARITH_OR); }
+"&&" { return(ARITH_AND); }
+"|" { return(ARITH_BOR); }
+"^" { return(ARITH_BXOR); }
+"&" { return(ARITH_BAND); }
+"==" { return(ARITH_EQ); }
+"!=" { return(ARITH_NE); }
+">" { return(ARITH_GT); }
+">=" { return(ARITH_GE); }
+"<" { return(ARITH_LT); }
+"<=" { return(ARITH_LE); }
+"<<" { return(ARITH_LSHIFT); }
+">>" { return(ARITH_RSHIFT); }
+"*" { return(ARITH_MUL); }
+"/" { return(ARITH_DIV); }
+"%" { return(ARITH_REM); }
+"+" { return(ARITH_ADD); }
+"-" { return(ARITH_SUB); }
+"~" { return(ARITH_BNOT); }
+"!" { return(ARITH_NOT); }
+. { error(arith_psh, "arith: syntax error: \"%s\"", arith_startbuf); }
+%%
+
+void
+arith_lex_reset() {
+#ifdef YY_NEW_FILE
+ YY_NEW_FILE;
+#endif
+}
+
+void *
+yyalloc(yy_size_t cb)
+{
+ return sh_malloc(NULL, cb);
+}
+
+void *
+yyrealloc(void *pv, yy_size_t cb)
+{
+ return sh_realloc(NULL, pv, cb);
+}
+
+void
+yyfree(void *pv)
+{
+ sh_free(NULL, pv);
+}
+
diff --git a/src/kash/bld_signames.c b/src/kash/bld_signames.c
new file mode 100644
index 0000000..7f266ce
--- /dev/null
+++ b/src/kash/bld_signames.c
@@ -0,0 +1,172 @@
+
+#include "shinstance.h" /* for MSC */
+#include <string.h>
+#include <stdio.h>
+
+int main(int argc, char **argv)
+{
+ char aszSigName[NSIG][16];
+ FILE *pFile;
+ int i;
+
+ if (argc != 2 || *argv[1] == '\0')
+ {
+ fprintf(stderr, "syntax error: Expected exactly one parameter, the output-file!\n");
+ return 2;
+ }
+
+ /*
+ * Populate the name array.
+ */
+ strcpy(aszSigName[0], "Signal 0");
+ for (i = 1; i < NSIG; ++i)
+ sprintf(aszSigName[i], "%i", i);
+
+#define SET_SIG_STR(sig) strcpy(aszSigName[SIG##sig], #sig);
+
+#if defined(SIGRTMIN) && defined(SIGRTMAX)
+ if (SIGRTMIN < SIGRTMAX && SIGRTMAX < NSIG)
+ {
+ /* lets mimick what bash seems to be doing. */
+ int const iMidWay = SIGRTMIN + (SIGRTMAX - SIGRTMIN) / 2;
+ SET_SIG_STR(RTMIN);
+ SET_SIG_STR(RTMAX);
+
+ for (i = SIGRTMIN + 1; i <= iMidWay; i++)
+ sprintf(aszSigName[i], "RTMIN+%i", (int)(i - SIGRTMIN));
+ for (; i < SIGRTMAX; i++)
+ sprintf(aszSigName[i], "RTMAX%i", (int)(i - SIGRTMAX));
+ }
+ else
+ fprintf(stderr, "warning: SIGRTMIN=%d, SIGRTMAX=%d, NSIG=%d\n", (int)SIGRTMIN, (int)SIGRTMAX, (int)NSIG);
+#endif
+
+#ifdef SIGHUP
+ SET_SIG_STR(HUP);
+#endif
+#ifdef SIGINT
+ SET_SIG_STR(INT);
+#endif
+#ifdef SIGQUIT
+ SET_SIG_STR(QUIT);
+#endif
+#ifdef SIGILL
+ SET_SIG_STR(ILL);
+#endif
+#ifdef SIGTRAP
+ SET_SIG_STR(TRAP);
+#endif
+#ifdef SIGABRT
+ SET_SIG_STR(ABRT);
+#endif
+#ifdef SIGIOT
+ SET_SIG_STR(IOT);
+#endif
+#ifdef SIGBUS
+ SET_SIG_STR(BUS);
+#endif
+#ifdef SIGFPE
+ SET_SIG_STR(FPE);
+#endif
+#ifdef SIGKILL
+ SET_SIG_STR(KILL);
+#endif
+#ifdef SIGUSR1
+ SET_SIG_STR(USR1);
+#endif
+#ifdef SIGSEGV
+ SET_SIG_STR(SEGV);
+#endif
+#ifdef SIGUSR2
+ SET_SIG_STR(USR2);
+#endif
+#ifdef SIGPIPE
+ SET_SIG_STR(PIPE);
+#endif
+#ifdef SIGALRM
+ SET_SIG_STR(ALRM);
+#endif
+#ifdef SIGTERM
+ SET_SIG_STR(TERM);
+#endif
+#ifdef SIGSTKFLT
+ SET_SIG_STR(STKFLT);
+#endif
+#ifdef SIGCHLD
+ SET_SIG_STR(CHLD);
+#endif
+#ifdef SIGCONT
+ SET_SIG_STR(CONT);
+#endif
+#ifdef SIGSTOP
+ SET_SIG_STR(STOP);
+#endif
+#ifdef SIGTSTP
+ SET_SIG_STR(TSTP);
+#endif
+#ifdef SIGTTIN
+ SET_SIG_STR(TTIN);
+#endif
+#ifdef SIGTTOU
+ SET_SIG_STR(TTOU);
+#endif
+#ifdef SIGURG
+ SET_SIG_STR(URG);
+#endif
+#ifdef SIGXCPU
+ SET_SIG_STR(XCPU);
+#endif
+#ifdef SIGXFSZ
+ SET_SIG_STR(XFSZ);
+#endif
+#ifdef SIGVTALRM
+ SET_SIG_STR(VTALRM);
+#endif
+#ifdef SIGPROF
+ SET_SIG_STR(PROF);
+#endif
+#ifdef SIGWINCH
+ SET_SIG_STR(WINCH);
+#endif
+#ifdef SIGIO
+ SET_SIG_STR(IO);
+#endif
+#ifdef SIGPWR
+ SET_SIG_STR(PWR);
+#endif
+#ifdef SIGSYS
+ SET_SIG_STR(SYS);
+#endif
+#ifdef SIGBREAK
+ SET_SIG_STR(BREAK);
+#endif
+#undef SET_SIG_STR
+
+ /*
+ * Write out the list.
+ */
+ pFile = fopen(argv[1], "w");
+ if (!pFile)
+ {
+ fprintf(stderr, "error: failed to open '%s' for writing\n", argv[1]);
+ return 1;
+ }
+ fputs("/* autogenerate */\n"
+ "\n"
+ "#include \"shinstance.h\"\n"
+ "\n"
+ "const char * const sys_signame[NSIG] = \n"
+ "{\n"
+ , pFile);
+ for (i = 0; i < NSIG; i++)
+ fprintf(pFile, " \"%s\",\n", aszSigName[i]);
+ fputs("};\n", pFile);
+
+ if (fclose(pFile) != 0)
+ {
+ fprintf(stderr, "error: error writing/closing '%s' after writing it\n", argv[1]);
+ return 1;
+ }
+ return 0;
+}
+
diff --git a/src/kash/bltin/Makefile.kup b/src/kash/bltin/Makefile.kup
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/kash/bltin/Makefile.kup
diff --git a/src/kash/bltin/echo.1 b/src/kash/bltin/echo.1
new file mode 100644
index 0000000..7e71fa3
--- /dev/null
+++ b/src/kash/bltin/echo.1
@@ -0,0 +1,109 @@
+.\" $NetBSD: echo.1,v 1.13 2003/08/07 09:05:40 agc Exp $
+.\"
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Kenneth Almquist.
+.\" Copyright 1989 by Kenneth Almquist
+.\"
+.\" 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.
+.\"
+.\" @(#)echo.1 8.1 (Berkeley) 5/31/93
+.\"
+.Dd May 31, 1993
+.Dt ECHO 1
+.Os
+.Sh NAME
+.Nm echo
+.Nd produce message in a shell script
+.Sh SYNOPSIS
+.Nm
+.Op Fl n | Fl e
+.Ar args ...
+.Sh DESCRIPTION
+.Nm
+prints its arguments on the standard output, separated by spaces.
+Unless the
+.Fl n
+option is present, a newline is output following the arguments.
+The
+.Fl e
+option causes
+.Nm
+to treat the escape sequences specially, as described in the following
+paragraph.
+The
+.Fl e
+option is the default, and is provided solely for compatibility with
+other systems.
+Only one of the options
+.Fl n
+and
+.Fl e
+may be given.
+.Pp
+If any of the following sequences of characters is encountered during
+output, the sequence is not output. Instead, the specified action is
+performed:
+.Bl -tag -width indent
+.It Li \eb
+A backspace character is output.
+.It Li \ec
+Subsequent output is suppressed. This is normally used at the end of the
+last argument to suppress the trailing newline that
+.Nm
+would otherwise output.
+.It Li \ef
+Output a form feed.
+.It Li \en
+Output a newline character.
+.It Li \er
+Output a carriage return.
+.It Li \et
+Output a (horizontal) tab character.
+.It Li \ev
+Output a vertical tab.
+.It Li \e0 Ns Ar digits
+Output the character whose value is given by zero to three digits.
+If there are zero digits, a nul character is output.
+.It Li \e\e
+Output a backslash.
+.El
+.Sh HINTS
+Remember that backslash is special to the shell and needs to be escaped.
+To output a message to standard error, say
+.Pp
+.D1 echo message \*[Gt]\*[Am]2
+.Sh BUGS
+The octal character escape mechanism
+.Pq Li \e0 Ns Ar digits
+differs from the
+C language mechanism.
+.Pp
+There is no way to force
+.Nm
+to treat its arguments literally, rather than interpreting them as
+options and escape sequences.
diff --git a/src/kash/bltin/echo.c b/src/kash/bltin/echo.c
new file mode 100644
index 0000000..ba778b1
--- /dev/null
+++ b/src/kash/bltin/echo.c
@@ -0,0 +1,120 @@
+/* $NetBSD: echo.c,v 1.12 2005/02/06 04:43:43 perry Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)echo.c 8.1 (Berkeley) 5/31/93
+ */
+
+/*
+ * Echo command.
+ *
+ * echo is steeped in tradition - several of them!
+ * netbsd has supported 'echo [-n | -e] args' in spite of -e not being
+ * documented anywhere.
+ * Posix requires that -n be supported, output from strings containing
+ * \ is implementation defined
+ * The Single Unix Spec requires that \ escapes be treated as if -e
+ * were set, but that -n not be treated as an option.
+ * (ksh supports 'echo [-eEn] args', but not -- so that it is actually
+ * impossible to actually output '-n')
+ *
+ * It is suggested that 'printf "%b" "string"' be used to get \ sequences
+ * expanded. printf is now a builtin of netbsd's sh and csh.
+ */
+
+#include "shinstance.h"
+#include "builtins.h"
+
+int
+echocmd(shinstance *psh, int argc, char **argv)
+{
+ char **ap;
+ const char *p;
+ int nflag = 0;
+ int eflag = 0;
+
+ ap = argv;
+ if (argc)
+ ap++;
+
+ if ((p = *ap) != NULL && *p == '-') {
+ if (p[1] == 'n' && !p[2]) {
+ nflag = 1;
+ ap++;
+ } else if (p[1] == 'e' && !p[2]) {
+ eflag = 1;
+ ap++;
+ }
+ }
+
+ while ((p = *ap++) != NULL) {
+ if (!eflag) {
+ out1str(psh, p);
+ } else {
+ char c;
+ int count;
+
+ while ((c = *p++) != '\0') {
+ if (c == '\\') {
+ switch (*p++) {
+ case 'a': c = '\a'; break; /* bell */
+ case 'b': c = '\b'; break;
+ case 'c': return 0; /* exit */
+ case 'e': c = 033; break; /* escape */
+ case 'f': c = '\f'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'v': c = '\v'; break;
+ case '\\': break; /* c = '\\' */
+ case '0':
+ c = 0;
+ count = 3;
+ while (--count >= 0 && (unsigned)(*p - '0') < 8)
+ c = (c << 3) + (*p++ - '0');
+ break;
+ default:
+ /* Output the '/' and char following */
+ p--;
+ break;
+ }
+ }
+ out1c(psh, c);
+ }
+ }
+ if (*ap)
+ out1c(psh, ' ');
+ }
+ if (! nflag)
+ out1c(psh, '\n');
+ return 0;
+}
diff --git a/src/kash/bltin/kill.c b/src/kash/bltin/kill.c
new file mode 100644
index 0000000..4b6e5d7
--- /dev/null
+++ b/src/kash/bltin/kill.c
@@ -0,0 +1,236 @@
+/* $NetBSD: kill.c,v 1.23 2003/08/07 09:05:13 agc Exp $ */
+
+/*
+ * Copyright (c) 1988, 1993, 1994
+ * 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.
+ */
+
+#if 0
+#if !defined(lint) && !defined(SHELL)
+__COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n");
+#endif /* not lint */
+#ifndef lint
+static char sccsid[] = "@(#)kill.c 8.4 (Berkeley) 4/28/95";
+#else
+__RCSID("$NetBSD: kill.c,v 1.23 2003/08/07 09:05:13 agc Exp $");
+#endif /* not lint */
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "shtypes.h"
+#include "jobs.h"
+#include "error.h"
+#include "shinstance.h"
+
+
+static int nosig(shinstance *, char *);
+static void printsignals(shinstance *, struct output *);
+static int signame_to_signum(char *);
+static int usage(shinstance *psh);
+
+int
+killcmd(shinstance *psh, int argc, char *argv[])
+{
+ int errors, numsig;
+ char *ep;
+
+ if (argc < 2)
+ return usage(psh);
+
+ numsig = SIGTERM;
+
+ argc--, argv++;
+ if (strcmp(*argv, "-l") == 0) {
+ argc--, argv++;
+ if (argc > 1)
+ return usage(psh);
+ if (argc == 1) {
+ if (isdigit((unsigned char)**argv) == 0)
+ return usage(psh);
+ numsig = strtol(*argv, &ep, 10);
+ if (*ep != '\0') {
+ sh_errx(psh, EXIT_FAILURE, "illegal signal number: %s",
+ *argv);
+ /* NOTREACHED */
+ }
+ if (numsig >= 128)
+ numsig -= 128;
+ if (numsig <= 0 || numsig >= NSIG)
+ return nosig(psh, *argv);
+ outfmt(psh->out1, "%s\n", sys_signame[numsig]);
+ //sh_exit(psh, 0);
+ return 0;
+ }
+ printsignals(psh, psh->out1);
+ //sh_exit(psh, 0);
+ return 0;
+ }
+
+ if (!strcmp(*argv, "-s")) {
+ argc--, argv++;
+ if (argc < 1) {
+ sh_warnx(psh, "option requires an argument -- s");
+ return usage(psh);
+ }
+ if (strcmp(*argv, "0")) {
+ if ((numsig = signame_to_signum(*argv)) < 0)
+ return nosig(psh, *argv);
+ } else
+ numsig = 0;
+ argc--, argv++;
+ } else if (**argv == '-') {
+ ++*argv;
+ if (isalpha((unsigned char)**argv)) {
+ if ((numsig = signame_to_signum(*argv)) < 0)
+ return nosig(psh, *argv);
+ } else if (isdigit((unsigned char)**argv)) {
+ numsig = strtol(*argv, &ep, 10);
+ if (!*argv || *ep) {
+ sh_errx(psh, EXIT_FAILURE, "illegal signal number: %s",
+ *argv);
+ /* NOTREACHED */
+ }
+ if (numsig < 0 || numsig >= NSIG)
+ return nosig(psh, *argv);
+ } else
+ return nosig(psh, *argv);
+ argc--, argv++;
+ }
+
+ if (argc == 0)
+ return usage(psh);
+
+ for (errors = 0; argc; argc--, argv++) {
+ const char * const strpid = argv[0];
+ shpid pid;
+ if (*strpid == '%') {
+ pid = getjobpgrp(psh, strpid);
+ if (pid == 0) {
+ sh_warnx(psh, "illegal job id: %s", strpid);
+ errors = 1;
+ continue;
+ }
+ } else {
+#if !defined(SH_FORKED_MODE) && defined(_MSC_VER)
+ pid = _strtoi64(strpid, &ep, 10);
+#elif !defined(SH_FORKED_MODE)
+ pid = strtoll(strpid, &ep, 10);
+#else
+ pid = strtol(strpid, &ep, 10);
+#endif
+ if (!*strpid || *ep) {
+ sh_warnx(psh, "illegal process id: %s", strpid);
+ errors = 1;
+ continue;
+ }
+ }
+ if (sh_kill(psh, pid, numsig) == -1) {
+ sh_warn(psh, "%s", strpid);
+ errors = 1;
+ }
+ /* Wakeup the process if it was suspended, so it can
+ exit without an explicit 'fg'. */
+ if (numsig == SIGTERM || numsig == SIGHUP)
+ sh_kill(psh, pid, SIGCONT);
+ }
+
+ //sh_exit(psh, errors);
+ ///* NOTREACHED */
+ return errors;
+}
+
+static int
+signame_to_signum(char *sig)
+{
+ int n;
+ if (strncasecmp(sig, "sig", 3) == 0)
+ sig += 3;
+ for (n = 1; n < NSIG; n++) {
+ if (!strcasecmp(sys_signame[n], sig))
+ return (n);
+ }
+ return (-1);
+}
+
+static int
+nosig(shinstance *psh, char *name)
+{
+ sh_warnx(psh, "unknown signal %s; valid signals:", name);
+ printsignals(psh, psh->out2);
+ //sh_exit(psh, 1);
+ ///* NOTREACHED */
+ return 1;
+}
+
+static void
+printsignals(shinstance *psh, struct output *out)
+{
+ int sig;
+ size_t len, nl;
+ const char *name;
+ unsigned termwidth = 80;
+
+ if (shfile_isatty(&psh->fdtab, out->fd)) {
+ sh_winsize win;
+ if (shfile_ioctl(&psh->fdtab, out->fd, TIOCGWINSZ, &win) == 0 && win.ws_col > 0)
+ termwidth = win.ws_col;
+ }
+
+ for (len = 0, sig = 1; sig < NSIG; sig++) {
+ name = sys_signame[sig];
+ nl = 1 + strlen(name);
+
+ if (len + nl >= termwidth) {
+ outfmt(out, "\n");
+ len = 0;
+ } else if (len != 0)
+ outfmt(out, " ");
+ len += nl;
+ outfmt(out, "%s", name);
+ }
+ if (len != 0)
+ outfmt(out, "\n");
+}
+
+static int
+usage(shinstance *psh)
+{
+ outfmt(psh->out2,
+ "usage: %s [-s signal_name] pid ...\n"
+ " %s -l [exit_status]\n"
+ " %s -signal_name pid ...\n"
+ " %s -signal_number pid ...\n",
+ psh->commandname, psh->commandname, psh->commandname, psh->commandname);
+ //sh_exit(psh, 1);
+ ///* NOTREACHED */
+ return 1;
+}
diff --git a/src/kash/bltin/printf.c b/src/kash/bltin/printf.c
new file mode 100644
index 0000000..4a6a952
--- /dev/null
+++ b/src/kash/bltin/printf.c
@@ -0,0 +1,659 @@
+/* $NetBSD: printf.c,v 1.31 2005/03/22 23:55:46 dsl Exp $ */
+
+/*
+ * Copyright (c) 1989, 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.
+ */
+
+#if 0
+#if !defined(BUILTIN) && !defined(SHELL)
+__COPYRIGHT("@(#) Copyright (c) 1989, 1993\n\
+ The Regents of the University of California. All rights reserved.\n");
+#endif
+#ifndef lint
+static char sccsid[] = "@(#)printf.c 8.2 (Berkeley) 3/22/95";
+#else
+__RCSID("$NetBSD: printf.c,v 1.31 2005/03/22 23:55:46 dsl Exp $");
+#endif /* not lint */
+#endif
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "shinstance.h"
+
+#ifdef __GNUC__
+#define ESCAPE '\e'
+#else
+#define ESCAPE 033
+#endif
+
+static void conv_escape_str(char *, void (*)(int));
+static char *conv_escape(char *, char *);
+static char *conv_expand(const char *);
+static int getchr(void);
+static double getdouble(void);
+static int getwidth(void);
+static intmax_t getintmax(void);
+static uintmax_t getuintmax(void);
+static char *getstr(void);
+static char *mklong(const char *, int);
+static void check_conversion(const char *, const char *);
+static void usage(void);
+
+static void b_count(int);
+static void b_output(int);
+static size_t b_length;
+static char *b_fmt;
+
+static int rval;
+static char **gargv;
+
+#ifdef BUILTIN /* csh builtin */
+#define main progprintf
+#endif
+
+#ifdef SHELL /* sh (aka ash) builtin */
+#define main printfcmd
+#include "../../bin/sh/bltin/bltin.h"
+#endif /* SHELL */
+
+#define PF(f, func) { \
+ if (fieldwidth != -1) { \
+ if (precision != -1) \
+ (void)printf(f, fieldwidth, precision, func); \
+ else \
+ (void)printf(f, fieldwidth, func); \
+ } else if (precision != -1) \
+ (void)printf(f, precision, func); \
+ else \
+ (void)printf(f, func); \
+}
+
+#define APF(cpp, f, func) { \
+ if (fieldwidth != -1) { \
+ if (precision != -1) \
+ (void)asprintf(cpp, f, fieldwidth, precision, func); \
+ else \
+ (void)asprintf(cpp, f, fieldwidth, func); \
+ } else if (precision != -1) \
+ (void)asprintf(cpp, f, precision, func); \
+ else \
+ (void)asprintf(cpp, f, func); \
+}
+
+int main(int, char **);
+int main(int argc, char *argv[])
+{
+ char *fmt, *start;
+ int fieldwidth, precision;
+ char nextch;
+ char *format;
+ int ch;
+
+#if !defined(SHELL) && !defined(BUILTIN)
+ (void)setlocale (LC_ALL, "");
+#endif
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch (ch) {
+ case '?':
+ default:
+ usage();
+ return 1;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc < 1) {
+ usage();
+ return 1;
+ }
+
+ format = *argv;
+ gargv = ++argv;
+
+#define SKIP1 "#-+ 0"
+#define SKIP2 "*0123456789"
+ do {
+ /*
+ * Basic algorithm is to scan the format string for conversion
+ * specifications -- once one is found, find out if the field
+ * width or precision is a '*'; if it is, gather up value.
+ * Note, format strings are reused as necessary to use up the
+ * provided arguments, arguments of zero/null string are
+ * provided to use up the format string.
+ */
+
+ /* find next format specification */
+ for (fmt = format; (ch = *fmt++) != '\0';) {
+ if (ch == '\\') {
+ char c_ch;
+ fmt = conv_escape(fmt, &c_ch);
+ putchar(c_ch);
+ continue;
+ }
+ if (ch != '%' || (*fmt == '%' && ++fmt)) {
+ (void)putchar(ch);
+ continue;
+ }
+
+ /* Ok - we've found a format specification,
+ Save its address for a later printf(). */
+ start = fmt - 1;
+
+ /* skip to field width */
+ fmt += strspn(fmt, SKIP1);
+ fieldwidth = *fmt == '*' ? getwidth() : -1;
+
+ /* skip to possible '.', get following precision */
+ fmt += strspn(fmt, SKIP2);
+ if (*fmt == '.')
+ ++fmt;
+ precision = *fmt == '*' ? getwidth() : -1;
+
+ fmt += strspn(fmt, SKIP2);
+
+ ch = *fmt;
+ if (!ch) {
+ warnx("missing format character");
+ return (1);
+ }
+ /* null terminate format string to we can use it
+ as an argument to printf. */
+ nextch = fmt[1];
+ fmt[1] = 0;
+ switch (ch) {
+
+ case 'B': {
+ const char *p = conv_expand(getstr());
+ *fmt = 's';
+ PF(start, p);
+ break;
+ }
+ case 'b': {
+ /* There has to be a better way to do this,
+ * but the string we generate might have
+ * embedded nulls. */
+ static char *a, *t;
+ char *cp = getstr();
+ /* Free on entry in case shell longjumped out */
+ if (a != NULL)
+ free(a);
+ a = NULL;
+ if (t != NULL)
+ free(t);
+ t = NULL;
+ /* Count number of bytes we want to output */
+ b_length = 0;
+ conv_escape_str(cp, b_count);
+ t = malloc(b_length + 1);
+ if (t == NULL)
+ break;
+ memset(t, 'x', b_length);
+ t[b_length] = 0;
+ /* Get printf to calculate the lengths */
+ *fmt = 's';
+ APF(&a, start, t);
+ b_fmt = a;
+ /* Output leading spaces and data bytes */
+ conv_escape_str(cp, b_output);
+ /* Add any trailing spaces */
+ printf("%s", b_fmt);
+ break;
+ }
+ case 'c': {
+ char p = getchr();
+ PF(start, p);
+ break;
+ }
+ case 's': {
+ char *p = getstr();
+ PF(start, p);
+ break;
+ }
+ case 'd':
+ case 'i': {
+ intmax_t p = getintmax();
+ char *f = mklong(start, ch);
+ PF(f, p);
+ break;
+ }
+ case 'o':
+ case 'u':
+ case 'x':
+ case 'X': {
+ uintmax_t p = getuintmax();
+ char *f = mklong(start, ch);
+ PF(f, p);
+ break;
+ }
+ case 'e':
+ case 'E':
+ case 'f':
+ case 'g':
+ case 'G': {
+ double p = getdouble();
+ PF(start, p);
+ break;
+ }
+ default:
+ warnx("%s: invalid directive", start);
+ return 1;
+ }
+ *fmt++ = ch;
+ *fmt = nextch;
+ /* escape if a \c was encountered */
+ if (rval & 0x100)
+ return rval & ~0x100;
+ }
+ } while (gargv != argv && *gargv);
+
+ return rval;
+}
+
+/* helper functions for conv_escape_str */
+
+static void
+/*ARGSUSED*/
+b_count(int ch)
+{
+ b_length++;
+}
+
+/* Output one converted character for every 'x' in the 'format' */
+
+static void
+b_output(int ch)
+{
+ for (;;) {
+ switch (*b_fmt++) {
+ case 0:
+ b_fmt--;
+ return;
+ case ' ':
+ putchar(' ');
+ break;
+ default:
+ putchar(ch);
+ return;
+ }
+ }
+}
+
+
+/*
+ * Print SysV echo(1) style escape string
+ * Halts processing string if a \c escape is encountered.
+ */
+static void
+conv_escape_str(char *str, void (*do_putchar)(int))
+{
+ int value;
+ int ch;
+ char c;
+
+ while ((ch = *str++) != '\0') {
+ if (ch != '\\') {
+ do_putchar(ch);
+ continue;
+ }
+
+ ch = *str++;
+ if (ch == 'c') {
+ /* \c as in SYSV echo - abort all processing.... */
+ rval |= 0x100;
+ break;
+ }
+
+ /*
+ * %b string octal constants are not like those in C.
+ * They start with a \0, and are followed by 0, 1, 2,
+ * or 3 octal digits.
+ */
+ if (ch == '0') {
+ int octnum = 0, i;
+ for (i = 0; i < 3; i++) {
+ if (!isdigit((unsigned char)*str) || *str > '7')
+ break;
+ octnum = (octnum << 3) | (*str++ - '0');
+ }
+ do_putchar(octnum);
+ continue;
+ }
+
+ /* \[M][^|-]C as defined by vis(3) */
+ if (ch == 'M' && *str == '-') {
+ do_putchar(0200 | str[1]);
+ str += 2;
+ continue;
+ }
+ if (ch == 'M' && *str == '^') {
+ str++;
+ value = 0200;
+ ch = '^';
+ } else
+ value = 0;
+ if (ch == '^') {
+ ch = *str++;
+ if (ch == '?')
+ value |= 0177;
+ else
+ value |= ch & 037;
+ do_putchar(value);
+ continue;
+ }
+
+ /* Finally test for sequences valid in the format string */
+ str = conv_escape(str - 1, &c);
+ do_putchar(c);
+ }
+}
+
+/*
+ * Print "standard" escape characters
+ */
+static char *
+conv_escape(char *str, char *conv_ch)
+{
+ int value;
+ int ch;
+ char num_buf[4], *num_end;
+
+ ch = *str++;
+
+ switch (ch) {
+ case '0': case '1': case '2': case '3':
+ case '4': case '5': case '6': case '7':
+ num_buf[0] = ch;
+ ch = str[0];
+ num_buf[1] = ch;
+ num_buf[2] = ch ? str[1] : 0;
+ num_buf[3] = 0;
+ value = strtoul(num_buf, &num_end, 8);
+ str += num_end - (num_buf + 1);
+ break;
+
+ case 'x':
+ /* Hexadecimal character constants are not required to be
+ supported (by SuS v1) because there is no consistent
+ way to detect the end of the constant.
+ Supporting 2 byte constants is a compromise. */
+ ch = str[0];
+ num_buf[0] = ch;
+ num_buf[1] = ch ? str[1] : 0;
+ num_buf[2] = 0;
+ value = strtoul(num_buf, &num_end, 16);
+ str += num_end - num_buf;
+ break;
+
+ case '\\': value = '\\'; break; /* backslash */
+ case '\'': value = '\''; break; /* single quote */
+ case '"': value = '"'; break; /* double quote */
+ case 'a': value = '\a'; break; /* alert */
+ case 'b': value = '\b'; break; /* backspace */
+ case 'e': value = ESCAPE; break; /* escape */
+ case 'f': value = '\f'; break; /* form-feed */
+ case 'n': value = '\n'; break; /* newline */
+ case 'r': value = '\r'; break; /* carriage-return */
+ case 't': value = '\t'; break; /* tab */
+ case 'v': value = '\v'; break; /* vertical-tab */
+
+ default:
+ warnx("unknown escape sequence `\\%c'", ch);
+ rval = 1;
+ value = ch;
+ break;
+ }
+
+ *conv_ch = value;
+ return str;
+}
+
+/* expand a string so that everything is printable */
+
+static char *
+conv_expand(const char *str)
+{
+ static char *conv_str;
+ static char no_memory[] = "<no memory>";
+ char *cp;
+ int ch;
+
+ if (conv_str)
+ free(conv_str);
+ /* get a buffer that is definitely large enough.... */
+ conv_str = malloc(4 * strlen(str) + 1);
+ if (!conv_str)
+ return no_memory;
+ cp = conv_str;
+
+ while ((ch = *(const unsigned char *)str++) != '\0') {
+ switch (ch) {
+ /* Use C escapes for expected control characters */
+ case '\\': ch = '\\'; break; /* backslash */
+ case '\'': ch = '\''; break; /* single quote */
+ case '"': ch = '"'; break; /* double quote */
+ case '\a': ch = 'a'; break; /* alert */
+ case '\b': ch = 'b'; break; /* backspace */
+ case ESCAPE: ch = 'e'; break; /* escape */
+ case '\f': ch = 'f'; break; /* form-feed */
+ case '\n': ch = 'n'; break; /* newline */
+ case '\r': ch = 'r'; break; /* carriage-return */
+ case '\t': ch = 't'; break; /* tab */
+ case '\v': ch = 'v'; break; /* vertical-tab */
+ default:
+ /* Copy anything printable */
+ if (isprint(ch)) {
+ *cp++ = ch;
+ continue;
+ }
+ /* Use vis(3) encodings for the rest */
+ *cp++ = '\\';
+ if (ch & 0200) {
+ *cp++ = 'M';
+ ch &= ~0200;
+ }
+ if (ch == 0177) {
+ *cp++ = '^';
+ *cp++ = '?';
+ continue;
+ }
+ if (ch < 040) {
+ *cp++ = '^';
+ *cp++ = ch | 0100;
+ continue;
+ }
+ *cp++ = '-';
+ *cp++ = ch;
+ continue;
+ }
+ *cp++ = '\\';
+ *cp++ = ch;
+ }
+
+ *cp = 0;
+ return conv_str;
+}
+
+static char *
+mklong(const char *str, int ch)
+{
+ static char copy[64];
+ size_t len;
+
+ len = strlen(str) + 2;
+ if (len > sizeof copy) {
+ warnx("format %s too complex\n", str);
+ len = 4;
+ }
+ (void)memmove(copy, str, len - 3);
+ copy[len - 3] = 'j';
+ copy[len - 2] = ch;
+ copy[len - 1] = '\0';
+ return copy;
+}
+
+static int
+getchr(void)
+{
+ if (!*gargv)
+ return 0;
+ return (int)**gargv++;
+}
+
+static char *
+getstr(void)
+{
+ static char empty[] = "";
+ if (!*gargv)
+ return empty;
+ return *gargv++;
+}
+
+static int
+getwidth(void)
+{
+ long val;
+ char *s, *ep;
+
+ s = *gargv;
+ if (!*gargv)
+ return (0);
+ gargv++;
+
+ errno = 0;
+ val = strtoul(s, &ep, 0);
+ check_conversion(s, ep);
+
+ /* Arbitrarily 'restrict' field widths to 1Mbyte */
+ if (val < 0 || val > 1 << 20) {
+ warnx("%s: invalid field width", s);
+ return 0;
+ }
+
+ return val;
+}
+
+static intmax_t
+getintmax(void)
+{
+ intmax_t val;
+ char *cp, *ep;
+
+ cp = *gargv;
+ if (cp == NULL)
+ return 0;
+ gargv++;
+
+ if (*cp == '\"' || *cp == '\'')
+ return *(cp+1);
+
+ errno = 0;
+ val = strtoimax(cp, &ep, 0);
+ check_conversion(cp, ep);
+ return val;
+}
+
+static uintmax_t
+getuintmax(void)
+{
+ uintmax_t val;
+ char *cp, *ep;
+
+ cp = *gargv;
+ if (cp == NULL)
+ return 0;
+ gargv++;
+
+ if (*cp == '\"' || *cp == '\'')
+ return *(cp + 1);
+
+ /* strtoumax won't error -ve values */
+ while (isspace(*(unsigned char *)cp))
+ cp++;
+ if (*cp == '-') {
+ warnx("%s: expected positive numeric value", cp);
+ rval = 1;
+ return 0;
+ }
+
+ errno = 0;
+ val = strtoumax(cp, &ep, 0);
+ check_conversion(cp, ep);
+ return val;
+}
+
+static double
+getdouble(void)
+{
+ double val;
+ char *ep;
+
+ if (!*gargv)
+ return (0.0);
+
+ if (**gargv == '\"' || **gargv == '\'')
+ return (double) *((*gargv++)+1);
+
+ errno = 0;
+ val = strtod(*gargv, &ep);
+ check_conversion(*gargv++, ep);
+ return val;
+}
+
+static void
+check_conversion(const char *s, const char *ep)
+{
+ if (*ep) {
+ if (ep == s)
+ warnx("%s: expected numeric value", s);
+ else
+ warnx("%s: not completely converted", s);
+ rval = 1;
+ } else if (errno == ERANGE) {
+ warnx("%s: %s", s, sh_strerror(psh, ERANGE));
+ rval = 1;
+ }
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "Usage: %s format [arg ...]\n", getprogname());
+}
diff --git a/src/kash/bltin/test.c b/src/kash/bltin/test.c
new file mode 100644
index 0000000..303a0f3
--- /dev/null
+++ b/src/kash/bltin/test.c
@@ -0,0 +1,483 @@
+/* $NetBSD: test.c,v 1.26 2005/02/10 06:56:55 simonb Exp $ */
+
+/*
+ * test(1); version 7-like -- author Erik Baalbergen
+ * modified by Eric Gisin to be used as built-in.
+ * modified by Arnold Robbins to add SVR3 compatibility
+ * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
+ * modified by J.T. Conklin for NetBSD.
+ *
+ * This program is in the Public Domain.
+ */
+
+#if 0
+#ifndef lint
+__RCSID("$NetBSD: test.c,v 1.26 2005/02/10 06:56:55 simonb Exp $");
+#endif
+#endif
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "shell.h"
+#include "error.h"
+#include "shinstance.h"
+
+
+/* test(1) accepts the following grammar:
+ oexpr ::= aexpr | aexpr "-o" oexpr ;
+ aexpr ::= nexpr | nexpr "-a" aexpr ;
+ nexpr ::= primary | "!" primary
+ primary ::= unary-operator operand
+ | operand binary-operator operand
+ | operand
+ | "(" oexpr ")"
+ ;
+ unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
+ "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
+
+ binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
+ "-nt"|"-ot"|"-ef";
+ operand ::= <any legal UNIX file name>
+*/
+
+enum token {
+ EOI,
+ FILRD,
+ FILWR,
+ FILEX,
+ FILEXIST,
+ FILREG,
+ FILDIR,
+ FILCDEV,
+ FILBDEV,
+ FILFIFO,
+ FILSOCK,
+ FILSYM,
+ FILGZ,
+ FILTT,
+ FILSUID,
+ FILSGID,
+ FILSTCK,
+ FILNT,
+ FILOT,
+ FILEQ,
+ FILUID,
+ FILGID,
+ STREZ,
+ STRNZ,
+ STREQ,
+ STRNE,
+ STRLT,
+ STRGT,
+ INTEQ,
+ INTNE,
+ INTGE,
+ INTGT,
+ INTLE,
+ INTLT,
+ UNOT,
+ BAND,
+ BOR,
+ LPAREN,
+ RPAREN,
+ OPERAND
+};
+
+enum token_types {
+ UNOP,
+ BINOP,
+ BUNOP,
+ BBINOP,
+ PAREN
+};
+
+static struct t_op {
+ const char *op_text;
+ short op_num, op_type;
+} const ops [] = {
+ {"-r", FILRD, UNOP},
+ {"-w", FILWR, UNOP},
+ {"-x", FILEX, UNOP},
+ {"-e", FILEXIST,UNOP},
+ {"-f", FILREG, UNOP},
+ {"-d", FILDIR, UNOP},
+ {"-c", FILCDEV,UNOP},
+ {"-b", FILBDEV,UNOP},
+ {"-p", FILFIFO,UNOP},
+ {"-u", FILSUID,UNOP},
+ {"-g", FILSGID,UNOP},
+ {"-k", FILSTCK,UNOP},
+ {"-s", FILGZ, UNOP},
+ {"-t", FILTT, UNOP},
+ {"-z", STREZ, UNOP},
+ {"-n", STRNZ, UNOP},
+ {"-h", FILSYM, UNOP}, /* for backwards compat */
+ {"-O", FILUID, UNOP},
+ {"-G", FILGID, UNOP},
+ {"-L", FILSYM, UNOP},
+ {"-S", FILSOCK,UNOP},
+ {"=", STREQ, BINOP},
+ {"!=", STRNE, BINOP},
+ {"<", STRLT, BINOP},
+ {">", STRGT, BINOP},
+ {"-eq", INTEQ, BINOP},
+ {"-ne", INTNE, BINOP},
+ {"-ge", INTGE, BINOP},
+ {"-gt", INTGT, BINOP},
+ {"-le", INTLE, BINOP},
+ {"-lt", INTLT, BINOP},
+ {"-nt", FILNT, BINOP},
+ {"-ot", FILOT, BINOP},
+ {"-ef", FILEQ, BINOP},
+ {"!", UNOT, BUNOP},
+ {"-a", BAND, BBINOP},
+ {"-o", BOR, BBINOP},
+ {"(", LPAREN, PAREN},
+ {")", RPAREN, PAREN},
+ {0, 0, 0}
+};
+
+//static char **t_wp;
+//static struct t_op const *t_wp_op;
+
+static void syntax(shinstance *, const char *, const char *);
+static int oexpr(shinstance *, enum token);
+static int aexpr(shinstance *, enum token);
+static int nexpr(shinstance *, enum token);
+static int primary(shinstance *, enum token);
+static int binop(shinstance *);
+static int filstat(shinstance *, char *, enum token);
+static enum token t_lex(shinstance *, char *);
+static int isoperand(shinstance *);
+static int getn(shinstance *, const char *);
+static int newerf(shinstance *, const char *, const char *);
+static int olderf(shinstance *, const char *, const char *);
+static int equalf(shinstance *, const char *, const char *);
+
+
+int
+testcmd(shinstance *psh, int argc, char **argv)
+{
+ int res;
+
+ if (strcmp(argv[0], "[") == 0) {
+ if (strcmp(argv[--argc], "]"))
+ error(psh, "missing ]");
+ argv[argc] = NULL;
+ }
+
+ if (argc < 2)
+ return 1;
+
+ psh->t_wp_op = NULL;
+ psh->t_wp = &argv[1];
+ res = !oexpr(psh, t_lex(psh, *psh->t_wp));
+
+ if (*psh->t_wp != NULL && *++psh->t_wp != NULL)
+ syntax(psh, *psh->t_wp, "unexpected operator");
+
+ return res;
+}
+
+static void
+syntax(shinstance *psh, const char *op, const char *msg)
+{
+
+ if (op && *op)
+ error(psh, "%s: %s", op, msg);
+ else
+ error(psh, "%s", msg);
+}
+
+static int
+oexpr(shinstance *psh, enum token n)
+{
+ int res;
+
+ res = aexpr(psh, n);
+ if (t_lex(psh, *++psh->t_wp) == BOR)
+ return oexpr(psh, t_lex(psh, *++psh->t_wp)) || res;
+ psh->t_wp--;
+ return res;
+}
+
+static int
+aexpr(shinstance *psh, enum token n)
+{
+ int res;
+
+ res = nexpr(psh, n);
+ if (t_lex(psh, *++psh->t_wp) == BAND)
+ return aexpr(psh, t_lex(psh, *++psh->t_wp)) && res;
+ psh->t_wp--;
+ return res;
+}
+
+static int
+nexpr(shinstance *psh, enum token n)
+{
+
+ if (n == UNOT)
+ return !nexpr(psh, t_lex(psh, *++psh->t_wp));
+ return primary(psh, n);
+}
+
+static int
+primary(shinstance *psh, enum token n)
+{
+ enum token nn;
+ int res;
+
+ if (n == EOI)
+ return 0; /* missing expression */
+ if (n == LPAREN) {
+ if ((nn = t_lex(psh, *++psh->t_wp)) == RPAREN)
+ return 0; /* missing expression */
+ res = oexpr(psh, nn);
+ if (t_lex(psh, *++psh->t_wp) != RPAREN)
+ syntax(psh, NULL, "closing paren expected");
+ return res;
+ }
+ if (psh->t_wp_op && psh->t_wp_op->op_type == UNOP) {
+ /* unary expression */
+ if (*++psh->t_wp == NULL)
+ syntax(psh, psh->t_wp_op->op_text, "argument expected");
+ switch (n) {
+ case STREZ:
+ return strlen(*psh->t_wp) == 0;
+ case STRNZ:
+ return strlen(*psh->t_wp) != 0;
+ case FILTT:
+ return shfile_isatty(&psh->fdtab, getn(psh, *psh->t_wp));
+ default:
+ return filstat(psh, *psh->t_wp, n);
+ }
+ }
+
+ if (t_lex(psh, psh->t_wp[1]), psh->t_wp_op && psh->t_wp_op->op_type == BINOP) {
+ return binop(psh);
+ }
+
+ return strlen(*psh->t_wp) > 0;
+}
+
+static int
+binop(shinstance *psh)
+{
+ const char *opnd1, *opnd2;
+ struct t_op const *op;
+
+ opnd1 = *psh->t_wp;
+ (void) t_lex(psh, *++psh->t_wp);
+ op = psh->t_wp_op;
+
+ if ((opnd2 = *++psh->t_wp) == NULL)
+ syntax(psh, op->op_text, "argument expected");
+
+ switch (op->op_num) {
+ case STREQ:
+ return strcmp(opnd1, opnd2) == 0;
+ case STRNE:
+ return strcmp(opnd1, opnd2) != 0;
+ case STRLT:
+ return strcmp(opnd1, opnd2) < 0;
+ case STRGT:
+ return strcmp(opnd1, opnd2) > 0;
+ case INTEQ:
+ return getn(psh, opnd1) == getn(psh, opnd2);
+ case INTNE:
+ return getn(psh, opnd1) != getn(psh, opnd2);
+ case INTGE:
+ return getn(psh, opnd1) >= getn(psh, opnd2);
+ case INTGT:
+ return getn(psh, opnd1) > getn(psh, opnd2);
+ case INTLE:
+ return getn(psh, opnd1) <= getn(psh, opnd2);
+ case INTLT:
+ return getn(psh, opnd1) < getn(psh, opnd2);
+ case FILNT:
+ return newerf(psh, opnd1, opnd2);
+ case FILOT:
+ return olderf(psh, opnd1, opnd2);
+ case FILEQ:
+ return equalf(psh, opnd1, opnd2);
+ default:
+ sh_abort(psh);
+ /* NOTREACHED */
+ return -1;
+ }
+}
+
+static int
+filstat(shinstance *psh, char *nm, enum token mode)
+{
+ struct stat s;
+
+ if (mode == FILSYM
+ ? shfile_lstat(&psh->fdtab, nm, &s)
+ : shfile_stat(&psh->fdtab, nm, &s))
+ return 0;
+
+ switch (mode) {
+ case FILRD:
+ return shfile_access(&psh->fdtab, nm, R_OK) == 0;
+ case FILWR:
+ return shfile_access(&psh->fdtab, nm, W_OK) == 0;
+ case FILEX:
+ return shfile_access(&psh->fdtab, nm, X_OK) == 0;
+ case FILEXIST:
+ return shfile_access(&psh->fdtab, nm, F_OK) == 0;
+ case FILREG:
+ return S_ISREG(s.st_mode);
+ case FILDIR:
+ return S_ISDIR(s.st_mode);
+ case FILCDEV:
+#ifdef S_ISCHR
+ return S_ISCHR(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILBDEV:
+#ifdef S_ISBLK
+ return S_ISBLK(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILFIFO:
+#ifdef S_ISFIFO
+ return S_ISFIFO(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILSOCK:
+#ifdef S_ISSOCK
+ return S_ISSOCK(s.st_mode);
+#else
+ return 0;
+#endif
+ case FILSYM:
+ return S_ISLNK(s.st_mode);
+ case FILSUID:
+ return (s.st_mode & S_ISUID) != 0;
+ case FILSGID:
+ return (s.st_mode & S_ISGID) != 0;
+ case FILSTCK:
+#ifdef S_ISVTX
+ return (s.st_mode & S_ISVTX) != 0;
+#else
+ return 0;
+#endif
+ case FILGZ:
+ return s.st_size > (off_t)0;
+ case FILUID:
+ return s.st_uid == sh_geteuid(psh);
+ case FILGID:
+ return s.st_gid == sh_getegid(psh);
+ default:
+ return 1;
+ }
+}
+
+static enum token
+t_lex(shinstance *psh, char *s)
+{
+ struct t_op const *op;
+
+ op = ops;
+
+ if (s == 0) {
+ psh->t_wp_op = NULL;
+ return EOI;
+ }
+ while (op->op_text) {
+ if (strcmp(s, op->op_text) == 0) {
+ if ((op->op_type == UNOP && isoperand(psh)) ||
+ (op->op_num == LPAREN && *(psh->t_wp+1) == 0))
+ break;
+ psh->t_wp_op = op;
+ return op->op_num;
+ }
+ op++;
+ }
+ psh->t_wp_op = NULL;
+ return OPERAND;
+}
+
+static int
+isoperand(shinstance *psh)
+{
+ struct t_op const *op;
+ char *s, *t;
+
+ op = ops;
+ if ((s = *(psh->t_wp+1)) == 0)
+ return 1;
+ if ((t = *(psh->t_wp+2)) == 0)
+ return 0;
+ while (op->op_text) {
+ if (strcmp(s, op->op_text) == 0)
+ return op->op_type == BINOP &&
+ (t[0] != ')' || t[1] != '\0');
+ op++;
+ }
+ return 0;
+}
+
+/* atoi with error detection */
+static int
+getn(shinstance *psh, const char *s)
+{
+ char *p;
+ long r;
+
+ errno = 0;
+ r = strtol(s, &p, 10);
+
+ if (errno != 0)
+ error(psh, "%s: out of range", s);
+
+ while (isspace((unsigned char)*p))
+ p++;
+
+ if (*p)
+ error(psh, "%s: bad number", s);
+
+ return (int) r;
+}
+
+static int
+newerf(shinstance *psh, const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (shfile_stat(&psh->fdtab, f1, &b1) == 0 &&
+ shfile_stat(&psh->fdtab, f2, &b2) == 0 &&
+ b1.st_mtime > b2.st_mtime);
+}
+
+static int
+olderf(shinstance *psh, const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (shfile_stat(&psh->fdtab, f1, &b1) == 0 &&
+ shfile_stat(&psh->fdtab, f2, &b2) == 0 &&
+ b1.st_mtime < b2.st_mtime);
+}
+
+static int
+equalf(shinstance *psh, const char *f1, const char *f2)
+{
+ struct stat b1, b2;
+
+ return (shfile_stat(&psh->fdtab, f1, &b1) == 0 &&
+ shfile_stat(&psh->fdtab, f2, &b2) == 0 &&
+ b1.st_dev == b2.st_dev &&
+ b1.st_ino == b2.st_ino);
+}
diff --git a/src/kash/builtins.def b/src/kash/builtins.def
new file mode 100644
index 0000000..bd38c33
--- /dev/null
+++ b/src/kash/builtins.def
@@ -0,0 +1,92 @@
+#!/bin/sh -
+# $NetBSD: builtins.def,v 1.21 2004/07/13 15:05:59 seb Exp $
+#
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# 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.
+#
+# @(#)builtins.def 8.4 (Berkeley) 5/4/95
+
+#
+# This file lists all the builtin commands. The first column is the name
+# of a C routine.
+# The -j flag specifies that this command is to be excluded from systems
+# without job control.
+# The -h flag specifies that this command is to be excluded from systems
+# based on the SMALL compile-time symbol.
+# The -s flag specifies that this is a posix 'special builtin' command.
+# The -u flag specifies that this is a posix 'standard utility'.
+# The rest of the line specifies the command name or names used to run
+# the command.
+
+bltincmd -u command
+bgcmd -j -u bg
+breakcmd -s break -s continue
+cdcmd -u cd chdir
+dotcmd -s .
+echocmd echo
+evalcmd -s eval
+execcmd -s exec
+exitcmd -s exit
+expcmd exp let
+exportcmd -s export -s readonly
+falsecmd -u false
+histcmd -h -u fc
+inputrc inputrc
+fgcmd -j -u fg
+getoptscmd -u getopts
+hashcmd hash
+jobidcmd jobid
+jobscmd -u jobs
+localcmd local
+#ifndef SMALL
+printfcmd printf
+#endif
+pwdcmd -u pwd
+readcmd -u read
+returncmd -s return
+setcmd -s set
+setvarcmd setvar
+shiftcmd -s shift
+timescmd -s times
+trapcmd -s trap
+truecmd -s : -u true
+typecmd type
+umaskcmd -u umask
+unaliascmd -u unalias
+unsetcmd -s unset
+waitcmd -u wait
+aliascmd -u alias
+ulimitcmd ulimit
+testcmd test [
+killcmd -u kill # mandated by posix for 'kill %job'
+wordexpcmd wordexp
+#newgrp -u newgrp # optional command in posix
+
+#exprcmd expr
diff --git a/src/kash/cd.c b/src/kash/cd.c
new file mode 100644
index 0000000..49fd157
--- /dev/null
+++ b/src/kash/cd.c
@@ -0,0 +1,445 @@
+/* $NetBSD: cd.c,v 1.34 2003/11/14 20:00:28 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)cd.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: cd.c,v 1.34 2003/11/14 20:00:28 dsl Exp $");
+#endif /* not lint */
+#endif
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+/*
+ * The cd and pwd commands.
+ */
+
+#include "shell.h"
+#include "var.h"
+#include "nodes.h" /* for jobs.h */
+#include "jobs.h"
+#include "options.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "exec.h"
+#include "redir.h"
+#include "mystring.h"
+#include "show.h"
+#include "cd.h"
+#include "shinstance.h"
+
+STATIC int docd(shinstance *psh, char *, int);
+STATIC char *getcomponent(shinstance *psh);
+STATIC void updatepwd(shinstance *psh, char *);
+STATIC void find_curdir(shinstance *psh, int noerror);
+
+/*char *curdir = NULL;*/ /* current working directory */
+/*char *prevdir;*/ /* previous working directory */
+/*STATIC char *cdcomppath;*/
+
+int
+cdcmd(shinstance *psh, int argc, char **argv)
+{
+ const char *dest;
+ const char *path;
+ char *p, *d;
+ struct stat statb;
+ int print = cdprint(psh); /* set -cdprint to enable */
+
+ nextopt(psh, nullstr);
+
+ /*
+ * Try (quite hard) to have 'curdir' defined, nothing has set
+ * it on entry to the shell, but we want 'cd fred; cd -' to work.
+ */
+ getpwd(psh, 1);
+ dest = *psh->argptr;
+ if (dest == NULL) {
+ dest = bltinlookup(psh, "HOME", 1);
+ if (dest == NULL)
+ error(psh, "HOME not set");
+ } else {
+ if (psh->argptr[1]) {
+ /* Do 'ksh' style substitution */
+ if (!psh->curdir)
+ error(psh, "PWD not set");
+ p = strstr(psh->curdir, dest);
+ if (!p)
+ error(psh, "bad substitution");
+ d = stalloc(psh, strlen(psh->curdir) + strlen(psh->argptr[1]) + 1);
+ memcpy(d, psh->curdir, p - psh->curdir);
+ strcpy(d + (p - psh->curdir), psh->argptr[1]);
+ strcat(d, p + strlen(dest));
+ dest = d;
+ print = 1;
+ }
+ }
+
+ if (dest[0] == '-' && dest[1] == '\0') {
+ dest = psh->prevdir ? psh->prevdir : psh->curdir;
+ print = 1;
+ }
+ if (*dest == '\0')
+ dest = ".";
+ if (IS_ROOT(dest) || (path = bltinlookup(psh, "CDPATH", 1)) == NULL)
+ path = nullstr;
+ while ((p = padvance(psh, &path, dest)) != NULL) {
+ if (shfile_stat(&psh->fdtab, p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
+ if (!print) {
+ /*
+ * XXX - rethink
+ */
+ if (p[0] == '.' && p[1] == '/' && p[2] != '\0')
+ p += 2;
+ print = strcmp(p, dest);
+ }
+ if (docd(psh, p, print) >= 0)
+ return 0;
+
+ }
+ }
+ error(psh, "can't cd to %s", dest);
+ /* NOTREACHED */
+ return 1;
+}
+
+
+/*
+ * Actually do the chdir. In an interactive shell, print the
+ * directory name if "print" is nonzero.
+ */
+
+STATIC int
+docd(shinstance *psh, char *dest, int print)
+{
+ char *p;
+ char *q;
+ char *component;
+ struct stat statb;
+ int first;
+ int badstat;
+
+ TRACE((psh, "docd(\"%s\", %d) called\n", dest, print));
+
+ /*
+ * Check each component of the path. If we find a symlink or
+ * something we can't stat, clear curdir to force a getcwd()
+ * next time we get the value of the current directory.
+ */
+ badstat = 0;
+ psh->cdcomppath = stalloc(psh, strlen(dest) + 1);
+ scopy(dest, psh->cdcomppath);
+ STARTSTACKSTR(psh, p);
+ if (IS_ROOT(dest)) {
+ STPUTC(psh, '/', p);
+ psh->cdcomppath++;
+ }
+ first = 1;
+ while ((q = getcomponent(psh)) != NULL) {
+ if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0'))
+ continue;
+ if (! first)
+ STPUTC(psh, '/', p);
+ first = 0;
+ component = q;
+ while (*q)
+ STPUTC(psh, *q++, p);
+ if (equal(component, ".."))
+ continue;
+ STACKSTRNUL(psh, p);
+ if ((shfile_lstat(&psh->fdtab, stackblock(psh), &statb) < 0)
+ || (S_ISLNK(statb.st_mode))) {
+ /* print = 1; */
+ badstat = 1;
+ break;
+ }
+ }
+
+ INTOFF;
+ if (shfile_chdir(&psh->fdtab, dest) < 0) {
+ INTON;
+ return -1;
+ }
+ updatepwd(psh, badstat ? NULL : dest);
+ INTON;
+ if (print && iflag(psh) && psh->curdir)
+ out1fmt(psh, "%s\n", psh->curdir);
+ return 0;
+}
+
+
+/*
+ * Get the next component of the path name pointed to by psh->cdcomppath.
+ * This routine overwrites the string pointed to by psh->cdcomppath.
+ */
+
+STATIC char *
+getcomponent(shinstance *psh)
+{
+ char *p;
+ char *start;
+
+ if ((p = psh->cdcomppath) == NULL)
+ return NULL;
+ start = psh->cdcomppath;
+ while (*p != '/' && *p != '\0')
+ p++;
+ if (*p == '\0') {
+ psh->cdcomppath = NULL;
+ } else {
+ *p++ = '\0';
+ psh->cdcomppath = p;
+ }
+ return start;
+}
+
+
+
+/*
+ * Update curdir (the name of the current directory) in response to a
+ * cd command. We also call hashcd to let the routines in exec.c know
+ * that the current directory has changed.
+ */
+
+STATIC void
+updatepwd(shinstance *psh, char *dir)
+{
+ char *new;
+ char *p;
+
+ hashcd(psh); /* update command hash table */
+
+ /*
+ * If our argument is NULL, we don't know the current directory
+ * any more because we traversed a symbolic link or something
+ * we couldn't stat().
+ */
+ if (dir == NULL || psh->curdir == NULL) {
+ if (psh->prevdir)
+ ckfree(psh, psh->prevdir);
+ INTOFF;
+ psh->prevdir = psh->curdir;
+ psh->curdir = NULL;
+ getpwd(psh, 1);
+ INTON;
+ if (psh->curdir)
+ setvar(psh, "PWD", psh->curdir, VEXPORT);
+ else
+ unsetvar(psh, "PWD", 0);
+ return;
+ }
+ psh->cdcomppath = stalloc(psh, strlen(dir) + 1);
+ scopy(dir, psh->cdcomppath);
+ STARTSTACKSTR(psh, new);
+ if (!IS_ROOT(dir)) {
+ p = psh->curdir;
+ while (*p)
+ STPUTC(psh, *p++, new);
+ if (p[-1] == '/')
+ STUNPUTC(psh, new);
+ }
+ while ((p = getcomponent(psh)) != NULL) {
+ if (equal(p, "..")) {
+ while (new > stackblock(psh) && (STUNPUTC(psh, new), *new) != '/');
+ } else if (*p != '\0' && ! equal(p, ".")) {
+ STPUTC(psh, '/', new);
+ while (*p)
+ STPUTC(psh, *p++, new);
+ }
+ }
+ if (new == stackblock(psh))
+ STPUTC(psh, '/', new);
+ STACKSTRNUL(psh, new);
+ INTOFF;
+ if (psh->prevdir)
+ ckfree(psh, psh->prevdir);
+ psh->prevdir = psh->curdir;
+ psh->curdir = savestr(psh, stackblock(psh));
+ setvar(psh, "PWD", psh->curdir, VEXPORT);
+ INTON;
+}
+
+/*
+ * Posix says the default should be 'pwd -L' (as below), however
+ * the 'cd' command (above) does something much nearer to the
+ * posix 'cd -P' (not the posix default of 'cd -L').
+ * If 'cd' is changed to support -P/L then the default here
+ * needs to be revisited if the historic behaviour is to be kept.
+ */
+
+int
+pwdcmd(shinstance *psh, int argc, char **argv)
+{
+ int i;
+ char opt = 'L';
+
+ while ((i = nextopt(psh, "LP")) != '\0')
+ opt = i;
+ if (*psh->argptr)
+ error(psh, "unexpected argument");
+
+ if (opt == 'L')
+ getpwd(psh, 0);
+ else
+ find_curdir(psh, 0);
+
+ setvar(psh, "PWD", psh->curdir, VEXPORT);
+ out1str(psh, psh->curdir);
+ out1c(psh, '\n');
+ return 0;
+}
+
+
+
+
+#define MAXPWD 256
+
+/*
+ * Find out what the current directory is. If we already know the current
+ * directory, this routine returns immediately.
+ */
+const char *
+getpwd(shinstance *psh, int noerror)
+{
+ char *pwd;
+ struct stat stdot, stpwd;
+ /*static int first = 1;*/
+
+ if (psh->curdir)
+ return psh->curdir;
+
+ if (psh->getpwd_first) {
+ psh->getpwd_first = 0;
+ pwd = sh_getenv(psh, "PWD");
+ if (pwd && IS_ROOT(pwd) && shfile_stat(&psh->fdtab, ".", &stdot) != -1 &&
+ shfile_stat(&psh->fdtab, pwd, &stpwd) != -1 &&
+ stdot.st_dev == stpwd.st_dev &&
+ stdot.st_ino == stpwd.st_ino) {
+ psh->curdir = savestr(psh, pwd);
+ return psh->curdir;
+ }
+ }
+
+ find_curdir(psh, noerror);
+
+ return psh->curdir;
+}
+
+STATIC void
+find_curdir(shinstance *psh, int noerror)
+{
+ int i;
+ char *pwd;
+
+ /*
+ * Things are a bit complicated here; we could have just used
+ * getcwd, but traditionally getcwd is implemented using popen
+ * to /bin/pwd. This creates a problem for us, since we cannot
+ * keep track of the job if it is being ran behind our backs.
+ * So we re-implement getcwd(), and we suppress interrupts
+ * throughout the process. This is not completely safe, since
+ * the user can still break out of it by killing the pwd program.
+ * We still try to use getcwd for systems that we know have a
+ * c implementation of getcwd, that does not open a pipe to
+ * /bin/pwd.
+ */
+#if 1 //defined(__NetBSD__) || defined(__SVR4) || defined(__INNOTEK_LIBC__)
+
+ for (i = MAXPWD;; i *= 2) {
+ pwd = stalloc(psh, i);
+ if (shfile_getcwd(&psh->fdtab, pwd, i) != NULL) {
+ psh->curdir = savestr(psh, pwd);
+ return;
+ }
+ stunalloc(psh, pwd);
+ if (errno == ERANGE)
+ continue;
+ if (!noerror)
+ error(psh, "getcwd() failed: %s", sh_strerror(psh, errno));
+ return;
+ }
+#else
+ {
+ char *p;
+ int status;
+ struct job *jp;
+ int pip[2];
+
+ pwd = stalloc(psh, MAXPWD);
+ INTOFF;
+ if (pipe(pip) < 0)
+ error(psh, "Pipe call failed");
+ jp = makejob((union node *)NULL, 1);
+ if (forkshell(jp, (union node *)NULL, FORK_NOJOB) == 0) {
+ (void) close(pip[0]);
+ if (pip[1] != 1) {
+ close(1);
+ copyfd(pip[1], 1);
+ close(pip[1]);
+ }
+ (void) execl("/bin/pwd", "pwd", (char *)0);
+ error(psh, "Cannot exec /bin/pwd");
+ }
+ (void) close(pip[1]);
+ pip[1] = -1;
+ p = pwd;
+ while ((i = read(pip[0], p, pwd + MAXPWD - p)) > 0
+ || (i == -1 && errno == EINTR)) {
+ if (i > 0)
+ p += i;
+ }
+ (void) close(pip[0]);
+ pip[0] = -1;
+ status = waitforjob(jp);
+ if (status != 0)
+ error(psh, (char *)0);
+ if (i < 0 || p == pwd || p[-1] != '\n') {
+ if (noerror) {
+ INTON;
+ return;
+ }
+ error(psh, "pwd command failed");
+ }
+ p[-1] = '\0';
+ INTON;
+ psh->curdir = savestr(pwd);
+ return;
+ }
+#endif
+}
diff --git a/src/kash/cd.h b/src/kash/cd.h
new file mode 100644
index 0000000..dcb272f
--- /dev/null
+++ b/src/kash/cd.h
@@ -0,0 +1,43 @@
+/* $NetBSD: cd.h,v 1.4 2003/08/07 09:05:30 agc Exp $ */
+
+/*-
+ * Copyright (c) 1995
+ * 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.
+ *
+ */
+
+const char *getpwd(struct shinstance *, int);
+int cdcmd(struct shinstance *, int, char **);
+int pwdcmd(struct shinstance *, int, char **);
+#ifdef PC_DRIVE_LETTERS
+#define IS_ROOT(path) ( *(path) == '/' \
+ || *(path) == '\\' \
+ || ( ((*(path) >= 'A' && *(path) <= 'Z') || (*(path) >= 'a' && *(path) <= 'z')) \
+ && (path)[1] == ':') )
+#else
+#define IS_ROOT(path) ( *(path) == '/' )
+#endif
diff --git a/src/kash/error.c b/src/kash/error.c
new file mode 100644
index 0000000..267f8ff
--- /dev/null
+++ b/src/kash/error.c
@@ -0,0 +1,394 @@
+/* $NetBSD: error.c,v 1.31 2003/08/07 09:05:30 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)error.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: error.c,v 1.31 2003/08/07 09:05:30 agc Exp $");
+#endif /* not lint */
+#endif
+
+/*
+ * Errors and exceptions.
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "shell.h"
+#include "main.h"
+#include "options.h"
+#include "output.h"
+#include "error.h"
+#include "show.h"
+#include "shinstance.h"
+
+
+/*
+ * Code to handle exceptions in C.
+ */
+
+/*struct jmploc *handler;
+int exception;
+volatile int suppressint;
+volatile int intpending;
+char *commandname;*/
+
+SH_NORETURN_1
+static void exverror(shinstance *psh, int, const char *, va_list) SH_NORETURN_2;
+
+/*
+ * Called to raise an exception. Since C doesn't include exceptions, we
+ * just do a longjmp to the exception handler. The type of exception is
+ * stored in the global variable "exception".
+ */
+
+SH_NORETURN_1 void
+exraise(shinstance *psh, int e)
+{
+ if (psh->handler == NULL)
+ sh_abort(psh);
+ psh->exception = e;
+ longjmp(psh->handler->loc, 1);
+}
+
+
+/*
+ * Called from trap.c when a SIGINT is received. (If the user specifies
+ * that SIGINT is to be trapped or ignored using the trap builtin, then
+ * this routine is not called.) Suppressint is nonzero when interrupts
+ * are held using the INTOFF macro. The call to _exit is necessary because
+ * there is a short period after a fork before the signal handlers are
+ * set to the appropriate value for the child. (The test for iflag is
+ * just defensive programming.)
+ */
+
+void
+onint(shinstance *psh)
+{
+ shsigset_t nsigset;
+
+ if (psh->suppressint) {
+ psh->intpending = 1;
+ return;
+ }
+ psh->intpending = 0;
+ sh_sigemptyset(&nsigset);
+ sh_sigprocmask(psh, SIG_SETMASK, &nsigset, NULL);
+ if (psh->rootshell && iflag(psh))
+ exraise(psh, EXINT);
+ else {
+ sh_signal(psh, SIGINT, SH_SIG_DFL);
+ sh_raise_sigint(psh);/*raise(psh, SIGINT);*/
+ }
+ /* NOTREACHED */
+}
+
+static void
+exvwarning(shinstance *psh, int sv_errno, const char *msg, va_list ap)
+{
+ /* Partially emulate line buffered output so that:
+ * printf '%d\n' 1 a 2
+ * and
+ * printf '%d %d %d\n' 1 a 2
+ * both generate sensible text when stdout and stderr are merged.
+ */
+ if (psh->output.nextc != psh->output.buf && psh->output.nextc[-1] == '\n')
+ flushout(&psh->output);
+ if (psh->commandname)
+ outfmt(&psh->errout, "%s: ", psh->commandname);
+ if (msg != NULL) {
+ doformat(&psh->errout, msg, ap);
+ if (sv_errno >= 0)
+ outfmt(&psh->errout, ": ");
+ }
+ if (sv_errno >= 0)
+ outfmt(&psh->errout, "%s", sh_strerror(psh, sv_errno));
+ out2c(psh, '\n');
+ flushout(&psh->errout);
+}
+
+/*
+ * Exverror is called to raise the error exception. If the second argument
+ * is not NULL then error prints an error message using printf style
+ * formatting. It then raises the error exception.
+ */
+static void
+exverror(shinstance *psh, int cond, const char *msg, va_list ap)
+{
+ CLEAR_PENDING_INT;
+ INTOFF;
+
+#ifdef DEBUG
+ if (msg) {
+ va_list va2;
+ TRACE((psh, "exverror(%d, \"", cond));
+# ifdef va_copy /* MSC 2010 still doesn't have va_copy. sigh. */
+ va_copy(va2, ap);
+# else
+ va2 = ap;
+# endif
+ TRACEV((psh, msg, va2));
+ va_end(va2);
+ TRACE((psh, "\") pid=%" SHPID_PRI "\n", sh_getpid(psh)));
+ } else
+ TRACE((psh, "exverror(%d, NULL) pid=%" SHPID_PRI "\n", cond, sh_getpid(psh)));
+#endif
+ if (msg)
+ exvwarning(psh, -1, msg, ap);
+
+ output_flushall(psh);
+ exraise(psh, cond);
+ /* NOTREACHED */
+}
+
+
+SH_NORETURN_1 void
+error(shinstance *psh, const char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ exverror(psh, EXERROR, msg, ap);
+ /* NOTREACHED */
+ va_end(ap);
+}
+
+
+SH_NORETURN_1 void
+exerror(shinstance *psh, int cond, const char *msg, ...)
+{
+ va_list ap;
+
+ va_start(ap, msg);
+ exverror(psh, cond, msg, ap);
+ /* NOTREACHED */
+ va_end(ap);
+}
+
+/*
+ * error/warning routines for external builtins
+ */
+
+SH_NORETURN_1 void
+sh_exit(shinstance *psh, int rval)
+{
+ psh->exerrno = rval & 255;
+ exraise(psh, EXEXEC);
+}
+
+SH_NORETURN_1 void
+sh_err(shinstance *psh, int status, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ exvwarning(psh, errno, fmt, ap);
+ va_end(ap);
+ sh_exit(psh, status);
+}
+
+SH_NORETURN_1 void
+sh_verr(shinstance *psh, int status, const char *fmt, va_list ap)
+{
+ exvwarning(psh, errno, fmt, ap);
+ sh_exit(psh, status);
+}
+
+SH_NORETURN_1 void
+sh_errx(shinstance *psh, int status, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ exvwarning(psh, -1, fmt, ap);
+ va_end(ap);
+ sh_exit(psh, status);
+}
+
+SH_NORETURN_1 void
+sh_verrx(shinstance *psh, int status, const char *fmt, va_list ap)
+{
+ exvwarning(psh, -1, fmt, ap);
+ sh_exit(psh, status);
+}
+
+void
+sh_warn(shinstance *psh, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ exvwarning(psh, errno, fmt, ap);
+ va_end(ap);
+}
+
+void
+sh_vwarn(shinstance *psh, const char *fmt, va_list ap)
+{
+ exvwarning(psh, errno, fmt, ap);
+}
+
+void
+sh_warnx(shinstance *psh, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ exvwarning(psh, -1, fmt, ap);
+ va_end(ap);
+}
+
+void
+sh_vwarnx(shinstance *psh, const char *fmt, va_list ap)
+{
+ exvwarning(psh, -1, fmt, ap);
+}
+
+
+/*
+ * Table of error messages.
+ */
+
+struct errname {
+ short errcode; /* error number */
+ short action; /* operation which encountered the error */
+ const char *msg; /* text describing the error */
+};
+
+
+#define ALL (E_OPEN|E_CREAT|E_EXEC)
+
+STATIC const struct errname errormsg[] = {
+ { EINTR, ALL, "interrupted" },
+ { EACCES, ALL, "permission denied" },
+ { EIO, ALL, "I/O error" },
+ { EEXIST, ALL, "file exists" },
+ { ENOENT, E_OPEN, "no such file" },
+ { ENOENT, E_CREAT,"directory nonexistent" },
+ { ENOENT, E_EXEC, "not found" },
+ { ENOTDIR, E_OPEN, "no such file" },
+ { ENOTDIR, E_CREAT,"directory nonexistent" },
+ { ENOTDIR, E_EXEC, "not found" },
+ { EISDIR, ALL, "is a directory" },
+#ifdef EMFILE
+ { EMFILE, ALL, "too many open files" },
+#endif
+ { ENFILE, ALL, "file table overflow" },
+ { ENOSPC, ALL, "file system full" },
+#ifdef EDQUOT
+ { EDQUOT, ALL, "disk quota exceeded" },
+#endif
+#ifdef ENOSR
+ { ENOSR, ALL, "no streams resources" },
+#endif
+ { ENXIO, ALL, "no such device or address" },
+ { EROFS, ALL, "read-only file system" },
+#ifdef ETXTBSY
+ { ETXTBSY, ALL, "text busy" },
+#endif
+#ifdef EAGAIN
+ { EAGAIN, E_EXEC, "not enough memory" },
+#endif
+ { ENOMEM, ALL, "not enough memory" },
+#ifdef ENOLINK
+ { ENOLINK, ALL, "remote access failed" },
+#endif
+#ifdef EMULTIHOP
+ { EMULTIHOP, ALL, "remote access failed" },
+#endif
+#ifdef ECOMM
+ { ECOMM, ALL, "remote access failed" },
+#endif
+#ifdef ESTALE
+ { ESTALE, ALL, "remote access failed" },
+#endif
+#ifdef ETIMEDOUT
+ { ETIMEDOUT, ALL, "remote access failed" },
+#endif
+#ifdef ELOOP
+ { ELOOP, ALL, "symbolic link loop" },
+#endif
+ { E2BIG, E_EXEC, "argument list too long" },
+#ifdef ELIBACC
+ { ELIBACC, E_EXEC, "shared library missing" },
+#endif
+ { 0, 0, NULL },
+};
+
+
+/*
+ * Return a string describing an error. The returned string may be a
+ * pointer to a static buffer that will be overwritten on the next call.
+ * Action describes the operation that got the error.
+ */
+
+const char *
+errmsg(shinstance *psh, int e, int action)
+{
+ struct errname const *ep;
+ /*static char buf[12];*/
+
+ for (ep = errormsg ; ep->errcode ; ep++) {
+ if (ep->errcode == e && (ep->action & action) != 0)
+ return ep->msg;
+ }
+ fmtstr(psh->errmsg_buf, sizeof psh->errmsg_buf, "error %d", e);
+ return psh->errmsg_buf;
+}
+
+
+#ifdef K_STRICT
+
+KHLP_DECL(void) kHlpAssertMsg1(const char *pszExpr, const char *pszFile, unsigned iLine, const char *pszFunction)
+{
+ shinstance *psh = shthread_get_shell();
+
+ TRACE((psh, "Assertion failed in %s --- %s --- %s(%u)\n", pszFunction, pszExpr, pszFile, iLine));
+ dprintf(psh, "Assertion failed in %s --- %s --- %s(%u)\n", pszFunction, pszExpr, pszFile, iLine);
+}
+
+KHLP_DECL(void) kHlpAssertMsg2(const char *pszFormat, ...)
+{
+ shinstance *psh = shthread_get_shell();
+ va_list va;
+ va_start(va, pszFormat);
+ doformat(psh->out2, pszFormat, va);
+ va_end(va);
+}
+
+#endif /* K_STRICT */
diff --git a/src/kash/error.h b/src/kash/error.h
new file mode 100644
index 0000000..6388a75
--- /dev/null
+++ b/src/kash/error.h
@@ -0,0 +1,133 @@
+/* $NetBSD: error.h,v 1.16 2003/08/07 09:05:30 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)error.h 8.2 (Berkeley) 5/4/95
+ */
+
+#ifndef ___error_h
+#define ___error_h
+
+#include "shtypes.h"
+#include <stdarg.h>
+
+/*
+ * Types of operations (passed to the errmsg routine).
+ */
+
+#define E_OPEN 01 /* opening a file */
+#define E_CREAT 02 /* creating a file */
+#define E_EXEC 04 /* executing a program */
+
+
+/*
+ * We enclose jmp_buf in a structure so that we can declare pointers to
+ * jump locations. The global variable handler contains the location to
+ * jump to when an exception occurs, and the global variable exception
+ * contains a code identifying the exeception. To implement nested
+ * exception handlers, the user should save the value of handler on entry
+ * to an inner scope, set handler to point to a jmploc structure for the
+ * inner scope, and restore handler on exit from the scope.
+ */
+
+#ifndef __HAIKU__
+# include <setjmp.h>
+#else
+# include <posix/setjmp.h> /** @todo silly */
+#endif
+
+struct jmploc {
+ jmp_buf loc;
+};
+
+/*
+extern struct jmploc *handler;
+extern int exception;
+extern int exerrno;*/ /* error for EXEXEC */
+
+/* exceptions */
+#define EXINT 0 /* SIGINT received */
+#define EXERROR 1 /* a generic error */
+#define EXSHELLPROC 2 /* execute a shell procedure */
+#define EXEXEC 3 /* command execution failed */
+
+
+/*
+ * These macros allow the user to suspend the handling of interrupt signals
+ * over a period of time. This is similar to SIGHOLD to or sigblock, but
+ * much more efficient and portable. (But hacking the kernel is so much
+ * more fun than worrying about efficiency and portability. :-))
+ */
+
+/*extern volatile int suppressint;
+extern volatile int intpending;*/
+
+#define INTOFF psh->suppressint++
+#define INTON { if (--psh->suppressint == 0 && psh->intpending) onint(psh); }
+#define FORCEINTON {psh->suppressint = 0; if (psh->intpending) onint(psh);}
+#define CLEAR_PENDING_INT psh->intpending = 0
+#define int_pending() psh->intpending
+
+#if !defined(__GNUC__) && !defined(__attribute__)
+# define __attribute__(a)
+#endif
+
+SH_NORETURN_1 void exraise(struct shinstance *, int) SH_NORETURN_2;
+void onint(struct shinstance *);
+SH_NORETURN_1 void error(struct shinstance *, const char *, ...) SH_NORETURN_2;
+SH_NORETURN_1 void exerror(struct shinstance *, int, const char *, ...) SH_NORETURN_2;
+const char *errmsg(struct shinstance *, int, int);
+
+SH_NORETURN_1 void sh_err(struct shinstance *, int, const char *, ...) SH_NORETURN_2;
+SH_NORETURN_1 void sh_verr(struct shinstance *, int, const char *, va_list) SH_NORETURN_2;
+SH_NORETURN_1 void sh_errx(struct shinstance *, int, const char *, ...) SH_NORETURN_2;
+SH_NORETURN_1 void sh_verrx(struct shinstance *, int, const char *, va_list) SH_NORETURN_2;
+void sh_warn(struct shinstance *, const char *, ...);
+void sh_vwarn(struct shinstance *, const char *, va_list);
+void sh_warnx(struct shinstance *, const char *, ...);
+void sh_vwarnx(struct shinstance *, const char *, va_list);
+
+SH_NORETURN_1 void sh_exit(struct shinstance *, int) SH_NORETURN_2;
+
+
+/*
+ * BSD setjmp saves the signal mask, which violates ANSI C and takes time,
+ * so we use _setjmp instead.
+ */
+
+#if defined(BSD) && !defined(__SVR4) && !defined(__GLIBC__) \
+ && !defined(__KLIBC__) && !defined(_MSC_VER) && !defined(__HAIKU__)
+#define setjmp(jmploc) _setjmp(jmploc)
+#define longjmp(jmploc, val) _longjmp(jmploc, val)
+#endif
+
+#endif
diff --git a/src/kash/eval.c b/src/kash/eval.c
new file mode 100644
index 0000000..5cbfac1
--- /dev/null
+++ b/src/kash/eval.c
@@ -0,0 +1,1485 @@
+/* $NetBSD: eval.c,v 1.84 2005/06/23 23:05:29 christos Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95";
+#else
+__RCSID("$NetBSD: eval.c,v 1.84 2005/06/23 23:05:29 christos Exp $");
+#endif /* not lint */
+#endif
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#ifdef HAVE_SYSCTL_H
+# ifdef __OpenBSD__ /* joyful crap */
+# include <sys/param.h>
+# undef psh
+# endif
+# include <sys/sysctl.h>
+#endif
+
+/*
+ * Evaluate a command.
+ */
+
+#include "shell.h"
+#include "nodes.h"
+#include "syntax.h"
+#include "expand.h"
+#include "parser.h"
+#include "jobs.h"
+#include "eval.h"
+#include "builtins.h"
+#include "options.h"
+#include "exec.h"
+#include "redir.h"
+#include "input.h"
+#include "output.h"
+#include "trap.h"
+#include "var.h"
+#include "memalloc.h"
+#include "error.h"
+#include "show.h"
+#include "mystring.h"
+#include "main.h"
+#ifndef SMALL
+# include "myhistedit.h"
+#endif
+#include "shinstance.h"
+
+
+/* flags in argument to evaltree */
+#define EV_EXIT 01 /* exit after evaluating tree */
+#define EV_TESTED 02 /* exit status is checked; ignore -e flag */
+#define EV_BACKCMD 04 /* command executing within back quotes */
+
+/*int evalskip;*/ /* set if we are skipping commands */
+/*STATIC int skipcount;*/ /* number of levels to skip */
+/*MKINIT int loopnest;*/ /* current loop nesting level */
+/*int funcnest;*/ /* depth of function calls */
+
+
+/*char *commandname;*/
+/*struct strlist *cmdenviron;*/
+/*int exitstatus;*/ /* exit status of last command */
+/*int back_exitstatus;*/ /* exit status of backquoted command */
+
+
+STATIC void evalloop(shinstance *, union node *, int);
+STATIC void evalfor(shinstance *, union node *, int);
+STATIC void evalcase(shinstance *, union node *, int);
+STATIC void evalsubshell(shinstance *, union node *, int);
+STATIC unsigned expredir(shinstance *, union node *);
+STATIC void evalpipe(shinstance *, union node *);
+STATIC void evalcommand(shinstance *, union node *, int, struct backcmd *);
+STATIC void prehash(shinstance *, union node *);
+
+
+/*
+ * Called to reset things after an exception.
+ */
+
+#ifdef mkinit
+INCLUDE "eval.h"
+
+RESET {
+ psh->evalskip = 0;
+ psh->loopnest = 0;
+ psh->funcnest = 0;
+}
+
+SHELLPROC {
+ psh->exitstatus = 0;
+}
+#endif
+
+static int
+sh_pipe(shinstance *psh, int fds[2])
+{
+ int nfd;
+
+ if (shfile_pipe(&psh->fdtab, fds))
+ return -1;
+
+ if (fds[0] < 3) {
+ nfd = shfile_fcntl(&psh->fdtab, fds[0], F_DUPFD, 3);
+ if (nfd != -1) {
+ shfile_close(&psh->fdtab, fds[0]);
+ fds[0] = nfd;
+ }
+ }
+
+ if (fds[1] < 3) {
+ nfd = shfile_fcntl(&psh->fdtab, fds[1], F_DUPFD, 3);
+ if (nfd != -1) {
+ shfile_close(&psh->fdtab, fds[1]);
+ fds[1] = nfd;
+ }
+ }
+ return 0;
+}
+
+
+/*
+ * The eval commmand.
+ */
+
+int
+evalcmd(shinstance *psh, int argc, char **argv)
+{
+ char *p;
+ char *concat;
+ char **ap;
+
+ if (argc > 1) {
+ p = argv[1];
+ if (argc > 2) {
+ STARTSTACKSTR(psh, concat);
+ ap = argv + 2;
+ for (;;) {
+ while (*p)
+ STPUTC(psh, *p++, concat);
+ if ((p = *ap++) == NULL)
+ break;
+ STPUTC(psh, ' ', concat);
+ }
+ STPUTC(psh, '\0', concat);
+ p = grabstackstr(psh, concat);
+ }
+ evalstring(psh, p, EV_TESTED);
+ }
+ return psh->exitstatus;
+}
+
+
+/*
+ * Execute a command or commands contained in a string.
+ */
+
+void
+evalstring(shinstance *psh, char *s, int flag)
+{
+ union node *n;
+ struct stackmark smark;
+
+ setstackmark(psh, &smark);
+ setinputstring(psh, s, 1);
+
+ while ((n = parsecmd(psh, 0)) != NEOF) {
+ evaltree(psh, n, flag);
+ popstackmark(psh, &smark);
+ }
+ popfile(psh);
+ popstackmark(psh, &smark);
+}
+
+
+
+/*
+ * Evaluate a parse tree. The value is left in the global variable
+ * exitstatus.
+ */
+
+void
+evaltree(shinstance *psh, union node *n, int flags)
+{
+ if (n == NULL) {
+ TRACE((psh, "evaltree(NULL) called\n"));
+ psh->exitstatus = 0;
+ goto out;
+ }
+#ifndef SMALL
+ psh->displayhist = 1; /* show history substitutions done with fc */
+#endif
+ TRACE((psh, "pid %" SHPID_PRI ", evaltree(%p: %d, %d) called\n",
+ sh_getpid(psh), n, n->type, flags));
+ switch (n->type) {
+ case NSEMI:
+ evaltree(psh, n->nbinary.ch1, flags & EV_TESTED);
+ if (psh->evalskip)
+ goto out;
+ evaltree(psh, n->nbinary.ch2, flags);
+ break;
+ case NAND:
+ evaltree(psh, n->nbinary.ch1, EV_TESTED);
+ if (psh->evalskip || psh->exitstatus != 0)
+ goto out;
+ evaltree(psh, n->nbinary.ch2, flags);
+ break;
+ case NOR:
+ evaltree(psh, n->nbinary.ch1, EV_TESTED);
+ if (psh->evalskip || psh->exitstatus == 0)
+ goto out;
+ evaltree(psh, n->nbinary.ch2, flags);
+ break;
+ case NREDIR: {
+ unsigned const oldfnames = expredir(psh, n->nredir.redirect);
+ redirect(psh, n->nredir.redirect, REDIR_PUSH);
+ evaltree(psh, n->nredir.n, flags);
+ popredir(psh);
+ expredircleanup(psh, oldfnames);
+ break;
+ }
+ case NSUBSHELL:
+ evalsubshell(psh, n, flags);
+ break;
+ case NBACKGND:
+ evalsubshell(psh, n, flags);
+ break;
+ case NIF: {
+ evaltree(psh, n->nif.test, EV_TESTED);
+ if (psh->evalskip)
+ goto out;
+ if (psh->exitstatus == 0)
+ evaltree(psh, n->nif.ifpart, flags);
+ else if (n->nif.elsepart)
+ evaltree(psh, n->nif.elsepart, flags);
+ else
+ psh->exitstatus = 0;
+ break;
+ }
+ case NWHILE:
+ case NUNTIL:
+ evalloop(psh, n, flags);
+ break;
+ case NFOR:
+ evalfor(psh, n, flags);
+ break;
+ case NCASE:
+ evalcase(psh, n, flags);
+ break;
+ case NDEFUN:
+ defun(psh, n->narg.text, n->narg.next);
+ psh->exitstatus = 0;
+ break;
+ case NNOT:
+ evaltree(psh, n->nnot.com, EV_TESTED);
+ psh->exitstatus = !psh->exitstatus;
+ break;
+ case NPIPE:
+ evalpipe(psh, n);
+ break;
+ case NCMD:
+ evalcommand(psh, n, flags, (struct backcmd *)NULL);
+ break;
+ default:
+ out1fmt(psh, "Node type = %d\n", n->type);
+ flushout(&psh->output);
+ break;
+ }
+out:
+ if (psh->pendingsigs)
+ dotrap(psh);
+ if ((flags & EV_EXIT) != 0)
+ exitshell(psh, psh->exitstatus);
+}
+
+
+STATIC void
+evalloop(shinstance *psh, union node *n, int flags)
+{
+ int status;
+
+ psh->loopnest++;
+ status = 0;
+ for (;;) {
+ evaltree(psh, n->nbinary.ch1, EV_TESTED);
+ if (psh->evalskip) {
+skipping: if (psh->evalskip == SKIPCONT && --psh->skipcount <= 0) {
+ psh->evalskip = 0;
+ continue;
+ }
+ if (psh->evalskip == SKIPBREAK && --psh->skipcount <= 0)
+ psh->evalskip = 0;
+ break;
+ }
+ if (n->type == NWHILE) {
+ if (psh->exitstatus != 0)
+ break;
+ } else {
+ if (psh->exitstatus == 0)
+ break;
+ }
+ evaltree(psh, n->nbinary.ch2, flags & EV_TESTED);
+ status = psh->exitstatus;
+ if (psh->evalskip)
+ goto skipping;
+ }
+ psh->loopnest--;
+ psh->exitstatus = status;
+}
+
+
+
+STATIC void
+evalfor(shinstance *psh, union node *n, int flags)
+{
+ struct arglist arglist;
+ union node *argp;
+ struct strlist *sp;
+ struct stackmark smark;
+ int status = 0;
+
+ setstackmark(psh, &smark);
+ arglist.lastp = &arglist.list;
+ for (argp = n->nfor.args ; argp ; argp = argp->narg.next) {
+ expandarg(psh, argp, &arglist, EXP_FULL | EXP_TILDE);
+ if (psh->evalskip)
+ goto out;
+ }
+ *arglist.lastp = NULL;
+
+ psh->loopnest++;
+ for (sp = arglist.list ; sp ; sp = sp->next) {
+ setvar(psh, n->nfor.var, sp->text, 0);
+ evaltree(psh, n->nfor.body, flags & EV_TESTED);
+ status = psh->exitstatus;
+ if (psh->evalskip) {
+ if (psh->evalskip == SKIPCONT && --psh->skipcount <= 0) {
+ psh->evalskip = 0;
+ continue;
+ }
+ if (psh->evalskip == SKIPBREAK && --psh->skipcount <= 0)
+ psh->evalskip = 0;
+ break;
+ }
+ }
+ psh->loopnest--;
+ psh->exitstatus = status;
+out:
+ popstackmark(psh, &smark);
+}
+
+
+
+STATIC void
+evalcase(shinstance *psh, union node *n, int flags)
+{
+ union node *cp;
+ union node *patp;
+ struct arglist arglist;
+ struct stackmark smark;
+ int status = 0;
+
+ setstackmark(psh, &smark);
+ arglist.lastp = &arglist.list;
+ expandarg(psh, n->ncase.expr, &arglist, EXP_TILDE);
+ for (cp = n->ncase.cases ; cp && psh->evalskip == 0 ; cp = cp->nclist.next) {
+ for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) {
+ if (casematch(psh, patp, arglist.list->text)) {
+ if (psh->evalskip == 0) {
+ evaltree(psh, cp->nclist.body, flags);
+ status = psh->exitstatus;
+ }
+ goto out;
+ }
+ }
+ }
+out:
+ psh->exitstatus = status;
+ popstackmark(psh, &smark);
+}
+
+
+#ifdef KASH_USE_FORKSHELL2
+/*
+ * Child of evalsubshell.
+ */
+struct evalsubshellchild
+{
+ int flags;
+ int backgnd;
+};
+
+static int evalsubshell_child(shinstance *psh, union node *n, void *argp)
+{
+ struct evalsubshellchild args = *(struct evalsubshellchild *)argp;
+
+ INTON;
+ if (args.backgnd)
+ args.flags &=~ EV_TESTED;
+ redirect(psh, n->nredir.redirect, 0);
+ /* never returns */
+ evaltree(psh, n->nredir.n, args.flags | EV_EXIT);
+ /** @todo make us return here. */
+ return 0;
+}
+#endif /* KASH_USE_FORKSHELL2 */
+
+
+/*
+ * Kick off a subshell to evaluate a tree.
+ */
+
+STATIC void
+evalsubshell(shinstance *psh, union node *n, int flags)
+{
+ struct job *jp;
+ int backgnd = (n->type == NBACKGND);
+ unsigned expfnamedepth;
+
+ expfnamedepth = expredir(psh, n->nredir.redirect);
+ INTOFF;
+ jp = makejob(psh, n, 1);
+#ifdef KASH_USE_FORKSHELL2
+ {
+ struct evalsubshellchild args;
+ args.flags = flags;
+ args.backgnd = backgnd;
+ forkshell2(psh, jp, n, backgnd ? FORK_BG : FORK_FG,
+ evalsubshell_child, n, &args, sizeof(args), NULL);
+ }
+#else
+ if (forkshell(psh, jp, n, backgnd ? FORK_BG : FORK_FG) == 0) {
+ INTON;
+ if (backgnd)
+ flags &=~ EV_TESTED;
+ redirect(psh, n->nredir.redirect, 0);
+ /* never returns */
+ evaltree(psh, n->nredir.n, flags | EV_EXIT);
+ }
+#endif
+ if (! backgnd)
+ psh->exitstatus = waitforjob(psh, jp);
+ expredircleanup(psh, expfnamedepth);
+ INTON;
+}
+
+
+
+/*
+ * Compute the names of the files in a redirection list.
+ */
+
+STATIC unsigned
+expredir(shinstance *psh, union node *n)
+{
+ union node *redir;
+ redirexpfnames *expfnames;
+ unsigned i;
+
+ /* We typically end up here w/o redirections. */
+ if (!n)
+ return !(expfnames = psh->expfnames) ? 0 : expfnames->depth + 1;
+
+ /* Prepare a table for the expanded names. */
+ i = 0;
+ for (redir = n; redir ; redir = redir->nfile.next)
+ i++;
+ expfnames = stalloc(psh, offsetof(redirexpfnames, names) + sizeof(expfnames->names[0]) * i);
+ expfnames->count = i;
+ TRACE2((psh, "expredir: %p: count=%u\n", expfnames, i));
+
+ /* Do the expansion. */
+ for (redir = n, i = 0 ; redir ; redir = redir->nfile.next, i++) {
+ struct arglist fn;
+ fn.lastp = &fn.list;
+ switch (redir->type) {
+ case NFROMTO:
+ case NFROM:
+ case NTO:
+ case NCLOBBER:
+ case NAPPEND:
+ expandarg(psh, redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
+ expfnames->names[i] = fn.list->text;
+ break;
+ case NFROMFD:
+ case NTOFD:
+ if (redir->ndup.vname) {
+ expandarg(psh, redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
+ fixredir(psh, redir, fn.list->text, 1);
+ }
+ expfnames->names[i] = NULL;
+ break;
+ default:
+ kHlpAssert(redir->type == NHERE || redir->type == NXHERE);
+ expfnames->names[i] = NULL;
+ break;
+ }
+ }
+ kHlpAssert(i == expfnames->count);
+
+ /* Do the linking at the end, as nesting happens when we expand backtick arguments. */
+ expfnames->prev = psh->expfnames;
+ psh->expfnames = expfnames;
+ return expfnames->depth = psh->expfnames ? psh->expfnames->depth + 1 : 1;
+}
+
+STATIC void
+expredircleanup(shinstance *psh, unsigned depth)
+{
+ redirexpfnames *expfnames = psh->expfnames;
+ kHlpAssert(expfnames == NULL ? depth == 0 : expfnames->depth == depth || expfnames->depth + 1 == depth);
+ while (expfnames && expfnames->depth >= depth)
+ expfnames = psh->expfnames = expfnames->prev;
+}
+
+
+#ifdef KASH_USE_FORKSHELL2
+/*
+ * Child of evalpipe.
+ */
+struct evalpipechild
+{
+ int prevfd;
+ int pip[2];
+};
+
+static int evalpipe_child(shinstance *psh, union node *n, void *argp)
+{
+ struct evalpipechild args = *(struct evalpipechild *)argp;
+
+ if (args.prevfd > 0) {
+ movefd(psh, args.prevfd, 0);
+ }
+ if (args.pip[1] >= 0) {
+ shfile_close(&psh->fdtab, args.pip[0]);
+ if (args.pip[1] != 1) {
+ movefd(psh, args.pip[1], 1);
+ }
+ }
+ evaltree(psh, n, EV_EXIT);
+ /** @todo make it return thru here. */
+ return 0;
+}
+#endif /* KASH_USE_FORKSHELL2 */
+
+/*
+ * Evaluate a pipeline. All the processes in the pipeline are children
+ * of the process creating the pipeline. (This differs from some versions
+ * of the shell, which make the last process in a pipeline the parent
+ * of all the rest.)
+ */
+
+STATIC void
+evalpipe(shinstance *psh, union node *n)
+{
+ struct job *jp;
+ struct nodelist *lp;
+ int pipelen;
+ int prevfd;
+ int pip[2];
+
+ TRACE((psh, "evalpipe(0x%lx) called\n", (long)n));
+ pipelen = 0;
+ for (lp = n->npipe.cmdlist ; lp ; lp = lp->next)
+ pipelen++;
+ INTOFF;
+ jp = makejob(psh, n, pipelen);
+ prevfd = -1;
+ for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+ prehash(psh, lp->n);
+ pip[1] = -1;
+ if (lp->next) {
+ if (sh_pipe(psh, pip) < 0) {
+ shfile_close(&psh->fdtab, prevfd);
+ error(psh, "Pipe call failed");
+ }
+ }
+#ifdef KASH_USE_FORKSHELL2
+ {
+ struct evalpipechild args;
+ args.prevfd = prevfd;
+ args.pip[0] = pip[0];
+ args.pip[1] = pip[1];
+ forkshell2(psh, jp, lp->n, n->npipe.backgnd ? FORK_BG : FORK_FG,
+ evalpipe_child, lp->n, &args, sizeof(args), NULL);
+ }
+#else
+ if (forkshell(psh, jp, lp->n, n->npipe.backgnd ? FORK_BG : FORK_FG) == 0) {
+ INTON;
+ if (prevfd > 0) {
+ movefd(psh, prevfd, 0);
+ }
+ if (pip[1] >= 0) {
+ shfile_close(&psh->fdtab, pip[0]);
+ if (pip[1] != 1) {
+ movefd(psh, pip[1], 1);
+ }
+ }
+ evaltree(psh, lp->n, EV_EXIT);
+ }
+#endif
+ if (prevfd >= 0)
+ shfile_close(&psh->fdtab, prevfd);
+ prevfd = pip[0];
+ shfile_close(&psh->fdtab, pip[1]);
+ }
+ if (n->npipe.backgnd == 0) {
+ psh->exitstatus = waitforjob(psh, jp);
+ TRACE((psh, "evalpipe: job done exit status %d\n", psh->exitstatus));
+ }
+ INTON;
+}
+
+#ifdef KASH_USE_FORKSHELL2
+/*
+ * evalbackcmd child.
+ */
+struct evalbackcmdchild
+{
+ int pip[2];
+};
+
+static int evalbackcmd_child(shinstance *psh, union node *n, void *argp)
+{
+ struct evalbackcmdchild args = *(struct evalbackcmdchild *)argp;
+
+ FORCEINTON;
+ shfile_close(&psh->fdtab, args.pip[0]);
+ if (args.pip[1] != 1) {
+ movefd(psh, args.pip[1], 1);
+ }
+ eflag(psh) = 0;
+ evaltree(psh, n, EV_EXIT);
+ /* NOTREACHED */ /** @todo make it return here to simplify thread handling (no need for setjmp). */
+ return 0;
+}
+#endif /* KASH_USE_FORKSHELL2 */
+
+/*
+ * Execute a command inside back quotes. If it's a builtin command, we
+ * want to save its output in a block obtained from malloc. Otherwise
+ * we fork off a subprocess and get the output of the command via a pipe.
+ * Should be called with interrupts off.
+ */
+
+void
+evalbackcmd(shinstance *psh, union node *n, struct backcmd *result)
+{
+ int pip[2];
+ struct job *jp;
+ struct stackmark smark; /* unnecessary */
+
+ setstackmark(psh, &smark);
+ result->fd = -1;
+ result->buf = NULL;
+ result->nleft = 0;
+ result->jp = NULL;
+ if (n == NULL) {
+ goto out;
+ }
+#ifdef notyet
+ /*
+ * For now we disable executing builtins in the same
+ * context as the shell, because we are not keeping
+ * enough state to recover from changes that are
+ * supposed only to affect subshells. eg. echo "`cd /`"
+ */
+ if (n->type == NCMD) {
+ psh->exitstatus = opsh->exitstatus;
+ evalcommand(psh, n, EV_BACKCMD, result);
+ } else
+#endif
+ {
+ INTOFF;
+ if (sh_pipe(psh, pip) < 0)
+ error(psh, "Pipe call failed");
+ jp = makejob(psh, n, 1);
+#ifdef KASH_USE_FORKSHELL2
+ {
+ struct evalbackcmdchild args;
+ args.pip[0] = pip[0];
+ args.pip[1] = pip[1];
+ forkshell2(psh, jp, n, FORK_NOJOB,
+ evalbackcmd_child, n, &args, sizeof(args), NULL);
+ }
+#else
+ if (forkshell(psh, jp, n, FORK_NOJOB) == 0) {
+ FORCEINTON;
+ shfile_close(&psh->fdtab, pip[0]);
+ if (pip[1] != 1) {
+ movefd(psh, pip[1], 1);
+ }
+ eflag(psh) = 0;
+ evaltree(psh, n, EV_EXIT);
+ /* NOTREACHED */
+ }
+#endif
+ shfile_close(&psh->fdtab, pip[1]);
+ result->fd = pip[0];
+ result->jp = jp;
+ INTON;
+ }
+out:
+ popstackmark(psh, &smark);
+ TRACE((psh, "evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
+ result->fd, result->buf, result->nleft, result->jp));
+}
+
+static const char *
+syspath(shinstance *psh)
+{
+#ifdef CTL_USER
+ static char *sys_path = NULL;
+ static int mib[] = {CTL_USER, USER_CS_PATH};
+#endif
+#ifdef PC_PATH_SEP
+ static char def_path[] = "PATH=/usr/bin;/bin;/usr/sbin;/sbin";
+#else
+ static char def_path[] = "PATH=/usr/bin:/bin:/usr/sbin:/sbin";
+#endif
+#ifdef CTL_USER
+ size_t len;
+
+ if (sys_path == NULL) {
+ if (sysctl(mib, 2, 0, &len, 0, 0) != -1 &&
+ (sys_path = ckmalloc(psh, len + 5)) != NULL &&
+ sysctl(mib, 2, sys_path + 5, &len, 0, 0) != -1) {
+ memcpy(sys_path, "PATH=", 5);
+ } else {
+ ckfree(psh, sys_path);
+ /* something to keep things happy */
+ sys_path = def_path;
+ }
+ }
+ return sys_path;
+#else
+ return def_path;
+#endif
+}
+
+static int
+parse_command_args(shinstance *psh, int argc, char **argv, int *use_syspath)
+{
+ int sv_argc = argc;
+ char *cp, c;
+
+ *use_syspath = 0;
+
+ for (;;) {
+ argv++;
+ if (--argc == 0)
+ break;
+ cp = *argv;
+ if (*cp++ != '-')
+ break;
+ if (*cp == '-' && cp[1] == 0) {
+ argv++;
+ argc--;
+ break;
+ }
+ while ((c = *cp++)) {
+ switch (c) {
+ case 'p':
+ *use_syspath = 1;
+ break;
+ default:
+ /* run 'typecmd' for other options */
+ return 0;
+ }
+ }
+ }
+ return sv_argc - argc;
+}
+
+
+/*
+ * The split up evalcommand code:
+ * evalcommand_out, evalcommand_parent, evalcommand_doit, evalcommand_child
+ */
+/*int vforked = 0; - obsolete */
+
+/* Both child and parent exits thru here. */
+STATIC void
+evalcommand_out(shinstance *psh, int flags, char *lastarg, unsigned expfnamedepth, struct stackmark *smarkp)
+{
+ if (lastarg)
+ /* dsl: I think this is intended to be used to support
+ * '_' in 'vi' command mode during line editing...
+ * However I implemented that within libedit itself.
+ */
+ setvar(psh, "_", lastarg, 0);
+ expredircleanup(psh, expfnamedepth);
+ popstackmark(psh, smarkp);
+
+ if (eflag(psh) && psh->exitstatus && !(flags & EV_TESTED))
+ exitshell(psh, psh->exitstatus);
+}
+
+
+/* Called if we forkshell(). */
+STATIC void
+evalcommand_parent(shinstance *psh, int flags, char *lastarg, unsigned expfnamedepth, struct stackmark *smarkp,
+ int mode, struct job *jp, int pip[2], struct backcmd *backcmd)
+{
+ if (mode == FORK_FG) { /* argument to fork */
+ psh->exitstatus = waitforjob(psh, jp);
+ } else if (mode == FORK_NOJOB) {
+ backcmd->fd = pip[0];
+ shfile_close(&psh->fdtab, pip[1]);
+ backcmd->jp = jp;
+ }
+ FORCEINTON;
+
+ evalcommand_out(psh, flags, lastarg, expfnamedepth, smarkp);
+}
+
+struct evalcommanddoit
+{
+ struct stackmark smark;
+ unsigned expfnamedepth;
+
+ struct backcmd *backcmd;
+ int flags;
+ int argc;
+ char **argv;
+ char *lastarg;
+ struct arglist varlist;
+ const char *path;
+ struct cmdentry cmdentry;
+
+ /* for child stuff only: */
+ int pip[2];
+};
+
+STATIC void
+evalcommand_doit(shinstance *psh, union node *cmd, struct evalcommanddoit *args)
+{
+ struct jmploc jmploc;
+ struct jmploc *volatile savehandler;
+ struct localvar *volatile savelocalvars;
+
+ /* This is the child process if a fork occurred. */
+ /* Execute the command. */
+ switch (args->cmdentry.cmdtype) {
+ case CMDFUNCTION: {
+ volatile struct shparam saveparam;
+#ifdef DEBUG
+ trputs(psh, "Shell function: "); trargs(psh, args->argv);
+#endif
+ redirect(psh, cmd->ncmd.redirect, REDIR_PUSH);
+ saveparam = psh->shellparam;
+ psh->shellparam.malloc = 0;
+ psh->shellparam.reset = 1;
+ psh->shellparam.nparam = args->argc - 1;
+ psh->shellparam.p = args->argv + 1;
+ psh->shellparam.optnext = NULL;
+ INTOFF;
+ savelocalvars = psh->localvars;
+ psh->localvars = NULL;
+ INTON;
+ if (setjmp(jmploc.loc)) {
+ if (psh->exception == EXSHELLPROC) {
+ freeparam(psh, (volatile struct shparam *)
+ &saveparam);
+ } else {
+ freeparam(psh, &psh->shellparam);
+ psh->shellparam = saveparam;
+ }
+ poplocalvars(psh);
+ psh->localvars = savelocalvars;
+ psh->handler = savehandler;
+ longjmp(psh->handler->loc, 1);
+ }
+ savehandler = psh->handler;
+ psh->handler = &jmploc;
+ listmklocal(psh, args->varlist.list, 0);
+ /* stop shell blowing its stack */
+ if (++psh->funcnest > 1000)
+ error(psh, "too many nested function calls");
+ evaltree(psh, args->cmdentry.u.func, args->flags & EV_TESTED);
+ psh->funcnest--;
+ INTOFF;
+ poplocalvars(psh);
+ psh->localvars = savelocalvars;
+ freeparam(psh, &psh->shellparam);
+ psh->shellparam = saveparam;
+ psh->handler = savehandler;
+ popredir(psh);
+ INTON;
+ if (psh->evalskip == SKIPFUNC) {
+ psh->evalskip = 0;
+ psh->skipcount = 0;
+ }
+ if (args->flags & EV_EXIT)
+ exitshell(psh, psh->exitstatus);
+ break;
+ }
+
+ case CMDBUILTIN:
+ case CMDSPLBLTIN: {
+ volatile int temp_path = 0;
+ char *volatile savecmdname;
+ int volatile savecmdnamemalloc;
+ volatile int e;
+ int mode;
+#ifdef DEBUG
+ trputs(psh, "builtin command: "); trargs(psh, args->argv);
+#endif
+ mode = (args->cmdentry.u.bltin == execcmd) ? 0 : REDIR_PUSH;
+ if (args->flags == EV_BACKCMD) {
+ psh->memout.nleft = 0;
+ psh->memout.nextc = psh->memout.buf;
+ psh->memout.bufsize = 64;
+ mode |= REDIR_BACKQ;
+ }
+ e = -1;
+ savehandler = psh->handler;
+ savecmdname = psh->commandname;
+ savecmdnamemalloc = psh->commandnamemalloc;
+ psh->handler = &jmploc;
+ if (!setjmp(jmploc.loc)) {
+ /* We need to ensure the command hash table isn't
+ * corruped by temporary PATH assignments.
+ * However we must ensure the 'local' command works!
+ */
+ if (args->path != pathval(psh) && (args->cmdentry.u.bltin == hashcmd ||
+ args->cmdentry.u.bltin == typecmd)) {
+ savelocalvars = psh->localvars;
+ psh->localvars = 0;
+ mklocal(psh, args->path - 5 /* PATH= */, 0);
+ temp_path = 1;
+ } else
+ temp_path = 0;
+ redirect(psh, cmd->ncmd.redirect, mode);
+
+ /* exec is a special builtin, but needs this list... */
+ psh->cmdenviron = args->varlist.list;
+ /* we must check 'readonly' flag for all builtins */
+ listsetvar(psh, args->varlist.list,
+ args->cmdentry.cmdtype == CMDSPLBLTIN ? 0 : VNOSET);
+ psh->commandnamemalloc = 0;
+ psh->commandname = args->argv[0];
+ /* initialize nextopt */
+ psh->argptr = args->argv + 1;
+ psh->optptr = NULL;
+ /* and getopt */
+#if 0 /** @todo fix getop usage! */
+#if defined(__FreeBSD__) || defined(__EMX__) || defined(__APPLE__)
+ optreset = 1;
+ optind = 1;
+#else
+ optind = 0; /* init */
+#endif
+#endif
+
+ psh->exitstatus = args->cmdentry.u.bltin(psh, args->argc, args->argv);
+ } else {
+ e = psh->exception;
+ psh->exitstatus = e == EXINT ? SIGINT + 128 :
+ e == EXEXEC ? psh->exerrno : 2;
+ }
+ psh->handler = savehandler;
+ output_flushall(psh);
+ psh->out1 = &psh->output;
+ psh->out2 = &psh->errout;
+ freestdout(psh);
+ if (temp_path) {
+ poplocalvars(psh);
+ psh->localvars = savelocalvars;
+ }
+ psh->cmdenviron = NULL;
+ if (e != EXSHELLPROC) {
+ psh->commandname = savecmdname;
+ psh->commandnamemalloc = savecmdnamemalloc;
+ if (args->flags & EV_EXIT)
+ exitshell(psh, psh->exitstatus);
+ }
+ else if (savecmdnamemalloc)
+ sh_free(psh, savecmdname);
+ if (e != -1) {
+ if ((e != EXERROR && e != EXEXEC)
+ || args->cmdentry.cmdtype == CMDSPLBLTIN)
+ exraise(psh, e);
+ FORCEINTON;
+ }
+ if (args->cmdentry.u.bltin != execcmd)
+ popredir(psh);
+ if (args->flags == EV_BACKCMD) {
+ args->backcmd->buf = psh->memout.buf;
+ args->backcmd->nleft = (int)(psh->memout.nextc - psh->memout.buf);
+ psh->memout.buf = NULL;
+ }
+ break;
+ }
+
+ default: {
+ struct strlist *sp;
+ char **envp;
+#ifdef DEBUG
+ trputs(psh, "normal command: "); trargs(psh, args->argv);
+#endif
+ clearredir(psh);
+ redirect(psh, cmd->ncmd.redirect, 0);
+ for (sp = args->varlist.list ; sp ; sp = sp->next)
+ setvareq(psh, sp->text, VEXPORT|VSTACK);
+ envp = environment(psh);
+ shellexec(psh, args->argv, envp, args->path,
+ args->cmdentry.u.n.index, args->cmdentry.u.n.suffix);
+ break;
+ }
+ }
+
+ evalcommand_out(psh, args->flags, args->lastarg, args->expfnamedepth, &args->smark);
+}
+
+/* child callback. */
+static int evalcommand_child(shinstance *psh, union node *cmd, void *argp)
+{
+ struct evalcommanddoit *args = (struct evalcommanddoit *)argp;
+
+ if (args->flags & EV_BACKCMD) {
+ FORCEINTON;
+ shfile_close(&psh->fdtab, args->pip[0]);
+ if (args->pip[1] != 1) {
+ movefd(psh, args->pip[1], 1);
+ }
+ }
+ args->flags |= EV_EXIT;
+
+ evalcommand_doit(psh, cmd, args);
+ /* not reached */ /** @todo make it return here */
+ return 0;
+}
+
+#ifdef KASH_USE_FORKSHELL2
+/* Copies data in the argument structure from parent to child. */
+static void evalcommand_setup_child(shinstance *pshchild, shinstance *pshparent, void *argp)
+{
+ struct evalcommanddoit *args = (struct evalcommanddoit *)argp;
+ char **argv;
+ char **srcargv;
+ struct strlist *sp;
+ int argc, i;
+
+ setstackmark(pshchild, &args->smark);
+
+ /* copy arguments. */
+ srcargv = args->argv;
+ argc = args->argc;
+ args->argv = argv = stalloc(pshchild, sizeof(char *) * (argc + 1));
+ for (i = 0; i < argc; i++)
+ argv[i] = stsavestr(pshchild, srcargv[i]);
+ argv[argc] = NULL;
+ if (args->lastarg)
+ args->lastarg = argv[argc - 1];
+
+ /* copy variable list, checking for the 'path'. */
+ sp = args->varlist.list;
+ args->varlist.list = NULL;
+ args->varlist.lastp = &args->varlist.list;
+ for (; sp; sp = sp->next) {
+ struct strlist *snew = (struct strlist *)stalloc(pshchild, sizeof(*snew));
+ char *text;
+ snew->next = NULL;
+ snew->text = text = stsavestr(pshchild, sp->text);
+
+ if (&text[5] == args->path)
+ args->path = &text[sizeof("PATH=") - 1];
+
+ *args->varlist.lastp = snew;
+ args->varlist.lastp = &snew->next;
+ }
+
+ if (args->path == pathval(pshparent))
+ args->path = pathval(pshchild);
+
+ /* back tick command should be ignored in this codepath
+ (flags != EV_BACKCMD as EV_EXIT is ORed in). */
+
+ /* If cmdentry references an internal function, we must duplicates (reference) it's nodes. */
+ if (args->cmdentry.cmdtype == CMDFUNCTION)
+ args->cmdentry.u.func = copyparsetree(pshchild, args->cmdentry.u.func); /** @todo isn't this duplicated already? */
+}
+#endif /* KASH_USE_FORKSHELL2 */
+
+/*
+ * Execute a simple command.
+ */
+
+STATIC void
+evalcommand(shinstance *psh, union node *cmd, int flags, struct backcmd *backcmd)
+{
+ struct evalcommanddoit args;
+ char **argv;
+ int argc;
+
+ union node *argp;
+ int numvars;
+ struct arglist arglist;
+ struct strlist *sp;
+ const char *path = pathval(psh);
+
+ /* First expand the arguments. */
+ TRACE((psh, "evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
+ setstackmark(psh, &args.smark);
+ psh->back_exitstatus = 0;
+
+ arglist.lastp = &arglist.list;
+ /* Expand arguments, ignoring the initial 'name=value' ones */
+ for (argp = cmd->ncmd.args, numvars = 0 ; argp ; argp = argp->narg.next, numvars++) {
+ char *p = argp->narg.text;
+ char ch = *p;
+ if (is_name(ch)) {
+ do ch = *++p;
+ while (is_in_name(ch));
+ if (ch == '=')
+ continue;
+ }
+ break;
+ }
+ for (/*continue on argp from above. */ ; argp ; argp = argp->narg.next)
+ expandarg(psh, argp, &arglist, EXP_FULL | EXP_TILDE);
+ *arglist.lastp = NULL;
+
+ args.expfnamedepth = expredir(psh, cmd->ncmd.redirect);
+
+ /* Now do the initial 'name=value' ones we skipped above */
+ args.varlist.lastp = &args.varlist.list;
+ for (argp = cmd->ncmd.args ; numvars > 0 && argp ; argp = argp->narg.next, numvars--)
+ expandarg(psh, argp, &args.varlist, EXP_VARTILDE);
+ *args.varlist.lastp = NULL;
+
+ argc = 0;
+ for (sp = arglist.list ; sp ; sp = sp->next)
+ argc++;
+ args.argc = argc;
+ args.argv = argv = stalloc(psh, sizeof (char *) * (argc + 1));
+
+ for (sp = arglist.list ; sp ; sp = sp->next) {
+ TRACE((psh, "evalcommand arg: %s\n", sp->text));
+ *argv++ = sp->text;
+ }
+ *argv = NULL;
+ args.lastarg = NULL;
+ if (iflag(psh) && psh->funcnest == 0 && argc > 0)
+ args.lastarg = argv[-1];
+ argv -= argc;
+
+ /* Print the command if xflag is set. */
+ if (xflag(psh)) {
+ char sep = 0;
+ out2str(psh, ps4val(psh));
+ for (sp = args.varlist.list ; sp ; sp = sp->next) {
+ if (sep != 0)
+ outc(sep, &psh->errout);
+ out2str(psh, sp->text);
+ sep = ' ';
+ }
+ for (sp = arglist.list ; sp ; sp = sp->next) {
+ if (sep != 0)
+ outc(sep, &psh->errout);
+ out2str(psh, sp->text);
+ sep = ' ';
+ }
+ outc('\n', &psh->errout);
+ flushout(&psh->errout);
+ }
+
+ /* Now locate the command. */
+ if (argc == 0) {
+ args.cmdentry.cmdtype = CMDSPLBLTIN;
+ args.cmdentry.u.bltin = bltincmd;
+ } else {
+ static const char PATH[] = "PATH=";
+ int cmd_flags = DO_ERR;
+
+ /*
+ * Modify the command lookup path, if a PATH= assignment
+ * is present
+ */
+ for (sp = args.varlist.list; sp; sp = sp->next)
+ if (strncmp(sp->text, PATH, sizeof(PATH) - 1) == 0)
+ path = sp->text + sizeof(PATH) - 1;
+
+ do {
+ int argsused, use_syspath;
+ find_command(psh, argv[0], &args.cmdentry, cmd_flags, path);
+ if (args.cmdentry.cmdtype == CMDUNKNOWN) {
+ psh->exitstatus = 127;
+ flushout(&psh->errout);
+ evalcommand_out(psh, flags, args.lastarg, args.expfnamedepth, &args.smark);
+ return;
+ }
+
+ /* implement the 'command' builtin here */
+ if (args.cmdentry.cmdtype != CMDBUILTIN ||
+ args.cmdentry.u.bltin != bltincmd)
+ break;
+ cmd_flags |= DO_NOFUNC;
+ argsused = parse_command_args(psh, argc, argv, &use_syspath);
+ if (argsused == 0) {
+ /* use 'type' builting to display info */
+ args.cmdentry.u.bltin = typecmd;
+ break;
+ }
+ argc -= argsused;
+ argv += argsused;
+ if (use_syspath)
+ path = syspath(psh) + 5;
+ } while (argc != 0);
+ if (args.cmdentry.cmdtype == CMDSPLBLTIN && cmd_flags & DO_NOFUNC)
+ /* posix mandates that 'command <splbltin>' act as if
+ <splbltin> was a normal builtin */
+ args.cmdentry.cmdtype = CMDBUILTIN;
+ }
+
+ /* Fork off a child process if necessary. */
+ if (cmd->ncmd.backgnd
+ || (args.cmdentry.cmdtype == CMDNORMAL && (flags & EV_EXIT) == 0)
+ || ( (flags & EV_BACKCMD) != 0
+ && ( (args.cmdentry.cmdtype != CMDBUILTIN && args.cmdentry.cmdtype != CMDSPLBLTIN)
+ || args.cmdentry.u.bltin == dotcmd
+ || args.cmdentry.u.bltin == evalcmd))) {
+ struct job *jp;
+ int mode;
+
+ INTOFF;
+ jp = makejob(psh, cmd, 1);
+ mode = cmd->ncmd.backgnd;
+ args.pip[0] = -1;
+ args.pip[1] = -1;
+ if (flags & EV_BACKCMD) {
+ mode = FORK_NOJOB;
+ if (sh_pipe(psh, args.pip) < 0)
+ error(psh, "Pipe call failed");
+ }
+
+ args.backcmd = backcmd;
+ args.flags = flags;
+ args.path = path;
+#ifdef KASH_USE_FORKSHELL2
+ forkshell2(psh, jp, cmd, mode, evalcommand_child, cmd,
+ &args, sizeof(args), evalcommand_setup_child);
+ evalcommand_parent(psh, flags, args.lastarg, args.expfnamedepth,
+ &args.smark, mode, jp, args.pip, backcmd);
+#else
+ if (forkshell(psh, jp, cmd, mode) != 0) {
+ evalcommand_parent(psh, flags, args.lastarg, args.expfnamedepth,
+ &args.smark, mode, jp, args.pip, backcmd);
+ return; /* at end of routine */
+ }
+ evalcommand_child(psh, cmd, &args);
+#endif
+ } else {
+ args.backcmd = backcmd;
+ args.flags = flags;
+ args.path = path;
+ evalcommand_doit(psh, cmd, &args);
+ }
+}
+
+
+/*
+ * Search for a command. This is called before we fork so that the
+ * location of the command will be available in the parent as well as
+ * the child. The check for "goodname" is an overly conservative
+ * check that the name will not be subject to expansion.
+ */
+
+STATIC void
+prehash(shinstance *psh, union node *n)
+{
+ struct cmdentry entry;
+
+ if (n->type == NCMD && n->ncmd.args)
+ if (goodname(n->ncmd.args->narg.text))
+ find_command(psh, n->ncmd.args->narg.text, &entry, 0,
+ pathval(psh));
+}
+
+
+
+/*
+ * Builtin commands. Builtin commands whose functions are closely
+ * tied to evaluation are implemented here.
+ */
+
+/*
+ * No command given.
+ */
+
+int
+bltincmd(shinstance *psh, int argc, char **argv)
+{
+ /*
+ * Preserve psh->exitstatus of a previous possible redirection
+ * as POSIX mandates
+ */
+ return psh->back_exitstatus;
+}
+
+
+/*
+ * Handle break and continue commands. Break, continue, and return are
+ * all handled by setting the psh->evalskip flag. The evaluation routines
+ * above all check this flag, and if it is set they start skipping
+ * commands rather than executing them. The variable skipcount is
+ * the number of loops to break/continue, or the number of function
+ * levels to return. (The latter is always 1.) It should probably
+ * be an error to break out of more loops than exist, but it isn't
+ * in the standard shell so we don't make it one here.
+ */
+
+int
+breakcmd(shinstance *psh, int argc, char **argv)
+{
+ int n = argc > 1 ? number(psh, argv[1]) : 1;
+
+ if (n > psh->loopnest)
+ n = psh->loopnest;
+ if (n > 0) {
+ psh->evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
+ psh->skipcount = n;
+ }
+ return 0;
+}
+
+
+/*
+ * The return command.
+ */
+
+int
+returncmd(shinstance *psh, int argc, char **argv)
+{
+#if 0
+ int ret = argc > 1 ? number(psh, argv[1]) : psh->exitstatus;
+#else
+ int ret;
+ if (argc > 1) {
+ /* make return -1 and VSC lite work ... */
+ if (argv[1][0] != '-' || !is_number(&argv[1][1]))
+ ret = number(psh, argv[1]);
+ else
+ ret = -number(psh, &argv[1][1]) & 255; /* take the bash approach */
+ } else {
+ ret = psh->exitstatus;
+ }
+#endif
+
+ if (psh->funcnest) {
+ psh->evalskip = SKIPFUNC;
+ psh->skipcount = 1;
+ return ret;
+ }
+ else {
+ /* Do what ksh does; skip the rest of the file */
+ psh->evalskip = SKIPFILE;
+ psh->skipcount = 1;
+ return ret;
+ }
+}
+
+
+int
+falsecmd(shinstance *psh, int argc, char **argv)
+{
+ return 1;
+}
+
+
+int
+truecmd(shinstance *psh, int argc, char **argv)
+{
+ return 0;
+}
+
+
+int
+execcmd(shinstance *psh, int argc, char **argv)
+{
+ if (argc > 1) {
+ struct strlist *sp;
+
+ iflag(psh) = 0; /* exit on error */
+ mflag(psh) = 0;
+ optschanged(psh);
+ for (sp = psh->cmdenviron; sp; sp = sp->next)
+ setvareq(psh, sp->text, VEXPORT|VSTACK);
+ shellexec(psh, argv + 1, environment(psh), pathval(psh), 0, -1);
+ }
+ return 0;
+}
+
+static int
+conv_time(clock_t ticks, char *seconds, size_t l)
+{
+ static clock_t tpm = 0;
+ clock_t mins;
+ size_t i;
+
+ if (!tpm)
+ tpm = /*sysconf(_SC_CLK_TCK)*/sh_sysconf_clk_tck() * 60;
+
+ mins = ticks / tpm;
+#ifdef _MSC_VER
+ {
+ char tmp[64];
+ sprintf(tmp, "%.4f", (ticks - mins * tpm) * 60.0 / tpm);
+ strlcpy(seconds, tmp, l);
+ }
+#else
+ snprintf(seconds, l, "%.4f", (ticks - mins * tpm) * 60.0 / tpm );
+#endif
+
+ if (seconds[0] == '6' && seconds[1] == '0') {
+ /* 59.99995 got rounded up... */
+ mins++;
+ strlcpy(seconds, "0.0", l);
+ return mins;
+ }
+
+ /* suppress trailing zeros */
+ i = strlen(seconds) - 1;
+ for (; seconds[i] == '0' && seconds[i - 1] != '.'; i--)
+ seconds[i] = 0;
+ return mins;
+}
+
+int
+timescmd(shinstance *psh, int argc, char **argv)
+{
+ shtms tms;
+ int u, s, cu, cs;
+ char us[8], ss[8], cus[8], css[8];
+
+ nextopt(psh, "");
+
+ sh_times(psh, &tms);
+
+ u = conv_time(tms.tms_utime, us, sizeof(us));
+ s = conv_time(tms.tms_stime, ss, sizeof(ss));
+ cu = conv_time(tms.tms_cutime, cus, sizeof(cus));
+ cs = conv_time(tms.tms_cstime, css, sizeof(css));
+
+ outfmt(psh->out1, "%dm%ss %dm%ss\n%dm%ss %dm%ss\n",
+ u, us, s, ss, cu, cus, cs, css);
+
+ return 0;
+}
diff --git a/src/kash/eval.h b/src/kash/eval.h
new file mode 100644
index 0000000..c3f4549
--- /dev/null
+++ b/src/kash/eval.h
@@ -0,0 +1,64 @@
+/* $NetBSD: eval.h,v 1.14 2003/08/07 09:05:31 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)eval.h 8.2 (Berkeley) 5/4/95
+ */
+
+/*extern char *commandname;*/ /* currently executing command */
+/*extern int exitstatus;*/ /* exit status of last command */
+/*extern int back_exitstatus;*/ /* exit status of backquoted command */
+/*extern struct strlist *cmdenviron;*/ /* environment for builtin command */
+
+
+struct backcmd { /* result of evalbackcmd */
+ int fd; /* file descriptor to read from */
+ char *buf; /* buffer */
+ int nleft; /* number of chars in buffer */
+ struct job *jp; /* job structure for command */
+};
+
+void evalstring(struct shinstance *, char *, int);
+union node; /* BLETCH for ansi C */
+void evaltree(struct shinstance *, union node *, int);
+void evalbackcmd(struct shinstance *, union node *, struct backcmd *);
+
+/* in_function returns nonzero if we are currently evaluating a function */
+#define in_function(psh) (psh)->funcnest
+/*extern int funcnest;
+extern int evalskip;*/
+
+/* reasons for skipping commands (see comment on breakcmd routine) */
+#define SKIPBREAK 1
+#define SKIPCONT 2
+#define SKIPFUNC 3
+#define SKIPFILE 4
diff --git a/src/kash/exec.c b/src/kash/exec.c
new file mode 100644
index 0000000..ec6d629
--- /dev/null
+++ b/src/kash/exec.c
@@ -0,0 +1,1352 @@
+/* $NetBSD: exec.c,v 1.37 2003/08/07 09:05:31 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)exec.c 8.4 (Berkeley) 6/8/95";
+#else
+__RCSID("$NetBSD: exec.c,v 1.37 2003/08/07 09:05:31 agc Exp $");
+#endif /* not lint */
+#endif
+
+#include <sys/types.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+
+/*
+ * When commands are first encountered, they are entered in a hash table.
+ * This ensures that a full path search will not have to be done for them
+ * on each invocation.
+ *
+ * We should investigate converting to a linear search, even though that
+ * would make the command name "hash" a misnomer.
+ */
+
+#include "shell.h"
+#include "main.h"
+#include "nodes.h"
+#include "parser.h"
+#include "redir.h"
+#include "eval.h"
+#include "exec.h"
+#include "builtins.h"
+#include "var.h"
+#include "options.h"
+#include "input.h"
+#include "output.h"
+#include "syntax.h"
+#include "memalloc.h"
+#include "error.h"
+#include "init.h"
+#include "mystring.h"
+#include "show.h"
+#include "jobs.h"
+#include "alias.h"
+#ifdef __INNOTEK_LIBC__
+#include <InnoTekLIBC/backend.h>
+#endif
+#include "shinstance.h"
+
+//#define CMDTABLESIZE 31 /* should be prime */
+//#define ARB 1 /* actual size determined at run time */
+//
+//
+//
+//struct tblentry {
+// struct tblentry *next; /* next entry in hash chain */
+// union param param; /* definition of builtin function */
+// short cmdtype; /* index identifying command */
+// char rehash; /* if set, cd done since entry created */
+// char cmdname[ARB]; /* name of command */
+//};
+//
+//
+//STATIC struct tblentry *cmdtable[CMDTABLESIZE];
+//STATIC int builtinloc = -1; /* index in path of %builtin, or -1 */
+//int exerrno = 0; /* Last exec error */
+#ifdef PC_EXE_EXTS
+STATIC const char * const g_exe_suffixes[] = { "", ".exe", ".cmd", ".btm", ".com" };
+#endif
+
+
+STATIC void tryexec(shinstance *, char *, char **, char **, int);
+STATIC void execinterp(shinstance *, char **, char **);
+STATIC void printentry(shinstance *, struct tblentry *, int);
+STATIC void clearcmdentry(shinstance *, int);
+STATIC struct tblentry *cmdlookup(shinstance *, const char *, int);
+STATIC void delete_cmd_entry(shinstance *);
+#ifdef PC_EXE_EXTS
+STATIC int stat_pc_exec_exts(shinstance *, char *fullname, int has_ext, int *suffixp);
+#endif
+
+
+extern char *const parsekwd[];
+
+#ifndef SH_FORKED_MODE
+void
+subshellinitexec(shinstance *psh, shinstance *inherit)
+{
+ unsigned i;
+ for (i = 0; i < K_ELEMENTS(inherit->cmdtable); i++) {
+ struct tblentry const *csrc = inherit->cmdtable[i];
+ if (!csrc) {
+ } else {
+ struct tblentry **ppdst = &psh->cmdtable[i];
+ do
+ {
+ size_t const namesize = strlen(csrc->cmdname) + 1;
+ size_t const entrysize = offsetof(struct tblentry, cmdname) + namesize;
+ struct tblentry *dst = (struct tblentry *)ckmalloc(psh, entrysize);
+ memcpy(dst->cmdname, csrc->cmdname, namesize);
+ dst->rehash = csrc->rehash;
+ dst->cmdtype = csrc->cmdtype;
+
+ dst->param.func = NULL;
+ switch (csrc->cmdtype) {
+ case CMDUNKNOWN:
+ case CMDNORMAL:
+ dst->param.n.index = csrc->param.n.index;
+ dst->param.n.suffix = csrc->param.n.suffix;
+ break;
+ case CMDFUNCTION:
+ dst->param.func = copyfunc(psh, csrc->param.func); /** @todo optimize function allocations */
+ break;
+ case CMDBUILTIN:
+ case CMDSPLBLTIN:
+ dst->param.bltin = csrc->param.bltin;
+ break;
+ }
+
+ *ppdst = dst;
+ ppdst = &dst->next;
+
+ csrc = csrc->next;
+ } while (csrc);
+ *ppdst = NULL;
+ }
+ }
+
+ psh->builtinloc = inherit->builtinloc;
+}
+#endif /* SH_FORKED_MODE */
+
+
+/*
+ * Check if 'path' is an absolute (starts with root) path or not.
+ */
+K_INLINE int isabspath(const char *path)
+{
+#if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+ if (path[0] == '/' || path[0] == '\\') {
+ if ( (path[1] == '/' || path[1] == '\\')
+ && (path[2] != '/' && path[2] != '\\' && path[2]))
+ return 1;
+ } else if (path[0] && path[1] == ':' && (path[2] == '\\' || path[2] == '/') && isalpha(path[0])) {
+ return 1;
+ }
+ return 0;
+#else
+ return path[0] == '/';
+#endif
+}
+
+/*
+ * Checks if the filename include a path or not.
+ */
+K_INLINE int haspath(const char *name)
+{
+#if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+ return strchr(name, '/') != NULL
+ || strchr(name, '\\') != NULL
+ || (name[0] && name[1] == ':');
+#else
+ return strchr(name, '/') != NULL;
+#endif
+}
+
+
+/*
+ * Exec a program. Never returns. If you change this routine, you may
+ * have to change the find_command routine as well.
+ */
+
+SH_NORETURN_1 void
+shellexec(shinstance *psh, char **argv, char **envp, const char *path, int idx, int suffix)
+{
+ char *cmdname;
+ int e;
+ const char *argv0 = argv[0];
+ int argv0len = (int)strlen(argv0);
+ char kmkcmd[48];
+#ifdef PC_EXE_EXTS
+ int has_ext = argv0len - 4;
+ has_ext = has_ext > 0
+ && argv0[has_ext] == '.'
+ /* use strstr and upper/lower permuated extensions to avoid multiple strcasecmp calls. */
+ && strstr("exe;" "Exe;" "EXe;" "EXE;" "ExE;" "eXe;" "eXE;" "exE;"
+ "cmd;" "Cmd;" "CMd;" "CMD;" "CmD;" "cMd;" "cMD;" "cmD;"
+ "com;" "Com;" "COm;" "COM;" "CoM;" "cOm;" "cOM;" "coM;"
+ "bat;" "Bat;" "BAt;" "BAT;" "BaT;" "bAt;" "bAT;" "baT;"
+ "btm;" "Btm;" "BTm;" "BTM;" "BtM;" "bTm;" "bTM;" "btM;",
+ argv0 + has_ext + 1)
+ != NULL;
+#else
+ const int has_ext = 1;
+#endif
+ TRACE((psh, "shellexec: argv[0]=%s idx=%d suffix=%d\n", argv0, idx, suffix));
+ if (haspath(argv0)) {
+#ifdef PC_EXE_EXTS
+ if (!has_ext && suffix && (unsigned)suffix < K_ELEMENTS(g_exe_suffixes)) {
+ size_t sufflen = strlen(g_exe_suffixes[suffix]);
+ cmdname = stalloc(psh, argv0len + sufflen + 1);
+ memcpy(cmdname, argv0, argv0len);
+ memcpy(cmdname + argv0len, g_exe_suffixes[suffix], sufflen + 1);
+ tryexec(psh, cmdname, argv, envp, 1);
+ } else
+#endif
+ {
+ cmdname = stalloc(psh, argv0len + 5);
+ memcpy(cmdname, argv0, argv0len + 1);
+ tryexec(psh, cmdname, argv, envp, has_ext);
+ }
+ TRACE((psh, "shellexec: cmdname=%s\n", cmdname));
+ stunalloc(psh, cmdname);
+ e = errno;
+ } else {
+ /* Before we search the PATH, transform kmk_builtin_% to kmk_% so we don't
+ need to be too careful mixing internal and external kmk commands. */
+ if ( argv0len > 12
+ && argv0len < 42
+ && strncmp(argv0, "kmk_builtin_", 12) == 0
+ && strpbrk(argv0 + 12, "./\\-:;<>") == NULL) {
+ memcpy(kmkcmd, "kmk_", 4);
+ memcpy(&kmkcmd[4], argv0 + 12, argv0len + 1 - 8);
+ TRACE((psh, "shellexec: dropped '_builtin' from %s to %s\n", argv0, kmkcmd));
+ argv0len -= 8;
+ argv0 = kmkcmd;
+ }
+
+ e = ENOENT;
+ while ((cmdname = padvance(psh, &path, argv0)) != NULL) {
+ if (--idx < 0 && psh->pathopt == NULL) {
+#ifdef PC_EXE_EXTS
+ if (!has_ext && idx == -1 && suffix && (unsigned)suffix < K_ELEMENTS(g_exe_suffixes)) {
+ strcat(cmdname, g_exe_suffixes[suffix]);
+ tryexec(psh, cmdname, argv, envp, 1);
+ } else
+#endif
+ tryexec(psh, cmdname, argv, envp, has_ext);
+ if (errno != ENOENT && errno != ENOTDIR)
+ e = errno;
+ }
+ stunalloc(psh, cmdname);
+ }
+ }
+
+ /* Map to POSIX errors */
+ switch (e) {
+ case EACCES:
+ psh->exerrno = 126;
+ break;
+ case ENOENT:
+ psh->exerrno = 127;
+ break;
+ default:
+ psh->exerrno = 2;
+ break;
+ }
+ TRACE((psh, "shellexec failed for '%s', errno %d, suppressint %d\n",
+ argv[0], e, psh->suppressint ));
+ exerror(psh, EXEXEC, "%s: %s", argv[0], errmsg(psh, e, E_EXEC));
+ /* NOTREACHED */
+}
+
+
+STATIC void
+tryexec(shinstance *psh, char *cmd, char **argv, char **envp, int has_ext)
+{
+ int e;
+#ifdef EXEC_HASH_BANG_SCRIPT
+ char *p;
+#endif
+#ifdef PC_EXE_EXTS
+ /* exploit the effect of stat_pc_exec_exts which adds the
+ * correct extentions to the file. */
+ if (!has_ext) {
+ int suffix;
+ stat_pc_exec_exts(psh, cmd, 0, &suffix);
+ }
+#endif
+#if defined(__INNOTEK_LIBC__) && defined(EXEC_HASH_BANG_SCRIPT)
+ __libc_Back_gfProcessHandleHashBangScripts = 0;
+#endif
+
+#ifdef SYSV
+ do {
+ sh_execve(psh, cmd, argv, envp);
+ } while (errno == EINTR);
+#else
+ sh_execve(psh, cmd, (const char * const*)argv, (const char * const*)envp);
+#endif
+ e = errno;
+ if (e == ENOEXEC) {
+ initshellproc(psh);
+ setinputfile(psh, cmd, 0);
+ if (psh->commandnamemalloc) {
+ sh_free(psh, psh->commandname);
+ psh->commandnamemalloc = 0;
+ }
+ if (psh->arg0malloc)
+ sh_free(psh, psh->arg0);
+ psh->commandname = psh->arg0 = savestr(psh, argv[0]);
+ psh->arg0malloc = 1;
+#ifdef EXEC_HASH_BANG_SCRIPT
+ pgetc(psh); pungetc(psh); /* fill up input buffer */
+ p = psh->parsenextc;
+ if (psh->parsenleft > 2 && p[0] == '#' && p[1] == '!') {
+ argv[0] = cmd;
+ execinterp(psh, argv, envp);
+ }
+#endif
+ setparam(psh, argv + 1);
+ exraise(psh, EXSHELLPROC);
+ }
+ errno = e;
+}
+
+#ifdef EXEC_HASH_BANG_SCRIPT
+
+/*
+ * Checks if NAME is the (base) name of the shell executable or something
+ * very similar.
+ */
+STATIC int
+is_shell_exe_name(const char *name)
+{
+ return equal(name, "kmk_ash")
+ || equal(name, "kmk_sh")
+ || equal(name, "kash")
+ || equal(name, "sh");
+}
+
+/*
+ * Execute an interpreter introduced by "#!", for systems where this
+ * feature has not been built into the kernel. If the interpreter is
+ * the shell, return (effectively ignoring the "#!"). If the execution
+ * of the interpreter fails, exit.
+ *
+ * This code peeks inside the input buffer in order to avoid actually
+ * reading any input. It would benefit from a rewrite.
+ */
+
+#define NEWARGS 16
+
+STATIC void
+execinterp(shinstance *psh, char **argv, char **envp)
+{
+ int n;
+ char *inp;
+ char *outp;
+ char c;
+ char *p;
+ char **ap;
+ char *newargs[NEWARGS];
+ intptr_t i;
+ char **ap2;
+ char **new;
+
+ /* Split the string into arguments. */
+ n = psh->parsenleft - 2;
+ inp = psh->parsenextc + 2;
+ ap = newargs;
+ for (;;) {
+ while (--n >= 0 && (*inp == ' ' || *inp == '\t'))
+ inp++;
+ if (n < 0)
+ goto bad;
+ if ((c = *inp++) == '\n')
+ break;
+ if (ap == &newargs[NEWARGS])
+bad: error(psh, "Bad #! line");
+ STARTSTACKSTR(psh, outp);
+ do {
+ STPUTC(psh, c, outp);
+ } while (--n >= 0 && (c = *inp++) != ' ' && c != '\t' && c != '\n');
+ STPUTC(psh, '\0', outp);
+ n++, inp--;
+ *ap++ = grabstackstr(psh, outp);
+ }
+
+ /* /usr/bin/env emulation, very common with kash/kmk_ash. */
+ i = ap - newargs;
+ if (i > 1 && equal(newargs[0], "/usr/bin/env")) {
+ if ( !strchr(newargs[1], '=')
+ && newargs[1][0] != '-') {
+ /* shellexec below searches the PATH for us, so just
+ drop /usr/bin/env. */
+ TRACE((psh, "hash bang /usr/bin/env utility, dropping /usr/bin/env\n"));
+ ap--;
+ i--;
+ for (n = 0; n < i; n++)
+ newargs[n] = newargs[n + 1];
+ } /* else: complicated invocation */
+ }
+
+ /* If the interpreter is the shell or a similar shell, there is
+ no need to exec. */
+ if (i == 1) {
+ p = strrchr(newargs[0], '/');
+ if (!p)
+ p = newargs[0];
+ if (is_shell_exe_name(p)) {
+ TRACE((psh, "hash bang self\n"));
+ return;
+ }
+ }
+
+ /* Combine the two argument lists and exec. */
+ i = (char *)ap - (char *)newargs; /* size in bytes */
+ if (i == 0)
+ error(psh, "Bad #! line");
+ for (ap2 = argv ; *ap2++ != NULL ; );
+ new = ckmalloc(psh, i + ((char *)ap2 - (char *)argv));
+ ap = newargs, ap2 = new;
+ while ((i -= sizeof (char **)) >= 0)
+ *ap2++ = *ap++;
+ ap = argv;
+ while ((*ap2++ = *ap++))
+ /* nothing*/;
+ TRACE((psh, "hash bang '%s'\n", new[0]));
+ shellexec(psh, new, envp, pathval(psh), 0, -1);
+ /* NOTREACHED */
+}
+
+#endif /* EXEC_HASH_BANG_SCRIPT */
+
+
+/*
+ * Do a path search. The variable path (passed by reference) should be
+ * set to the start of the path before the first call; padvance will update
+ * this value as it proceeds. Successive calls to padvance will return
+ * the possible path expansions in sequence. If an option (indicated by
+ * a percent sign) appears in the path entry then the global variable
+ * psh->pathopt will be set to point to it; otherwise psh->pathopt will be set to
+ * NULL.
+ */
+
+//const char *pathopt;
+
+char *
+padvance(shinstance *psh, const char **path, const char *name)
+{
+ const char *p;
+ char *q;
+ const char *start;
+ int len;
+
+ if (*path == NULL)
+ return NULL;
+ start = *path;
+#ifdef PC_PATH_SEP
+ for (p = start ; *p && *p != ';' && *p != '%' ; p++);
+#else
+ for (p = start ; *p && *p != ':' && *p != '%' ; p++);
+#endif
+ len = (int)(p - start + strlen(name) + 2); /* "2" is for '/' and '\0' */
+#ifdef PC_EXE_EXTS
+ len += 4; /* "4" is for .exe/.com/.cmd/.bat/.btm */
+#endif
+ while (stackblocksize(psh) < len)
+ growstackblock(psh);
+ q = stackblock(psh);
+ if (p != start) {
+ memcpy(q, start, p - start);
+ q += p - start;
+ *q++ = '/';
+ }
+ strcpy(q, name);
+ psh->pathopt = NULL;
+ if (*p == '%') {
+ psh->pathopt = ++p;
+#ifdef PC_PATH_SEP
+ while (*p && *p != ';') p++;
+#else
+ while (*p && *p != ':') p++;
+#endif
+ }
+#ifdef PC_PATH_SEP
+ if (*p == ';')
+#else
+ if (*p == ':')
+#endif
+ *path = p + 1;
+ else
+ *path = NULL;
+ return stalloc(psh, len);
+}
+
+
+#ifdef PC_EXE_EXTS
+STATIC int stat_pc_exec_exts(shinstance *psh, char *fullname, int has_ext, int *suffixp)
+{
+ int isreg;
+
+ /* skip the SYSV crap */
+ if ((isreg = shfile_stat_isreg(&psh->fdtab, fullname)) >= 1) {
+ *suffixp = 0;
+ return isreg;
+ }
+ if (!has_ext && errno == ENOENT)
+ {
+ /* Ignore non-regular files here. */
+ char *psz = strchr(fullname, '\0');
+ int i;
+ for (i = 1 /*first entry is empty*/; i < K_ELEMENTS(g_exe_suffixes); i++) {
+ strcpy(psz, g_exe_suffixes[i]);
+ if ((isreg = shfile_stat_isreg(&psh->fdtab, fullname)) >= 1) {
+ *suffixp = i;
+ return isreg;
+ }
+ if (isreg < 0 && errno != ENOENT && errno != ENOTDIR)
+ break;
+ }
+ }
+ *suffixp = -1;
+ return isreg;
+}
+#endif /* PC_EXE_EXTS */
+
+
+
+/*** Command hashing code ***/
+
+
+int
+hashcmd(shinstance *psh, int argc, char **argv)
+{
+ struct tblentry **pp;
+ struct tblentry *cmdp;
+ int c;
+ int verbose;
+ struct cmdentry entry;
+ char *name;
+
+ verbose = 0;
+ while ((c = nextopt(psh, "rv")) != '\0') {
+ if (c == 'r') {
+ clearcmdentry(psh, 0);
+ } else if (c == 'v') {
+ verbose++;
+ }
+ }
+ if (*psh->argptr == NULL) {
+ for (pp = psh->cmdtable ; pp < &psh->cmdtable[CMDTABLESIZE] ; pp++) {
+ for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+ if (verbose || cmdp->cmdtype == CMDNORMAL)
+ printentry(psh, cmdp, verbose);
+ }
+ }
+ return 0;
+ }
+ while ((name = *psh->argptr) != NULL) {
+ if ((cmdp = cmdlookup(psh, name, 0)) != NULL
+ && (cmdp->cmdtype == CMDNORMAL
+ || (cmdp->cmdtype == CMDBUILTIN && psh->builtinloc >= 0)))
+ delete_cmd_entry(psh);
+ find_command(psh, name, &entry, DO_ERR, pathval(psh));
+ if (verbose) {
+ if (entry.cmdtype != CMDUNKNOWN) { /* if no error msg */
+ cmdp = cmdlookup(psh, name, 0);
+ printentry(psh, cmdp, verbose);
+ }
+ output_flushall(psh);
+ }
+ psh->argptr++;
+ }
+ return 0;
+}
+
+
+STATIC void
+printentry(shinstance *psh, struct tblentry *cmdp, int verbose)
+{
+ int idx;
+ const char *path;
+ char *name;
+
+ switch (cmdp->cmdtype) {
+ case CMDNORMAL:
+ idx = cmdp->param.n.index;
+ path = pathval(psh);
+ do {
+ name = padvance(psh, &path, cmdp->cmdname);
+ stunalloc(psh, name);
+ } while (--idx >= 0);
+ out1str(psh, name);
+#ifdef PC_EXE_EXTS
+ if ((unsigned)cmdp->param.n.suffix < K_ELEMENTS(g_exe_suffixes))
+ out1str(psh, g_exe_suffixes[cmdp->param.n.suffix]);
+#endif
+ break;
+ case CMDSPLBLTIN:
+ out1fmt(psh, "special builtin %s", cmdp->cmdname);
+ break;
+ case CMDBUILTIN:
+ out1fmt(psh, "builtin %s", cmdp->cmdname);
+ break;
+ case CMDFUNCTION:
+ out1fmt(psh, "function %s", cmdp->cmdname);
+ if (verbose) {
+ struct procstat ps;
+ INTOFF;
+ commandtext(psh, &ps, cmdp->param.func);
+ INTON;
+ out1str(psh, "() { ");
+ out1str(psh, ps.cmd);
+ out1str(psh, "; }");
+ }
+ break;
+ default:
+ error(psh, "internal error: %s cmdtype %d", cmdp->cmdname, cmdp->cmdtype);
+ }
+ if (cmdp->rehash)
+ out1c(psh, '*');
+ out1c(psh, '\n');
+}
+
+
+
+/*
+ * Resolve a command name. If you change this routine, you may have to
+ * change the shellexec routine as well.
+ */
+
+void
+find_command(shinstance *psh, char *name, struct cmdentry *entry, int act, const char *path)
+{
+ struct tblentry *cmdp, loc_cmd;
+ int idx;
+ int prev;
+ char *fullname;
+ int e;
+ int (*bltin)(shinstance*,int,char **);
+ int argv0len = (int)strlen(name);
+ char kmkcmd[48];
+#ifdef PC_EXE_EXTS
+ int has_ext = argv0len - 4;
+ has_ext = has_ext > 0
+ && name[has_ext] == '.'
+ /* use strstr and upper/lower permuated extensions to avoid multiple strcasecmp calls. */
+ && strstr("exe;" "Exe;" "EXe;" "EXE;" "ExE;" "eXe;" "eXE;" "exE;"
+ "cmd;" "Cmd;" "CMd;" "CMD;" "CmD;" "cMd;" "cMD;" "cmD;"
+ "com;" "Com;" "COm;" "COM;" "CoM;" "cOm;" "cOM;" "coM;"
+ "bat;" "Bat;" "BAt;" "BAT;" "BaT;" "bAt;" "bAT;" "baT;"
+ "btm;" "Btm;" "BTm;" "BTM;" "BtM;" "bTm;" "bTM;" "btM;",
+ name + has_ext + 1)
+ != NULL;
+#endif
+
+ /* If name contains a slash, don't use PATH or hash table */
+ if (haspath(name)) {
+ if (act & DO_ABS) {
+ while (shfile_stat_exists(&psh->fdtab, name) < 0) {
+#ifdef SYSV
+ if (errno == EINTR)
+ continue;
+#endif
+ if (errno != ENOENT && errno != ENOTDIR)
+ e = errno;
+ entry->cmdtype = CMDUNKNOWN;
+ entry->u.n.index = -1;
+ entry->u.n.suffix = -1;
+ return;
+ }
+ entry->cmdtype = CMDNORMAL;
+ entry->u.n.index = -1;
+ entry->u.n.suffix = -1;
+ return;
+ }
+ entry->cmdtype = CMDNORMAL;
+ entry->u.n.index = 0;
+ entry->u.n.suffix = 0;
+ return;
+ }
+
+ if (path != pathval(psh))
+ act |= DO_ALTPATH;
+
+ if (act & DO_ALTPATH && strstr(path, "%builtin") != NULL)
+ act |= DO_ALTBLTIN;
+
+ /* If name is in the table, check answer will be ok */
+ if ((cmdp = cmdlookup(psh, name, 0)) != NULL) {
+ do {
+ switch (cmdp->cmdtype) {
+ case CMDNORMAL:
+ if (act & DO_ALTPATH) {
+ cmdp = NULL;
+ continue;
+ }
+ break;
+ case CMDFUNCTION:
+ if (act & DO_NOFUNC) {
+ cmdp = NULL;
+ continue;
+ }
+ break;
+ case CMDBUILTIN:
+ if ((act & DO_ALTBLTIN) || psh->builtinloc >= 0) {
+ cmdp = NULL;
+ continue;
+ }
+ break;
+ }
+ /* if not invalidated by cd, we're done */
+ if (cmdp->rehash == 0)
+ goto success;
+ } while (0);
+ }
+
+ /* If %builtin not in path, check for builtin next */
+ if ((act & DO_ALTPATH ? !(act & DO_ALTBLTIN) : psh->builtinloc < 0) &&
+ (bltin = find_builtin(psh, name)) != 0)
+ goto builtin_success;
+
+ /* We have to search path. */
+ prev = -1; /* where to start */
+ if (cmdp) { /* doing a rehash */
+ if (cmdp->cmdtype == CMDBUILTIN)
+ prev = psh->builtinloc;
+ else
+ prev = cmdp->param.n.index;
+ }
+
+ /* Before we search the PATH, transform kmk_builtin_% to kmk_% so we don't
+ need to be too careful mixing internal and external kmk command. */
+ if ( argv0len > 12
+ && argv0len < (int)sizeof(kmkcmd)
+ && strncmp(name, "kmk_builtin_", 12) == 0
+ && strpbrk(name + 12, "./\\-:;<>") == NULL) {
+ memcpy(kmkcmd, "kmk_", 4);
+ memcpy(&kmkcmd[4], name + 12, argv0len + 1 - 8);
+ TRACE((psh, "find_command: dropped '_builtin' from %s to %s\n", name, kmkcmd));
+ argv0len -= 8;
+ name = kmkcmd;
+ }
+
+ e = ENOENT;
+ idx = -1;
+loop:
+ while ((fullname = padvance(psh, &path, name)) != NULL) {
+#ifdef PC_EXE_EXTS
+ int suffix;
+#endif
+ int isreg;
+ stunalloc(psh, fullname);
+ idx++;
+ if (psh->pathopt) {
+ if (prefix("builtin", psh->pathopt)) {
+ if ((bltin = find_builtin(psh, name)) == 0)
+ goto loop;
+ goto builtin_success;
+ } else if (prefix("func", psh->pathopt)) {
+ /* handled below */
+ } else {
+ /* ignore unimplemented options */
+ goto loop;
+ }
+ }
+ /* if rehash, don't redo absolute path names */
+ if (idx <= prev && isabspath(fullname)) {
+ if (idx < prev)
+ goto loop;
+ TRACE((psh, "searchexec \"%s\": no change\n", name));
+ goto success;
+ }
+#ifdef PC_EXE_EXTS
+ while ((isreg = stat_pc_exec_exts(psh, fullname, has_ext, &suffix)) < 0) {
+#else
+ while ((isreg = shfile_stat_isreg(&psh->fdtab, fullname)) < 0) {
+#endif
+#ifdef SYSV
+ if (errno == EINTR)
+ continue;
+#endif
+ if (errno != ENOENT && errno != ENOTDIR)
+ e = errno;
+
+ goto loop;
+ }
+ e = EACCES; /* if we fail, this will be the error */
+ if (isreg < 1)
+ goto loop;
+ if (psh->pathopt) { /* this is a %func directory */
+ if (act & DO_NOFUNC)
+ goto loop;
+ stalloc(psh, strlen(fullname) + 1);
+ readcmdfile(psh, fullname);
+ if ((cmdp = cmdlookup(psh, name, 0)) == NULL ||
+ cmdp->cmdtype != CMDFUNCTION)
+ error(psh, "%s not defined in %s", name, fullname);
+ stunalloc(psh, fullname);
+ goto success;
+ }
+#ifdef notdef
+ /* XXX this code stops root executing stuff, and is buggy
+ if you need a group from the group list. */
+ if (statb.st_uid == sh_geteuid(psh)) {
+ if ((statb.st_mode & 0100) == 0)
+ goto loop;
+ } else if (statb.st_gid == sh_getegid(psh)) {
+ if ((statb.st_mode & 010) == 0)
+ goto loop;
+ } else {
+ if ((statb.st_mode & 01) == 0)
+ goto loop;
+ }
+#endif
+ TRACE((psh, "searchexec \"%s\" returns \"%s\"\n", name, fullname));
+ INTOFF;
+ if (act & DO_ALTPATH) {
+ stalloc(psh, strlen(fullname) + 1);
+ cmdp = &loc_cmd;
+ } else
+ cmdp = cmdlookup(psh, name, 1);
+ cmdp->cmdtype = CMDNORMAL;
+ cmdp->param.n.index = idx;
+#ifdef PC_EXE_EXTS
+ cmdp->param.n.suffix = suffix;
+#else
+ cmdp->param.n.suffix = 0;
+#endif
+ INTON;
+ goto success;
+ }
+
+ /* We failed. If there was an entry for this command, delete it */
+ if (cmdp)
+ delete_cmd_entry(psh);
+ if (act & DO_ERR)
+ outfmt(psh->out2, "%s: %s\n", name, errmsg(psh, e, E_EXEC));
+ entry->cmdtype = CMDUNKNOWN;
+ return;
+
+builtin_success:
+ INTOFF;
+ if (act & DO_ALTPATH)
+ cmdp = &loc_cmd;
+ else
+ cmdp = cmdlookup(psh, name, 1);
+ if (cmdp->cmdtype == CMDFUNCTION)
+ /* DO_NOFUNC must have been set */
+ cmdp = &loc_cmd;
+ cmdp->cmdtype = CMDBUILTIN;
+ cmdp->param.bltin = bltin;
+ INTON;
+success:
+ cmdp->rehash = 0;
+ entry->cmdtype = cmdp->cmdtype;
+ entry->u = cmdp->param;
+}
+
+
+
+/*
+ * Search the table of builtin commands.
+ */
+
+int
+(*find_builtin(shinstance *psh, char *name))(shinstance *psh, int, char **)
+{
+ const struct builtincmd *bp;
+
+ for (bp = builtincmd ; bp->name ; bp++) {
+ if (*bp->name == *name && equal(bp->name, name))
+ return bp->builtin;
+ }
+ return 0;
+}
+
+int
+(*find_splbltin(shinstance *psh, char *name))(shinstance *psh, int, char **)
+{
+ const struct builtincmd *bp;
+
+ for (bp = splbltincmd ; bp->name ; bp++) {
+ if (*bp->name == *name && equal(bp->name, name))
+ return bp->builtin;
+ }
+ return 0;
+}
+
+/*
+ * At shell startup put special builtins into hash table.
+ * ensures they are executed first (see posix).
+ * We stop functions being added with the same name
+ * (as they are impossible to call)
+ */
+
+void
+hash_special_builtins(shinstance *psh)
+{
+ const struct builtincmd *bp;
+ struct tblentry *cmdp;
+
+ for (bp = splbltincmd ; bp->name ; bp++) {
+ cmdp = cmdlookup(psh, bp->name, 1);
+ cmdp->cmdtype = CMDSPLBLTIN;
+ cmdp->param.bltin = bp->builtin;
+ }
+}
+
+
+
+/*
+ * Called when a cd is done. Marks all commands so the next time they
+ * are executed they will be rehashed.
+ */
+
+void
+hashcd(shinstance *psh)
+{
+ struct tblentry **pp;
+ struct tblentry *cmdp;
+
+ for (pp = psh->cmdtable ; pp < &psh->cmdtable[CMDTABLESIZE] ; pp++) {
+ for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+ if (cmdp->cmdtype == CMDNORMAL
+ || (cmdp->cmdtype == CMDBUILTIN && psh->builtinloc >= 0))
+ cmdp->rehash = 1;
+ }
+ }
+}
+
+
+
+/*
+ * Fix command hash table when PATH changed.
+ * Called before PATH is changed. The argument is the new value of PATH;
+ * pathval(psh) still returns the old value at this point.
+ * Called with interrupts off.
+ */
+
+void
+changepath(shinstance *psh, const char *newval)
+{
+ const char *old, *new;
+ int idx;
+ int firstchange;
+ int bltin;
+
+ old = pathval(psh);
+ new = newval;
+ firstchange = 9999; /* assume no change */
+ idx = 0;
+ bltin = -1;
+ for (;;) {
+ if (*old != *new) {
+ firstchange = idx;
+#ifdef PC_PATH_SEP
+ if ((*old == '\0' && *new == ';')
+ || (*old == ';' && *new == '\0'))
+#else
+ if ((*old == '\0' && *new == ':')
+ || (*old == ':' && *new == '\0'))
+#endif
+ firstchange++;
+ old = new; /* ignore subsequent differences */
+ }
+ if (*new == '\0')
+ break;
+ if (*new == '%' && bltin < 0 && prefix("builtin", new + 1))
+ bltin = idx;
+#ifdef PC_PATH_SEP
+ if (*new == ';') {
+#else
+ if (*new == ':') {
+#endif
+ idx++;
+ }
+ new++, old++;
+ }
+ if (psh->builtinloc < 0 && bltin >= 0)
+ psh->builtinloc = bltin; /* zap builtins */
+ if (psh->builtinloc >= 0 && bltin < 0)
+ firstchange = 0;
+ clearcmdentry(psh, firstchange);
+ psh->builtinloc = bltin;
+}
+
+
+/*
+ * Clear out command entries. The argument specifies the first entry in
+ * PATH which has changed.
+ */
+
+STATIC void
+clearcmdentry(shinstance *psh, int firstchange)
+{
+ struct tblentry **tblp;
+ struct tblentry **pp;
+ struct tblentry *cmdp;
+
+ INTOFF;
+ for (tblp = psh->cmdtable ; tblp < &psh->cmdtable[CMDTABLESIZE] ; tblp++) {
+ pp = tblp;
+ while ((cmdp = *pp) != NULL) {
+ if ((cmdp->cmdtype == CMDNORMAL &&
+ cmdp->param.n.index >= firstchange)
+ || (cmdp->cmdtype == CMDBUILTIN &&
+ psh->builtinloc >= firstchange)) {
+ *pp = cmdp->next;
+ ckfree(psh, cmdp);
+ } else {
+ pp = &cmdp->next;
+ }
+ }
+ }
+ INTON;
+}
+
+
+/*
+ * Delete all functions.
+ */
+
+#ifdef mkinit
+MKINIT void deletefuncs(struct shinstance *);
+MKINIT void hash_special_builtins(struct shinstance *);
+
+INIT {
+ hash_special_builtins(psh);
+}
+
+SHELLPROC {
+ deletefuncs(psh);
+}
+#endif
+
+void
+deletefuncs(shinstance *psh)
+{
+ struct tblentry **tblp;
+ struct tblentry **pp;
+ struct tblentry *cmdp;
+
+ INTOFF;
+ for (tblp = psh->cmdtable ; tblp < &psh->cmdtable[CMDTABLESIZE] ; tblp++) {
+ pp = tblp;
+ while ((cmdp = *pp) != NULL) {
+ if (cmdp->cmdtype == CMDFUNCTION) {
+ *pp = cmdp->next;
+ freefunc(psh, cmdp->param.func);
+ ckfree(psh, cmdp);
+ } else {
+ pp = &cmdp->next;
+ }
+ }
+ }
+ INTON;
+}
+
+
+
+/*
+ * Locate a command in the command hash table. If "add" is nonzero,
+ * add the command to the table if it is not already present. The
+ * variable "lastcmdentry" is set to point to the address of the link
+ * pointing to the entry, so that delete_cmd_entry can delete the
+ * entry.
+ */
+
+struct tblentry **lastcmdentry;
+
+
+STATIC struct tblentry *
+cmdlookup(shinstance *psh, const char *name, int add)
+{
+ int hashval;
+ const char *p;
+ struct tblentry *cmdp;
+ struct tblentry **pp;
+
+ p = name;
+ hashval = *p << 4;
+ while (*p)
+ hashval += *p++;
+ hashval &= 0x7FFF;
+ pp = &psh->cmdtable[hashval % CMDTABLESIZE];
+ for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+ if (equal(cmdp->cmdname, name))
+ break;
+ pp = &cmdp->next;
+ }
+ if (add && cmdp == NULL) {
+ INTOFF;
+ cmdp = *pp = ckmalloc(psh, sizeof (struct tblentry) - ARB
+ + strlen(name) + 1);
+ cmdp->next = NULL;
+ cmdp->cmdtype = CMDUNKNOWN;
+ cmdp->rehash = 0;
+ cmdp->param.n.index = 0;
+ cmdp->param.n.suffix = 0;
+ strcpy(cmdp->cmdname, name);
+ INTON;
+ }
+ lastcmdentry = pp;
+ return cmdp;
+}
+
+/*
+ * Delete the command entry returned on the last lookup.
+ */
+
+STATIC void
+delete_cmd_entry(shinstance *psh)
+{
+ struct tblentry *cmdp;
+
+ INTOFF;
+ cmdp = *lastcmdentry;
+ *lastcmdentry = cmdp->next;
+ ckfree(psh, cmdp);
+ INTON;
+}
+
+
+
+#ifdef notdef
+void
+getcmdentry(shinstance *psh, char *name, struct cmdentry *entry)
+{
+ struct tblentry *cmdp = cmdlookup(psh, name, 0);
+
+ if (cmdp) {
+ entry->u = cmdp->param;
+ entry->cmdtype = cmdp->cmdtype;
+ } else {
+ entry->cmdtype = CMDUNKNOWN;
+ entry->u.index = 0;
+ }
+}
+#endif
+
+
+/*
+ * Add a new command entry, replacing any existing command entry for
+ * the same name - except special builtins.
+ */
+
+STATIC void
+addcmdentry(shinstance *psh, char *name, struct cmdentry *entry)
+{
+ struct tblentry *cmdp;
+
+ INTOFF;
+ cmdp = cmdlookup(psh, name, 1);
+ if (cmdp->cmdtype != CMDSPLBLTIN) {
+ if (cmdp->cmdtype == CMDFUNCTION) {
+ freefunc(psh, cmdp->param.func);
+ }
+ cmdp->cmdtype = entry->cmdtype;
+ cmdp->param = entry->u;
+ }
+ INTON;
+}
+
+
+/*
+ * Define a shell function.
+ */
+
+void
+defun(shinstance *psh, char *name, union node *func)
+{
+ struct cmdentry entry;
+
+ INTOFF;
+ entry.cmdtype = CMDFUNCTION;
+ entry.u.func = copyfunc(psh, func);
+ addcmdentry(psh, name, &entry);
+ INTON;
+}
+
+
+/*
+ * Delete a function if it exists.
+ */
+
+int
+unsetfunc(shinstance *psh, char *name)
+{
+ struct tblentry *cmdp;
+
+ if ((cmdp = cmdlookup(psh, name, 0)) != NULL &&
+ cmdp->cmdtype == CMDFUNCTION) {
+ freefunc(psh, cmdp->param.func);
+ delete_cmd_entry(psh);
+ return (0);
+ }
+ return (1);
+}
+
+/*
+ * Locate and print what a word is...
+ * also used for 'command -[v|V]'
+ */
+
+int
+typecmd(shinstance *psh, int argc, char **argv)
+{
+ struct cmdentry entry;
+ struct tblentry *cmdp;
+ char * const *pp;
+ struct alias *ap;
+ int err = 0;
+ char *arg;
+ int c;
+ int V_flag = 0;
+ int v_flag = 0;
+ int p_flag = 0;
+
+ while ((c = nextopt(psh, "vVp")) != 0) {
+ switch (c) {
+ case 'v': v_flag = 1; break;
+ case 'V': V_flag = 1; break;
+ case 'p': p_flag = 1; break;
+ }
+ }
+
+ if (p_flag && (v_flag || V_flag))
+ error(psh, "cannot specify -p with -v or -V");
+
+ while ((arg = *psh->argptr++)) {
+ if (!v_flag)
+ out1str(psh, arg);
+ /* First look at the keywords */
+ for (pp = parsekwd; *pp; pp++)
+ if (**pp == *arg && equal(*pp, arg))
+ break;
+
+ if (*pp) {
+ if (v_flag)
+ err = 1;
+ else
+ out1str(psh, " is a shell keyword\n");
+ continue;
+ }
+
+ /* Then look at the aliases */
+ if ((ap = lookupalias(psh, arg, 1)) != NULL) {
+ if (!v_flag)
+ out1fmt(psh, " is an alias for \n");
+ out1fmt(psh, "%s\n", ap->val);
+ continue;
+ }
+
+ /* Then check if it is a tracked alias */
+ if ((cmdp = cmdlookup(psh, arg, 0)) != NULL) {
+ entry.cmdtype = cmdp->cmdtype;
+ entry.u = cmdp->param;
+ } else {
+ /* Finally use brute force */
+ find_command(psh, arg, &entry, DO_ABS, pathval(psh));
+ }
+
+ switch (entry.cmdtype) {
+ case CMDNORMAL: {
+ if (!haspath(arg)) {
+ const char *path = pathval(psh);
+ char *name;
+ int j = entry.u.n.index;
+ do {
+ name = padvance(psh, &path, arg);
+ stunalloc(psh, name);
+ } while (--j >= 0);
+ if (!v_flag)
+ out1fmt(psh, " is%s ",
+ cmdp ? " a tracked alias for" : "");
+#ifdef PC_EXE_EXTS
+ if ((unsigned)entry.u.n.suffix < K_ELEMENTS(g_exe_suffixes))
+ out1fmt(psh, "%s%s\n", name, g_exe_suffixes[entry.u.n.suffix]);
+ else
+#endif
+ out1fmt(psh, "%s\n", name);
+ } else {
+ if (shfile_access(&psh->fdtab, arg, X_OK) == 0) {
+ if (!v_flag)
+ out1fmt(psh, " is ");
+ out1fmt(psh, "%s\n", arg);
+ } else {
+ if (!v_flag)
+ out1fmt(psh, ": %s\n",
+ sh_strerror(psh, errno));
+ else
+ err = 126;
+ }
+ }
+ break;
+ }
+ case CMDFUNCTION:
+ if (!v_flag)
+ out1str(psh, " is a shell function\n");
+ else
+ out1fmt(psh, "%s\n", arg);
+ break;
+
+ case CMDBUILTIN:
+ if (!v_flag)
+ out1str(psh, " is a shell builtin\n");
+ else
+ out1fmt(psh, "%s\n", arg);
+ break;
+
+ case CMDSPLBLTIN:
+ if (!v_flag)
+ out1str(psh, " is a special shell builtin\n");
+ else
+ out1fmt(psh, "%s\n", arg);
+ break;
+
+ default:
+ if (!v_flag)
+ out1str(psh, ": not found\n");
+ err = 127;
+ break;
+ }
+ }
+ return err;
+}
diff --git a/src/kash/exec.h b/src/kash/exec.h
new file mode 100644
index 0000000..50849fd
--- /dev/null
+++ b/src/kash/exec.h
@@ -0,0 +1,96 @@
+/* $NetBSD: exec.h,v 1.21 2003/08/07 09:05:31 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)exec.h 8.3 (Berkeley) 6/8/95
+ */
+
+#ifndef ___exec_h
+#define ___exec_h
+
+/* values of cmdtype */
+#define CMDUNKNOWN -1 /* no entry in table for command */
+#define CMDNORMAL 0 /* command is an executable program */
+#define CMDFUNCTION 1 /* command is a shell function */
+#define CMDBUILTIN 2 /* command is a shell builtin */
+#define CMDSPLBLTIN 3 /* command is a special shell builtin */
+
+
+union param {
+ struct
+ {
+ int index;
+ int suffix; /* PC suffix index */
+ } n;
+ int (*bltin)(struct shinstance*, int, char**);
+ union node *func;
+};
+
+struct cmdentry {
+ int cmdtype;
+ union param u;
+};
+
+
+/* action to find_command() */
+#define DO_ERR 0x01 /* prints errors */
+#define DO_ABS 0x02 /* checks absolute paths */
+#define DO_NOFUNC 0x04 /* don't return shell functions, for command */
+#define DO_ALTPATH 0x08 /* using alternate path */
+#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */
+
+/*extern const char *pathopt;*/ /* set by padvance */
+
+#if !defined(__GNUC__) && !defined(__attribute__)
+# define __attribute__(a)
+#endif
+
+#ifndef SH_FORKED_MODE
+void subshellinitexec(shinstance *, shinstance *);
+#endif
+SH_NORETURN_1 void shellexec(struct shinstance *, char **, char **, const char *, int, int) SH_NORETURN_2;
+char *padvance(struct shinstance *, const char **, const char *);
+int hashcmd(struct shinstance *, int, char **);
+void find_command(struct shinstance *, char *, struct cmdentry *, int, const char *);
+int (*find_builtin(struct shinstance *, char *))(struct shinstance *, int, char **);
+int (*find_splbltin(struct shinstance *, char *))(struct shinstance *, int, char **);
+void hashcd(struct shinstance *);
+void changepath(struct shinstance *, const char *);
+void deletefuncs(struct shinstance *);
+void getcmdentry(struct shinstance *, char *, struct cmdentry *);
+void addcmdentry(struct shinstance *, char *, struct cmdentry *);
+void defun(struct shinstance *, char *, union node *);
+int unsetfunc(struct shinstance *, char *);
+int typecmd(struct shinstance *, int, char **);
+void hash_special_builtins(struct shinstance *);
+
+#endif
diff --git a/src/kash/expand.c b/src/kash/expand.c
new file mode 100644
index 0000000..ff30455
--- /dev/null
+++ b/src/kash/expand.c
@@ -0,0 +1,1617 @@
+/* $NetBSD: expand.c,v 1.71 2005/06/01 15:41:19 lukem Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95";
+#else
+__RCSID("$NetBSD: expand.c,v 1.71 2005/06/01 15:41:19 lukem Exp $");
+#endif /* not lint */
+#endif
+
+#include <sys/types.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+/*
+ * Routines to expand arguments to commands. We have to deal with
+ * backquotes, shell variables, and file metacharacters.
+ */
+
+#include "shell.h"
+#include "main.h"
+#include "nodes.h"
+#include "eval.h"
+#include "expand.h"
+#include "syntax.h"
+#include "parser.h"
+#include "jobs.h"
+#include "options.h"
+#include "var.h"
+#include "input.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "show.h"
+#include "shinstance.h"
+
+///*
+// * Structure specifying which parts of the string should be searched
+// * for IFS characters.
+// */
+//
+//struct ifsregion {
+// struct ifsregion *next; /* next region in list */
+// int begoff; /* offset of start of region */
+// int endoff; /* offset of end of region */
+// int inquotes; /* search for nul bytes only */
+//};
+//
+//
+//char *expdest; /* output of current string */
+//struct nodelist *argbackq; /* list of back quote expressions */
+//struct ifsregion ifsfirst; /* first struct in list of ifs regions */
+//struct ifsregion *ifslastp; /* last struct in list */
+//struct arglist exparg; /* holds expanded arg list */
+
+STATIC void argstr(shinstance *, char *, int);
+STATIC void expari(shinstance *, int);
+STATIC char *exptilde(shinstance *, char *, int);
+STATIC void expbackq(shinstance *, union node *, int, int);
+STATIC int subevalvar(shinstance *, char *, char *, int, int, int, int);
+STATIC char *evalvar(shinstance *, char *, int);
+STATIC int varisset(shinstance *, char *, int);
+STATIC void varvalue(shinstance *, char *, int, int, int);
+STATIC void recordregion(shinstance *, int, int, int);
+STATIC void removerecordregions(shinstance *, int);
+STATIC void ifsbreakup(shinstance *, char *, struct arglist *);
+STATIC void ifsfree(shinstance *);
+STATIC void expandmeta(shinstance *, struct strlist *, int);
+STATIC void expmeta(shinstance *, char *, char *);
+STATIC void addfname(shinstance *, char *);
+STATIC struct strlist *expsort(struct strlist *);
+STATIC struct strlist *msort(struct strlist *, int);
+STATIC int pmatch(char *, char *, int);
+STATIC char *cvtnum(shinstance *, int, char *);
+STATIC char *cvtnum64(shinstance *, KI64, char *);
+
+/*
+ * Expand shell variables and backquotes inside a here document.
+ */
+
+void
+expandhere(shinstance *psh, union node *arg, int fd)
+{
+ psh->herefd = fd;
+ expandarg(psh, arg, (struct arglist *)NULL, 0);
+ xwrite(psh, fd, stackblock(psh), psh->expdest - stackblock(psh));
+}
+
+
+/*
+ * Perform variable substitution and command substitution on an argument,
+ * placing the resulting list of arguments in arglist. If EXP_FULL is true,
+ * perform splitting and file name expansion. When arglist is NULL, perform
+ * here document expansion.
+ */
+
+void
+expandarg(shinstance *psh, union node *arg, struct arglist *arglist, int flag)
+{
+ struct strlist *sp;
+ char *p;
+
+ psh->argbackq = arg->narg.backquote;
+ STARTSTACKSTR(psh, psh->expdest);
+ psh->ifsfirst.next = NULL;
+ psh->ifslastp = NULL;
+ argstr(psh, arg->narg.text, flag);
+ if (arglist == NULL) {
+ return; /* here document expanded */
+ }
+ STPUTC(psh, '\0', psh->expdest);
+ p = grabstackstr(psh, psh->expdest);
+ TRACE2((psh, "expandarg: p='%s'\n", p));
+ psh->exparg.lastp = &psh->exparg.list;
+ /*
+ * TODO - EXP_REDIR
+ */
+ if (flag & EXP_FULL) {
+ ifsbreakup(psh, p, &psh->exparg);
+ *psh->exparg.lastp = NULL;
+ psh->exparg.lastp = &psh->exparg.list;
+ expandmeta(psh, psh->exparg.list, flag);
+ } else {
+ if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */
+ rmescapes(psh, p);
+ sp = (struct strlist *)stalloc(psh, sizeof (struct strlist));
+ sp->text = p;
+ *psh->exparg.lastp = sp;
+ psh->exparg.lastp = &sp->next;
+ }
+ ifsfree(psh);
+ *psh->exparg.lastp = NULL;
+ if (psh->exparg.list) {
+ *arglist->lastp = psh->exparg.list;
+ arglist->lastp = psh->exparg.lastp;
+ }
+}
+
+
+
+/*
+ * Perform variable and command substitution.
+ * If EXP_FULL is set, output CTLESC characters to allow for further processing.
+ * Otherwise treat $@ like $* since no splitting will be performed.
+ */
+
+STATIC void
+argstr(shinstance *psh, char *p, int flag)
+{
+ char c;
+ int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */
+ int firsteq = 1;
+ const char *ifs = NULL;
+ int ifs_split = EXP_IFS_SPLIT;
+
+ if (flag & EXP_IFS_SPLIT)
+ ifs = ifsset(psh) ? ifsval(psh) : " \t\n";
+
+ if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE)))
+ p = exptilde(psh, p, flag);
+ for (;;) {
+ switch (c = *p++) {
+ case '\0':
+ case CTLENDVAR: /* end of expanding yyy in ${xxx-yyy} */
+ return;
+ case CTLQUOTEMARK:
+ /* "$@" syntax adherence hack */
+ if (p[0] == CTLVAR && p[2] == '@' && p[3] == '=')
+ break;
+ if ((flag & EXP_FULL) != 0)
+ STPUTC(psh, c, psh->expdest);
+ ifs_split = 0;
+ break;
+ case CTLQUOTEEND:
+ ifs_split = EXP_IFS_SPLIT;
+ break;
+ case CTLESC:
+ if (quotes)
+ STPUTC(psh, c, psh->expdest);
+ c = *p++;
+ STPUTC(psh, c, psh->expdest);
+ break;
+ case CTLVAR:
+ p = evalvar(psh, p, (flag & ~EXP_IFS_SPLIT) | (flag & ifs_split));
+ break;
+ case CTLBACKQ:
+ case CTLBACKQ|CTLQUOTE:
+ expbackq(psh, psh->argbackq->n, c & CTLQUOTE, flag);
+ psh->argbackq = psh->argbackq->next;
+ break;
+ case CTLENDARI:
+ expari(psh, flag);
+ break;
+ case ':':
+ case '=':
+ /*
+ * sort of a hack - expand tildes in variable
+ * assignments (after the first '=' and after ':'s).
+ */
+ STPUTC(psh, c, psh->expdest);
+ if (flag & EXP_VARTILDE && *p == '~') {
+ if (c == '=') {
+ if (firsteq)
+ firsteq = 0;
+ else
+ break;
+ }
+ p = exptilde(psh, p, flag);
+ }
+ break;
+ default:
+ STPUTC(psh, c, psh->expdest);
+ if (flag & EXP_IFS_SPLIT & ifs_split && strchr(ifs, c) != NULL) {
+ /* We need to get the output split here... */
+ recordregion(psh, (int)(psh->expdest - stackblock(psh) - 1),
+ (int)(psh->expdest - stackblock(psh)), 0);
+ }
+ break;
+ }
+ }
+}
+
+STATIC char *
+exptilde(shinstance *psh, char *p, int flag)
+{
+ char c, *startp = p;
+ const char *home;
+ int quotes = flag & (EXP_FULL | EXP_CASE);
+
+ while ((c = *p) != '\0') {
+ switch(c) {
+ case CTLESC:
+ return (startp);
+ case CTLQUOTEMARK:
+ return (startp);
+ case ':':
+ if (flag & EXP_VARTILDE)
+ goto done;
+ break;
+ case '/':
+ goto done;
+ }
+ p++;
+ }
+done:
+ *p = '\0';
+ if (*(startp+1) == '\0') {
+ if ((home = lookupvar(psh, "HOME")) == NULL)
+ goto lose;
+ } else {
+ if ((home = sh_gethomedir(psh, startp+1)) == NULL)
+ goto lose;
+ }
+ if (*home == '\0')
+ goto lose;
+ *p = c;
+ while ((c = *home++) != '\0') {
+ if (quotes && SQSYNTAX[(int)c] == CCTL)
+ STPUTC(psh, CTLESC, psh->expdest);
+ STPUTC(psh, c, psh->expdest);
+ }
+ return (p);
+lose:
+ *p = c;
+ return (startp);
+}
+
+
+STATIC void
+removerecordregions(shinstance *psh, int endoff)
+{
+ if (psh->ifslastp == NULL)
+ return;
+
+ if (psh->ifsfirst.endoff > endoff) {
+ while (psh->ifsfirst.next != NULL) {
+ struct ifsregion *ifsp;
+ INTOFF;
+ ifsp = psh->ifsfirst.next->next;
+ ckfree(psh, psh->ifsfirst.next);
+ psh->ifsfirst.next = ifsp;
+ INTON;
+ }
+ if (psh->ifsfirst.begoff > endoff)
+ psh->ifslastp = NULL;
+ else {
+ psh->ifslastp = &psh->ifsfirst;
+ psh->ifsfirst.endoff = endoff;
+ }
+ return;
+ }
+
+ psh->ifslastp = &psh->ifsfirst;
+ while (psh->ifslastp->next && psh->ifslastp->next->begoff < endoff)
+ psh->ifslastp=psh->ifslastp->next;
+ while (psh->ifslastp->next != NULL) {
+ struct ifsregion *ifsp;
+ INTOFF;
+ ifsp = psh->ifslastp->next->next;
+ ckfree(psh, psh->ifslastp->next);
+ psh->ifslastp->next = ifsp;
+ INTON;
+ }
+ if (psh->ifslastp->endoff > endoff)
+ psh->ifslastp->endoff = endoff;
+}
+
+
+/*
+ * Expand arithmetic expression. Backup to start of expression,
+ * evaluate, place result in (backed up) result, adjust string position.
+ */
+STATIC void
+expari(shinstance *psh, int flag)
+{
+ char *p, *start;
+ int result;
+ int begoff;
+ int quotes = flag & (EXP_FULL | EXP_CASE);
+ int quoted;
+
+ /* ifsfree(); */
+
+ /*
+ * This routine is slightly over-complicated for
+ * efficiency. First we make sure there is
+ * enough space for the result, which may be bigger
+ * than the expression if we add exponentation. Next we
+ * scan backwards looking for the start of arithmetic. If the
+ * next previous character is a CTLESC character, then we
+ * have to rescan starting from the beginning since CTLESC
+ * characters have to be processed left to right.
+ */
+#if INT_MAX / 1000000000 >= 10 || INT_MIN / 1000000000 <= -10
+#error "integers with more than 10 digits are not supported"
+#endif
+ CHECKSTRSPACE(psh, 12 - 2, psh->expdest);
+ USTPUTC(psh, '\0', psh->expdest);
+ start = stackblock(psh);
+ p = psh->expdest - 1;
+ while (*p != CTLARI && p >= start)
+ --p;
+ if (*p != CTLARI)
+ error(psh, "missing CTLARI (shouldn't happen)");
+ if (p > start && *(p-1) == CTLESC)
+ for (p = start; *p != CTLARI; p++)
+ if (*p == CTLESC)
+ p++;
+
+ if (p[1] == '"')
+ quoted=1;
+ else
+ quoted=0;
+ begoff = (int)(p - start);
+ removerecordregions(psh, begoff);
+ if (quotes)
+ rmescapes(psh, p+2);
+ result = arith(psh, p+2);
+ fmtstr(p, 12, "%d", result);
+
+ while (*p++)
+ ;
+
+ if (quoted == 0)
+ recordregion(psh, begoff, (int)(p - 1 - start), 0);
+ result = (int)(psh->expdest - p + 1);
+ STADJUST(psh, -result, psh->expdest);
+}
+
+
+/*
+ * Expand stuff in backwards quotes.
+ */
+
+STATIC void
+expbackq(shinstance *psh, union node *cmd, int quoted, int flag)
+{
+ struct backcmd in;
+ int i;
+ char buf[128];
+ char *p;
+ char *dest = psh->expdest;
+ struct ifsregion saveifs, *savelastp;
+ struct nodelist *saveargbackq;
+ char lastc;
+ int startloc = (int)(dest - stackblock(psh));
+ char const *syntax = quoted? DQSYNTAX : BASESYNTAX;
+ int saveherefd;
+ int quotes = flag & (EXP_FULL | EXP_CASE);
+#ifdef SH_DEAL_WITH_CRLF
+ int pending_cr = 0;
+#endif
+
+ INTOFF;
+ saveifs = psh->ifsfirst;
+ savelastp = psh->ifslastp;
+ saveargbackq = psh->argbackq;
+ saveherefd = psh->herefd;
+ psh->herefd = -1;
+ p = grabstackstr(psh, dest);
+ evalbackcmd(psh, cmd, &in);
+ ungrabstackstr(psh, p, dest);
+ psh->ifsfirst = saveifs;
+ psh->ifslastp = savelastp;
+ psh->argbackq = saveargbackq;
+ psh->herefd = saveherefd;
+
+ p = in.buf;
+ lastc = '\0';
+ for (;;) {
+ if (--in.nleft < 0) {
+ if (in.fd < 0)
+ break;
+ while ((i = shfile_read(&psh->fdtab, in.fd, buf, sizeof buf)) < 0 && errno == EINTR);
+ TRACE((psh, "expbackq: read returns %d\n", i));
+ if (i <= 0)
+ break;
+ p = buf;
+ in.nleft = i - 1;
+ }
+ lastc = *p++;
+#ifdef SH_DEAL_WITH_CRLF
+ if (pending_cr) {
+ pending_cr = 0;
+ if (lastc != '\n') {
+ if (quotes && syntax[(int)'\r'] == CCTL)
+ STPUTC(psh, CTLESC, dest);
+ STPUTC(psh, '\r', dest);
+ }
+ }
+ if (lastc == '\r')
+ pending_cr = '\r';
+ else
+#endif
+ if (lastc != '\0') {
+ if (quotes && syntax[(int)lastc] == CCTL)
+ STPUTC(psh, CTLESC, dest);
+ STPUTC(psh, lastc, dest);
+ }
+ }
+#ifdef SH_DEAL_WITH_CRLF
+ if (pending_cr) {
+ if (quotes && syntax[(int)'\r'] == CCTL)
+ STPUTC(psh, CTLESC, dest);
+ STPUTC(psh, '\r', dest);
+ }
+#endif
+
+ /* Eat all trailing newlines */
+ p = stackblock(psh) + startloc;
+ while (dest > p && dest[-1] == '\n')
+ STUNPUTC(psh, dest);
+
+ if (in.fd >= 0)
+ shfile_close(&psh->fdtab, in.fd);
+ if (in.buf)
+ ckfree(psh, in.buf);
+ if (in.jp)
+ psh->back_exitstatus = waitforjob(psh, in.jp);
+ if (quoted == 0)
+ recordregion(psh, startloc, (int)(dest - stackblock(psh)), 0);
+ TRACE((psh, "evalbackq: size=%d: \"%.*s\"\n",
+ (dest - stackblock(psh)) - startloc,
+ (dest - stackblock(psh)) - startloc,
+ stackblock(psh) + startloc));
+ psh->expdest = dest;
+ INTON;
+}
+
+
+
+STATIC int
+subevalvar(shinstance *psh, char *p, char *str, int strloc, int subtype, int startloc, int varflags)
+{
+ char *startp;
+ char *loc = NULL;
+ char *q;
+ int c = 0;
+ int saveherefd = psh->herefd;
+ struct nodelist *saveargbackq = psh->argbackq;
+ int amount;
+
+ psh->herefd = -1;
+ argstr(psh, p, 0);
+ STACKSTRNUL(psh, psh->expdest);
+ psh->herefd = saveherefd;
+ psh->argbackq = saveargbackq;
+ startp = stackblock(psh) + startloc;
+ if (str == NULL)
+ str = stackblock(psh) + strloc;
+
+ switch (subtype) {
+ case VSASSIGN:
+ setvar(psh, str, startp, 0);
+ amount = (int)(startp - psh->expdest);
+ STADJUST(psh, amount, psh->expdest);
+ varflags &= ~VSNUL;
+ if (c != 0)
+ *loc = c;
+ return 1;
+
+ case VSQUESTION:
+ if (*p != CTLENDVAR) {
+ outfmt(&psh->errout, "%s\n", startp);
+ error(psh, (char *)NULL);
+ }
+ error(psh, "%.*s: parameter %snot set", p - str - 1,
+ str, (varflags & VSNUL) ? "null or "
+ : nullstr);
+ /* NOTREACHED */
+
+ case VSTRIMLEFT:
+ for (loc = startp; loc < str; loc++) {
+ c = *loc;
+ *loc = '\0';
+ if (patmatch(psh, str, startp, varflags & VSQUOTE))
+ goto recordleft;
+ *loc = c;
+ if ((varflags & VSQUOTE) && *loc == CTLESC)
+ loc++;
+ }
+ return 0;
+
+ case VSTRIMLEFTMAX:
+ for (loc = str - 1; loc >= startp;) {
+ c = *loc;
+ *loc = '\0';
+ if (patmatch(psh, str, startp, varflags & VSQUOTE))
+ goto recordleft;
+ *loc = c;
+ loc--;
+ if ((varflags & VSQUOTE) && loc > startp &&
+ *(loc - 1) == CTLESC) {
+ for (q = startp; q < loc; q++)
+ if (*q == CTLESC)
+ q++;
+ if (q > loc)
+ loc--;
+ }
+ }
+ return 0;
+
+ case VSTRIMRIGHT:
+ for (loc = str - 1; loc >= startp;) {
+ if (patmatch(psh, str, loc, varflags & VSQUOTE))
+ goto recordright;
+ loc--;
+ if ((varflags & VSQUOTE) && loc > startp &&
+ *(loc - 1) == CTLESC) {
+ for (q = startp; q < loc; q++)
+ if (*q == CTLESC)
+ q++;
+ if (q > loc)
+ loc--;
+ }
+ }
+ return 0;
+
+ case VSTRIMRIGHTMAX:
+ for (loc = startp; loc < str - 1; loc++) {
+ if (patmatch(psh, str, loc, varflags & VSQUOTE))
+ goto recordright;
+ if ((varflags & VSQUOTE) && *loc == CTLESC)
+ loc++;
+ }
+ return 0;
+
+ default:
+ sh_abort(psh);
+ }
+
+recordleft:
+ *loc = c;
+ amount = (int)(((str - 1) - (loc - startp)) - psh->expdest);
+ STADJUST(psh, amount, psh->expdest);
+ while (loc != str - 1)
+ *startp++ = *loc++;
+ return 1;
+
+recordright:
+ amount = (int)(loc - psh->expdest);
+ STADJUST(psh, amount, psh->expdest);
+ STPUTC(psh, '\0', psh->expdest);
+ STADJUST(psh, -1, psh->expdest);
+ return 1;
+}
+
+
+/*
+ * Expand a variable, and return a pointer to the next character in the
+ * input string.
+ */
+
+STATIC char *
+evalvar(shinstance *psh, char *p, int flag)
+{
+ int subtype;
+ int varflags;
+ char *var;
+ char *val;
+ int patloc;
+ int c;
+ int set;
+ int special;
+ int startloc;
+ int varlen;
+ int apply_ifs;
+ int quotes = flag & (EXP_FULL | EXP_CASE);
+
+ varflags = (unsigned char)*p++;
+ subtype = varflags & VSTYPE;
+ var = p;
+ special = !is_name(*p);
+ p = strchr(p, '=') + 1;
+
+again: /* jump here after setting a variable with ${var=text} */
+ if (special) {
+ set = varisset(psh, var, varflags & VSNUL);
+ val = NULL;
+ } else {
+ val = lookupvar(psh, var);
+ if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) {
+ val = NULL;
+ set = 0;
+ } else
+ set = 1;
+ }
+
+ varlen = 0;
+ startloc = (int)(psh->expdest - stackblock(psh));
+
+ if (!set && uflag(psh)) {
+ switch (subtype) {
+ case VSNORMAL:
+ case VSTRIMLEFT:
+ case VSTRIMLEFTMAX:
+ case VSTRIMRIGHT:
+ case VSTRIMRIGHTMAX:
+ case VSLENGTH:
+ error(psh, "%.*s: parameter not set", p - var - 1, var);
+ /* NOTREACHED */
+ }
+ }
+
+ if (set && subtype != VSPLUS) {
+ /* insert the value of the variable */
+ if (special) {
+ varvalue(psh, var, varflags & VSQUOTE, subtype, flag);
+ if (subtype == VSLENGTH) {
+ varlen = (int)(psh->expdest - stackblock(psh) - startloc);
+ STADJUST(psh, -varlen, psh->expdest);
+ }
+ } else {
+ char const *syntax = (varflags & VSQUOTE) ? DQSYNTAX
+ : BASESYNTAX;
+
+ if (subtype == VSLENGTH) {
+ for (;*val; val++)
+ varlen++;
+ } else {
+ while (*val) {
+ if (quotes && syntax[(int)*val] == CCTL)
+ STPUTC(psh, CTLESC, psh->expdest);
+ STPUTC(psh, *val++, psh->expdest);
+ }
+
+ }
+ }
+ }
+
+
+ apply_ifs = ((varflags & VSQUOTE) == 0 ||
+ (*var == '@' && psh->shellparam.nparam != 1));
+
+ switch (subtype) {
+ case VSLENGTH:
+ psh->expdest = cvtnum(psh, varlen, psh->expdest);
+ break;
+
+ case VSNORMAL:
+ break;
+
+ case VSPLUS:
+ set = !set;
+ /* FALLTHROUGH */
+ case VSMINUS:
+ if (!set) {
+ argstr(psh, p, flag | (apply_ifs ? EXP_IFS_SPLIT : 0));
+ /*
+ * ${x-a b c} doesn't get split, but removing the
+ * 'apply_ifs = 0' apparantly breaks ${1+"$@"}..
+ * ${x-'a b' c} should generate 2 args.
+ */
+ /* We should have marked stuff already */
+ apply_ifs = 0;
+ }
+ break;
+
+ case VSTRIMLEFT:
+ case VSTRIMLEFTMAX:
+ case VSTRIMRIGHT:
+ case VSTRIMRIGHTMAX:
+ if (!set)
+ break;
+ /*
+ * Terminate the string and start recording the pattern
+ * right after it
+ */
+ STPUTC(psh, '\0', psh->expdest);
+ patloc = (int)(psh->expdest - stackblock(psh));
+ if (subevalvar(psh, p, NULL, patloc, subtype,
+ startloc, varflags) == 0) {
+ int amount = (int)(psh->expdest - stackblock(psh) - patloc) + 1;
+ STADJUST(psh, -amount, psh->expdest);
+ }
+ /* Remove any recorded regions beyond start of variable */
+ removerecordregions(psh, startloc);
+ apply_ifs = 1;
+ break;
+
+ case VSASSIGN:
+ case VSQUESTION:
+ if (set)
+ break;
+ if (subevalvar(psh, p, var, 0, subtype, startloc, varflags)) {
+ varflags &= ~VSNUL;
+ /*
+ * Remove any recorded regions beyond
+ * start of variable
+ */
+ removerecordregions(psh, startloc);
+ goto again;
+ }
+ apply_ifs = 0;
+ break;
+
+ default:
+ sh_abort(psh);
+ }
+
+ if (apply_ifs)
+ recordregion(psh, startloc, (int)(psh->expdest - stackblock(psh)),
+ varflags & VSQUOTE);
+
+ if (subtype != VSNORMAL) { /* skip to end of alternative */
+ int nesting = 1;
+ for (;;) {
+ if ((c = *p++) == CTLESC)
+ p++;
+ else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) {
+ if (set)
+ psh->argbackq = psh->argbackq->next;
+ } else if (c == CTLVAR) {
+ if ((*p++ & VSTYPE) != VSNORMAL)
+ nesting++;
+ } else if (c == CTLENDVAR) {
+ if (--nesting == 0)
+ break;
+ }
+ }
+ }
+ return p;
+}
+
+
+
+/*
+ * Test whether a specialized variable is set.
+ */
+
+STATIC int
+varisset(shinstance *psh, char *name, int nulok)
+{
+ if (*name == '!')
+ return psh->backgndpid != -1;
+ else if (*name == '@' || *name == '*') {
+ if (*psh->shellparam.p == NULL)
+ return 0;
+
+ if (nulok) {
+ char **av;
+
+ for (av = psh->shellparam.p; *av; av++)
+ if (**av != '\0')
+ return 1;
+ return 0;
+ }
+ } else if (is_digit(*name)) {
+ char *ap;
+ int num = atoi(name);
+
+ if (num > psh->shellparam.nparam)
+ return 0;
+
+ if (num == 0)
+ ap = psh->arg0;
+ else
+ ap = psh->shellparam.p[num - 1];
+
+ if (nulok && (ap == NULL || *ap == '\0'))
+ return 0;
+ }
+ return 1;
+}
+
+
+
+/*
+ * Add the value of a specialized variable to the stack string.
+ */
+
+STATIC void
+varvalue(shinstance *psh, char *name, int quoted, int subtype, int flag)
+{
+ int num;
+ char *p;
+ int i;
+ char sep;
+ char **ap;
+ char const *syntax;
+
+#define STRTODEST(p) \
+ do {\
+ if (flag & (EXP_FULL | EXP_CASE) && subtype != VSLENGTH) { \
+ syntax = quoted? DQSYNTAX : BASESYNTAX; \
+ while (*p) { \
+ if (syntax[(int)*p] == CCTL) \
+ STPUTC(psh, CTLESC, psh->expdest); \
+ STPUTC(psh, *p++, psh->expdest); \
+ } \
+ } else \
+ while (*p) \
+ STPUTC(psh, *p++, psh->expdest); \
+ } while (0)
+
+
+ switch (*name) {
+ case '$':
+#ifndef SH_FORKED_MODE
+ psh->expdest = cvtnum64(psh, psh->rootpid, psh->expdest);
+ break;
+#else
+ num = psh->rootpid;
+ goto numvar;
+#endif
+ case '?':
+ num = psh->exitstatus;
+ goto numvar;
+ case '#':
+ num = psh->shellparam.nparam;
+numvar:
+ psh->expdest = cvtnum(psh, num, psh->expdest);
+ break;
+ case '!':
+#ifndef SH_FORKED_MODE
+ psh->expdest = cvtnum64(psh, psh->backgndpid, psh->expdest);
+ break;
+#else
+ num = psh->backgndpid;
+ goto numvar;
+#endif
+ case '-':
+ for (i = 0; psh->optlist[i].name; i++) {
+ if (psh->optlist[i].val)
+ STPUTC(psh, psh->optlist[i].letter, psh->expdest);
+ }
+ break;
+ case '@':
+ if (flag & EXP_FULL && quoted) {
+ for (ap = psh->shellparam.p ; (p = *ap++) != NULL ; ) {
+ STRTODEST(p);
+ if (*ap)
+ STPUTC(psh, '\0', psh->expdest);
+ }
+ break;
+ }
+ /* fall through */
+ case '*':
+ if (ifsset(psh) != 0)
+ sep = ifsval(psh)[0];
+ else
+ sep = ' ';
+ for (ap = psh->shellparam.p ; (p = *ap++) != NULL ; ) {
+ STRTODEST(p);
+ if (*ap && sep)
+ STPUTC(psh, sep, psh->expdest);
+ }
+ break;
+ case '0':
+ p = psh->arg0;
+ STRTODEST(p);
+ break;
+ default:
+ if (is_digit(*name)) {
+ num = atoi(name);
+ if (num > 0 && num <= psh->shellparam.nparam) {
+ p = psh->shellparam.p[num - 1];
+ STRTODEST(p);
+ }
+ }
+ break;
+ }
+}
+
+
+
+/*
+ * Record the fact that we have to scan this region of the
+ * string for IFS characters.
+ */
+
+STATIC void
+recordregion(shinstance *psh, int start, int end, int inquotes)
+{
+ struct ifsregion *ifsp;
+
+ if (psh->ifslastp == NULL) {
+ ifsp = &psh->ifsfirst;
+ } else {
+ if (psh->ifslastp->endoff == start
+ && psh->ifslastp->inquotes == inquotes) {
+ /* extend previous area */
+ psh->ifslastp->endoff = end;
+ return;
+ }
+ ifsp = (struct ifsregion *)ckmalloc(psh, sizeof (struct ifsregion));
+ psh->ifslastp->next = ifsp;
+ }
+ psh->ifslastp = ifsp;
+ psh->ifslastp->next = NULL;
+ psh->ifslastp->begoff = start;
+ psh->ifslastp->endoff = end;
+ psh->ifslastp->inquotes = inquotes;
+}
+
+
+
+/*
+ * Break the argument string into pieces based upon IFS and add the
+ * strings to the argument list. The regions of the string to be
+ * searched for IFS characters have been stored by recordregion.
+ */
+STATIC void
+ifsbreakup(shinstance *psh, char *string, struct arglist *arglist)
+{
+ struct ifsregion *ifsp;
+ struct strlist *sp;
+ char *start;
+ char *p;
+ char *q;
+ const char *ifs;
+ const char *ifsspc;
+ int inquotes;
+
+ start = string;
+ ifsspc = NULL;
+ inquotes = 0;
+
+ if (psh->ifslastp == NULL) {
+ /* Return entire argument, IFS doesn't apply to any of it */
+ sp = (struct strlist *)stalloc(psh, sizeof *sp);
+ sp->text = start;
+ *arglist->lastp = sp;
+ arglist->lastp = &sp->next;
+ return;
+ }
+
+ ifs = ifsset(psh) ? ifsval(psh) : " \t\n";
+
+ for (ifsp = &psh->ifsfirst; ifsp != NULL; ifsp = ifsp->next) {
+ p = string + ifsp->begoff;
+ inquotes = ifsp->inquotes;
+ ifsspc = NULL;
+ while (p < string + ifsp->endoff) {
+ q = p;
+ if (*p == CTLESC)
+ p++;
+ if (inquotes) {
+ /* Only NULs (probably from "$@") end args */
+ if (*p != 0) {
+ p++;
+ continue;
+ }
+ } else {
+ if (!strchr(ifs, *p)) {
+ p++;
+ continue;
+ }
+ ifsspc = strchr(" \t\n", *p);
+
+ /* Ignore IFS whitespace at start */
+ if (q == start && ifsspc != NULL) {
+ p++;
+ start = p;
+ continue;
+ }
+ }
+
+ /* Save this argument... */
+ *q = '\0';
+ sp = (struct strlist *)stalloc(psh, sizeof *sp);
+ sp->text = start;
+ *arglist->lastp = sp;
+ arglist->lastp = &sp->next;
+ p++;
+
+ if (ifsspc != NULL) {
+ /* Ignore further trailing IFS whitespace */
+ for (; p < string + ifsp->endoff; p++) {
+ q = p;
+ if (*p == CTLESC)
+ p++;
+ if (strchr(ifs, *p) == NULL) {
+ p = q;
+ break;
+ }
+ if (strchr(" \t\n", *p) == NULL) {
+ p++;
+ break;
+ }
+ }
+ }
+ start = p;
+ }
+ }
+
+ /*
+ * Save anything left as an argument.
+ * Traditionally we have treated 'IFS=':'; set -- x$IFS' as
+ * generating 2 arguments, the second of which is empty.
+ * Some recent clarification of the Posix spec say that it
+ * should only generate one....
+ */
+ if (*start /* || (!ifsspc && start > string) */) {
+ sp = (struct strlist *)stalloc(psh, sizeof *sp);
+ sp->text = start;
+ *arglist->lastp = sp;
+ arglist->lastp = &sp->next;
+ }
+}
+
+STATIC void
+ifsfree(shinstance *psh)
+{
+ while (psh->ifsfirst.next != NULL) {
+ struct ifsregion *ifsp;
+ INTOFF;
+ ifsp = psh->ifsfirst.next->next;
+ ckfree(psh, psh->ifsfirst.next);
+ psh->ifsfirst.next = ifsp;
+ INTON;
+ }
+ psh->ifslastp = NULL;
+ psh->ifsfirst.next = NULL;
+}
+
+
+
+/*
+ * Expand shell metacharacters. At this point, the only control characters
+ * should be escapes. The results are stored in the list psh->exparg.
+ */
+
+//char *expdir;
+
+
+STATIC void
+expandmeta(shinstance *psh, struct strlist *str, int flag)
+{
+ char *p;
+ struct strlist **savelastp;
+ struct strlist *sp;
+ char c;
+ /* TODO - EXP_REDIR */
+
+ while (str) {
+ if (fflag(psh))
+ goto nometa;
+ p = str->text;
+ for (;;) { /* fast check for meta chars */
+ if ((c = *p++) == '\0')
+ goto nometa;
+ if (c == '*' || c == '?' || c == '[' || c == '!')
+ break;
+ }
+ savelastp = psh->exparg.lastp;
+ INTOFF;
+ if (psh->expdir == NULL) {
+ size_t i = strlen(str->text);
+ psh->expdir = ckmalloc(psh, i < 2048 ? 2048 : i); /* XXX */
+ }
+
+ expmeta(psh, psh->expdir, str->text);
+ ckfree(psh, psh->expdir);
+ psh->expdir = NULL;
+ INTON;
+ if (psh->exparg.lastp == savelastp) {
+ /*
+ * no matches
+ */
+nometa:
+ *psh->exparg.lastp = str;
+ rmescapes(psh, str->text);
+ psh->exparg.lastp = &str->next;
+ } else {
+ *psh->exparg.lastp = NULL;
+ *savelastp = sp = expsort(*savelastp);
+ while (sp->next != NULL)
+ sp = sp->next;
+ psh->exparg.lastp = &sp->next;
+ }
+ str = str->next;
+ }
+}
+
+
+/*
+ * Do metacharacter (i.e. *, ?, [...]) expansion.
+ */
+
+STATIC void
+expmeta(shinstance *psh, char *enddir, char *name)
+{
+ char *p;
+ const char *cp;
+ char *q;
+ char *start;
+ char *endname;
+ int metaflag;
+ struct stat statb;
+ shdir *dirp;
+ shdirent *dp;
+ int atend;
+ int matchdot;
+
+ metaflag = 0;
+ start = name;
+ for (p = name ; ; p++) {
+ if (*p == '*' || *p == '?')
+ metaflag = 1;
+ else if (*p == '[') {
+ q = p + 1;
+ if (*q == '!')
+ q++;
+ for (;;) {
+ while (*q == CTLQUOTEMARK)
+ q++;
+ if (*q == CTLESC)
+ q++;
+ if (*q == '/' || *q == '\0')
+ break;
+ if (*++q == ']') {
+ metaflag = 1;
+ break;
+ }
+ }
+ } else if (*p == '!' && p[1] == '!' && (p == name || p[-1] == '/')) {
+ metaflag = 1;
+ } else if (*p == '\0')
+ break;
+ else if (*p == CTLQUOTEMARK)
+ continue;
+ else if (*p == CTLESC)
+ p++;
+ if (*p == '/') {
+ if (metaflag)
+ break;
+ start = p + 1;
+ }
+ }
+ if (metaflag == 0) { /* we've reached the end of the file name */
+ if (enddir != psh->expdir)
+ metaflag++;
+ for (p = name ; ; p++) {
+ if (*p == CTLQUOTEMARK)
+ continue;
+ if (*p == CTLESC)
+ p++;
+ *enddir++ = *p;
+ if (*p == '\0')
+ break;
+ }
+ if (metaflag == 0 || shfile_lstat(&psh->fdtab, psh->expdir, &statb) >= 0)
+ addfname(psh, psh->expdir);
+ TRACE2((psh, "expandarg: return #1 (metaflag=%d)\n", metaflag));
+ return;
+ }
+ endname = p;
+ if (start != name) {
+ p = name;
+ while (p < start) {
+ while (*p == CTLQUOTEMARK)
+ p++;
+ if (*p == CTLESC)
+ p++;
+ *enddir++ = *p++;
+ }
+ }
+ if (enddir == psh->expdir) {
+ cp = ".";
+ } else if (enddir == psh->expdir + 1 && *psh->expdir == '/') {
+ cp = "/";
+ } else {
+ cp = psh->expdir;
+ enddir[-1] = '\0';
+ }
+ if ((dirp = shfile_opendir(&psh->fdtab, cp)) == NULL) {
+ TRACE2((psh, "expandarg: return #2 (shfile_opendir(,%s) failed)\n", cp));
+ return;
+ }
+ if (enddir != psh->expdir)
+ enddir[-1] = '/';
+ if (*endname == 0) {
+ atend = 1;
+ } else {
+ atend = 0;
+ *endname++ = '\0';
+ }
+ matchdot = 0;
+ p = start;
+ while (*p == CTLQUOTEMARK)
+ p++;
+ if (*p == CTLESC)
+ p++;
+ if (*p == '.')
+ matchdot++;
+ while (! int_pending() && (dp = shfile_readdir(dirp)) != NULL) {
+ if (dp->name[0] == '.' && ! matchdot)
+ continue;
+ if (patmatch(psh, start, dp->name, 0)) {
+ if (atend) {
+ scopy(dp->name, enddir);
+ addfname(psh, psh->expdir);
+ } else {
+ for (p = enddir, cp = dp->name;
+ (*p++ = *cp++) != '\0';)
+ continue;
+ p[-1] = '/';
+ expmeta(psh, p, endname);
+ }
+ }
+ }
+ shfile_closedir(dirp);
+ if (! atend)
+ endname[-1] = '/';
+}
+
+
+/*
+ * Add a file name to the list.
+ */
+
+STATIC void
+addfname(shinstance *psh, char *name)
+{
+ char *p;
+ struct strlist *sp;
+
+ p = stalloc(psh, strlen(name) + 1);
+ scopy(name, p);
+ sp = (struct strlist *)stalloc(psh, sizeof *sp);
+ sp->text = p;
+ *psh->exparg.lastp = sp;
+ psh->exparg.lastp = &sp->next;
+}
+
+
+/*
+ * Sort the results of file name expansion. It calculates the number of
+ * strings to sort and then calls msort (short for merge sort) to do the
+ * work.
+ */
+
+STATIC struct strlist *
+expsort(struct strlist *str)
+{
+ int len;
+ struct strlist *sp;
+
+ len = 0;
+ for (sp = str ; sp ; sp = sp->next)
+ len++;
+ return msort(str, len);
+}
+
+
+STATIC struct strlist *
+msort(struct strlist *list, int len)
+{
+ struct strlist *p, *q = NULL;
+ struct strlist **lpp;
+ int half;
+ int n;
+
+ if (len <= 1)
+ return list;
+ half = len >> 1;
+ p = list;
+ for (n = half ; --n >= 0 ; ) {
+ q = p;
+ p = p->next;
+ }
+ q->next = NULL; /* terminate first half of list */
+ q = msort(list, half); /* sort first half of list */
+ p = msort(p, len - half); /* sort second half */
+ lpp = &list;
+ for (;;) {
+ if (strcmp(p->text, q->text) < 0) {
+ *lpp = p;
+ lpp = &p->next;
+ if ((p = *lpp) == NULL) {
+ *lpp = q;
+ break;
+ }
+ } else {
+ *lpp = q;
+ lpp = &q->next;
+ if ((q = *lpp) == NULL) {
+ *lpp = p;
+ break;
+ }
+ }
+ }
+ return list;
+}
+
+
+
+/*
+ * Returns true if the pattern matches the string.
+ */
+
+int
+patmatch(shinstance *psh, char *pattern, char *string, int squoted)
+{
+#ifdef notdef
+ if (pattern[0] == '!' && pattern[1] == '!')
+ return 1 - pmatch(pattern + 2, string);
+ else
+#endif
+ return pmatch(pattern, string, squoted);
+}
+
+
+STATIC int
+pmatch(char *pattern, char *string, int squoted)
+{
+ char *p, *q;
+ char c;
+
+ p = pattern;
+ q = string;
+ for (;;) {
+ switch (c = *p++) {
+ case '\0':
+ goto breakloop;
+ case CTLESC:
+ if (squoted && *q == CTLESC)
+ q++;
+ if (*q++ != *p++)
+ return 0;
+ break;
+ case CTLQUOTEMARK:
+ continue;
+ case '?':
+ if (squoted && *q == CTLESC)
+ q++;
+ if (*q++ == '\0')
+ return 0;
+ break;
+ case '*':
+ c = *p;
+ while (c == CTLQUOTEMARK || c == '*')
+ c = *++p;
+ if (c != CTLESC && c != CTLQUOTEMARK &&
+ c != '?' && c != '*' && c != '[') {
+ while (*q != c) {
+ if (squoted && *q == CTLESC &&
+ q[1] == c)
+ break;
+ if (*q == '\0')
+ return 0;
+ if (squoted && *q == CTLESC)
+ q++;
+ q++;
+ }
+ }
+ do {
+ if (pmatch(p, q, squoted))
+ return 1;
+ if (squoted && *q == CTLESC)
+ q++;
+ } while (*q++ != '\0');
+ return 0;
+ case '[': {
+ char *endp;
+ int invert, found;
+ char chr;
+
+ endp = p;
+ if (*endp == '!')
+ endp++;
+ for (;;) {
+ while (*endp == CTLQUOTEMARK)
+ endp++;
+ if (*endp == '\0')
+ goto dft; /* no matching ] */
+ if (*endp == CTLESC)
+ endp++;
+ if (*++endp == ']')
+ break;
+ }
+ invert = 0;
+ if (*p == '!') {
+ invert++;
+ p++;
+ }
+ found = 0;
+ chr = *q++;
+ if (squoted && chr == CTLESC)
+ chr = *q++;
+ if (chr == '\0')
+ return 0;
+ c = *p++;
+ do {
+ if (c == CTLQUOTEMARK)
+ continue;
+ if (c == CTLESC)
+ c = *p++;
+ if (*p == '-' && p[1] != ']') {
+ p++;
+ while (*p == CTLQUOTEMARK)
+ p++;
+ if (*p == CTLESC)
+ p++;
+ if (chr >= c && chr <= *p)
+ found = 1;
+ p++;
+ } else {
+ if (chr == c)
+ found = 1;
+ }
+ } while ((c = *p++) != ']');
+ if (found == invert)
+ return 0;
+ break;
+ }
+dft: default:
+ if (squoted && *q == CTLESC)
+ q++;
+ if (*q++ != c)
+ return 0;
+ break;
+ }
+ }
+breakloop:
+ if (*q != '\0')
+ return 0;
+ return 1;
+}
+
+
+
+/*
+ * Remove any CTLESC characters from a string.
+ */
+
+void
+rmescapes(shinstance *psh, char *str)
+{
+ char *p, *q;
+
+ p = str;
+ while (*p != CTLESC && *p != CTLQUOTEMARK) {
+ if (*p++ == '\0')
+ return;
+ }
+ q = p;
+ while (*p) {
+ if (*p == CTLQUOTEMARK) {
+ p++;
+ continue;
+ }
+ if (*p == CTLESC)
+ p++;
+ *q++ = *p++;
+ }
+ *q = '\0';
+}
+
+
+
+/*
+ * See if a pattern matches in a case statement.
+ */
+
+int
+casematch(shinstance *psh, union node *pattern, char *val)
+{
+ struct stackmark smark;
+ int result;
+ char *p;
+
+ setstackmark(psh, &smark);
+ psh->argbackq = pattern->narg.backquote;
+ STARTSTACKSTR(psh, psh->expdest);
+ psh->ifslastp = NULL;
+ argstr(psh, pattern->narg.text, EXP_TILDE | EXP_CASE);
+ STPUTC(psh, '\0', psh->expdest);
+ p = grabstackstr(psh, psh->expdest);
+ result = patmatch(psh, p, val, 0);
+ popstackmark(psh, &smark);
+ return result;
+}
+
+/*
+ * Our own itoa().
+ */
+
+STATIC char *
+cvtnum(shinstance *psh, int num, char *buf)
+{
+ char temp[32];
+ int neg = num < 0;
+ char *p = temp + 31;
+
+ temp[31] = '\0';
+
+ do {
+ *--p = num % 10 + '0';
+ } while ((num /= 10) != 0);
+
+ if (neg)
+ *--p = '-';
+
+ while (*p)
+ STPUTC(psh, *p++, buf);
+ return buf;
+}
+
+STATIC char *
+cvtnum64(shinstance *psh, KI64 num, char *buf)
+{
+ char temp[32];
+ int neg = num < 0;
+ char *p = temp + 31;
+
+ temp[31] = '\0';
+
+ do {
+ *--p = num % 10 + '0';
+ } while ((num /= 10) != 0);
+
+ if (neg)
+ *--p = '-';
+
+ while (*p)
+ STPUTC(psh, *p++, buf);
+ return buf;
+}
+
+/*
+ * Do most of the work for wordexp(3).
+ */
+
+int
+wordexpcmd(shinstance *psh, int argc, char **argv)
+{
+ size_t len;
+ int i;
+
+ out1fmt(psh, "%d", argc - 1);
+ out1c(psh, '\0');
+ for (i = 1, len = 0; i < argc; i++)
+ len += strlen(argv[i]);
+ out1fmt(psh, "%zd", len);
+ out1c(psh, '\0');
+ for (i = 1; i < argc; i++) {
+ out1str(psh, argv[i]);
+ out1c(psh, '\0');
+ }
+ return (0);
+}
diff --git a/src/kash/expand.h b/src/kash/expand.h
new file mode 100644
index 0000000..8987cb9
--- /dev/null
+++ b/src/kash/expand.h
@@ -0,0 +1,78 @@
+/* $NetBSD: expand.h,v 1.16 2004/07/13 15:05:59 seb Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)expand.h 8.2 (Berkeley) 5/4/95
+ */
+
+#ifndef ___expand_h
+#define ___expand_h
+
+#include "shtypes.h"
+
+struct strlist {
+ struct strlist *next;
+ char *text;
+};
+
+
+struct arglist {
+ struct strlist *list;
+ struct strlist **lastp;
+};
+
+/*
+ * expandarg() flags
+ */
+#define EXP_FULL 0x1 /* perform word splitting & file globbing */
+#define EXP_TILDE 0x2 /* do normal tilde expansion */
+#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */
+#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */
+#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */
+#define EXP_IFS_SPLIT 0x20 /* need to record arguments for ifs breakup */
+
+
+union node;
+void expandhere(struct shinstance *, union node *, int);
+void expandarg(struct shinstance *, union node *, struct arglist *, int);
+int patmatch(struct shinstance *, char *, char *, int);
+void rmescapes(struct shinstance *, char *);
+int casematch(struct shinstance *, union node *, char *);
+int wordexpcmd(struct shinstance *, int, char **);
+
+/* From arith.y */
+int arith(struct shinstance *, const char *);
+int expcmd(struct shinstance *, int , char **);
+void arith_lex_reset(void);
+int yylex(void);
+
+#endif
diff --git a/src/kash/funcs/cmv b/src/kash/funcs/cmv
new file mode 100644
index 0000000..667f846
--- /dev/null
+++ b/src/kash/funcs/cmv
@@ -0,0 +1,50 @@
+# $NetBSD: cmv,v 1.7 1995/05/11 21:31:05 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# 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. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. 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.
+#
+# @(#)cmv 8.2 (Berkeley) 5/4/95
+
+# Conditional move--don't replace an existing file.
+
+cmv() {
+ if test $# != 2
+ then echo "cmv: arg count"
+ return 2
+ fi
+ if test -f "$2" -o -w "$2"
+ then echo "$2 exists"
+ return 2
+ fi
+ /bin/mv "$1" "$2"
+}
diff --git a/src/kash/funcs/dirs b/src/kash/funcs/dirs
new file mode 100644
index 0000000..68bb317
--- /dev/null
+++ b/src/kash/funcs/dirs
@@ -0,0 +1,74 @@
+# $NetBSD: dirs,v 1.7 1995/05/11 21:31:08 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# 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. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. 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.
+#
+# @(#)dirs 8.2 (Berkeley) 5/4/95
+
+# pushd, popd, and dirs --- written by Chris Bertin
+# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris
+# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW
+
+pushd () {
+ SAVE=`pwd`
+ if [ "$1" = "" ]
+ then if [ "$DSTACK" = "" ]
+ then echo "pushd: directory stack empty."
+ return 1
+ fi
+ set $DSTACK
+ cd $1 || return
+ shift 1
+ DSTACK="$*"
+ else cd $1 > /dev/null || return
+ fi
+ DSTACK="$SAVE $DSTACK"
+ dirs
+}
+
+popd () {
+ if [ "$DSTACK" = "" ]
+ then echo "popd: directory stack empty."
+ return 1
+ fi
+ set $DSTACK
+ cd $1
+ shift
+ DSTACK=$*
+ dirs
+}
+
+dirs () {
+ echo "`pwd` $DSTACK"
+ return 0
+}
diff --git a/src/kash/funcs/kill b/src/kash/funcs/kill
new file mode 100644
index 0000000..75b0180
--- /dev/null
+++ b/src/kash/funcs/kill
@@ -0,0 +1,50 @@
+# $NetBSD: kill,v 1.7 1995/05/11 21:31:10 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# 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. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. 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.
+#
+# @(#)kill 8.2 (Berkeley) 5/4/95
+
+# Convert job names to process ids and then run /bin/kill.
+
+kill() {
+ local args x
+ args=
+ for x in "$@"
+ do case $x in
+ %*) x=`jobid "$x"` ;;
+ esac
+ args="$args $x"
+ done
+ /bin/kill $args
+}
diff --git a/src/kash/funcs/login b/src/kash/funcs/login
new file mode 100644
index 0000000..7ae08b2
--- /dev/null
+++ b/src/kash/funcs/login
@@ -0,0 +1,39 @@
+# $NetBSD: login,v 1.7 1995/05/11 21:31:11 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# 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. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. 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.
+#
+# @(#)login 8.2 (Berkeley) 5/4/95
+
+# replaces the login builtin in the BSD shell
+login () exec login "$@"
diff --git a/src/kash/funcs/newgrp b/src/kash/funcs/newgrp
new file mode 100644
index 0000000..796a4f1
--- /dev/null
+++ b/src/kash/funcs/newgrp
@@ -0,0 +1,38 @@
+# $NetBSD: newgrp,v 1.7 1995/05/11 21:31:12 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# 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. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. 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.
+#
+# @(#)newgrp 8.2 (Berkeley) 5/4/95
+
+newgrp() exec newgrp "$@"
diff --git a/src/kash/funcs/popd b/src/kash/funcs/popd
new file mode 100644
index 0000000..b2b65d5
--- /dev/null
+++ b/src/kash/funcs/popd
@@ -0,0 +1,74 @@
+# $NetBSD: popd,v 1.7 1995/05/11 21:31:13 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# 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. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. 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.
+#
+# @(#)popd 8.2 (Berkeley) 5/4/95
+
+# pushd, popd, and dirs --- written by Chris Bertin
+# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris
+# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW
+
+pushd () {
+ SAVE=`pwd`
+ if [ "$1" = "" ]
+ then if [ "$DSTACK" = "" ]
+ then echo "pushd: directory stack empty."
+ return 1
+ fi
+ set $DSTACK
+ cd $1 || return
+ shift 1
+ DSTACK="$*"
+ else cd $1 > /dev/null || return
+ fi
+ DSTACK="$SAVE $DSTACK"
+ dirs
+}
+
+popd () {
+ if [ "$DSTACK" = "" ]
+ then echo "popd: directory stack empty."
+ return 1
+ fi
+ set $DSTACK
+ cd $1
+ shift
+ DSTACK=$*
+ dirs
+}
+
+dirs () {
+ echo "`pwd` $DSTACK"
+ return 0
+}
diff --git a/src/kash/funcs/pushd b/src/kash/funcs/pushd
new file mode 100644
index 0000000..b393038
--- /dev/null
+++ b/src/kash/funcs/pushd
@@ -0,0 +1,74 @@
+# $NetBSD: pushd,v 1.7 1995/05/11 21:31:15 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# 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. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. 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.
+#
+# @(#)pushd 8.2 (Berkeley) 5/4/95
+
+# pushd, popd, and dirs --- written by Chris Bertin
+# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris
+# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW
+
+pushd () {
+ SAVE=`pwd`
+ if [ "$1" = "" ]
+ then if [ "$DSTACK" = "" ]
+ then echo "pushd: directory stack empty."
+ return 1
+ fi
+ set $DSTACK
+ cd $1 || return
+ shift 1
+ DSTACK="$*"
+ else cd $1 > /dev/null || return
+ fi
+ DSTACK="$SAVE $DSTACK"
+ dirs
+}
+
+popd () {
+ if [ "$DSTACK" = "" ]
+ then echo "popd: directory stack empty."
+ return 1
+ fi
+ set $DSTACK
+ cd $1
+ shift
+ DSTACK=$*
+ dirs
+}
+
+dirs () {
+ echo "`pwd` $DSTACK"
+ return 0
+}
diff --git a/src/kash/funcs/suspend b/src/kash/funcs/suspend
new file mode 100644
index 0000000..8a4197d
--- /dev/null
+++ b/src/kash/funcs/suspend
@@ -0,0 +1,42 @@
+# $NetBSD: suspend,v 1.7 1995/05/11 21:31:17 christos Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# 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. All advertising materials mentioning features or use of this software
+# must display the following acknowledgement:
+# This product includes software developed by the University of
+# California, Berkeley and its contributors.
+# 4. 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.
+#
+# @(#)suspend 8.2 (Berkeley) 5/4/95
+
+suspend() {
+ local -
+ set +j
+ kill -TSTP 0
+}
diff --git a/src/kash/generated/arith.c b/src/kash/generated/arith.c
new file mode 100644
index 0000000..1f6b6e0
--- /dev/null
+++ b/src/kash/generated/arith.c
@@ -0,0 +1,748 @@
+#ifndef lint
+static const char yysccsid[] = "@(#)yaccpar 1.9 (Berkeley) 02/21/93";
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#define YYBYACC 1
+#define YYMAJOR 1
+#define YYMINOR 9
+#define YYPATCH 20091027
+
+#define YYEMPTY (-1)
+#define yyclearin (yychar = YYEMPTY)
+#define yyerrok (yyerrflag = 0)
+#define YYRECOVERING() (yyerrflag != 0)
+
+/* compatibility with bison */
+#ifdef YYPARSE_PARAM
+/* compatibility with FreeBSD */
+#ifdef YYPARSE_PARAM_TYPE
+#define YYPARSE_DECL() yyparse(YYPARSE_PARAM_TYPE YYPARSE_PARAM)
+#else
+#define YYPARSE_DECL() yyparse(void *YYPARSE_PARAM)
+#endif
+#else
+#define YYPARSE_DECL() yyparse(void)
+#endif /* YYPARSE_PARAM */
+
+extern int YYPARSE_DECL();
+
+static int yygrowstack(void);
+#define YYPREFIX "yy"
+/* $NetBSD: arith.y,v 1.17 2003/09/17 17:33:36 jmmv Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)arith.y 8.3 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: arith.y,v 1.17 2003/09/17 17:33:36 jmmv Exp $");
+#endif /* not lint */
+#endif
+
+#include <stdlib.h>
+#include "expand.h"
+#include "shell.h"
+#include "error.h"
+#include "output.h"
+#include "memalloc.h"
+#include "shinstance.h"
+
+shinstance *arith_psh;
+const char *arith_buf, *arith_startbuf;
+
+void yyerror(const char *);
+#ifdef TESTARITH
+int main(int , char *[]);
+int error(char *);
+#else
+# undef malloc
+# define malloc(cb) sh_malloc(NULL, (cb))
+# undef realloc
+# define realloc(pv,cb) sh_realloc(NULL, (pv), (cb))
+# undef free
+# define free(pv) sh_free(NULL, (pv))
+#endif
+
+#define ARITH_NUM 257
+#define ARITH_LPAREN 258
+#define ARITH_RPAREN 259
+#define ARITH_OR 260
+#define ARITH_AND 261
+#define ARITH_BOR 262
+#define ARITH_BXOR 263
+#define ARITH_BAND 264
+#define ARITH_EQ 265
+#define ARITH_NE 266
+#define ARITH_LT 267
+#define ARITH_GT 268
+#define ARITH_GE 269
+#define ARITH_LE 270
+#define ARITH_LSHIFT 271
+#define ARITH_RSHIFT 272
+#define ARITH_ADD 273
+#define ARITH_SUB 274
+#define ARITH_MUL 275
+#define ARITH_DIV 276
+#define ARITH_REM 277
+#define ARITH_UNARYMINUS 278
+#define ARITH_UNARYPLUS 279
+#define ARITH_NOT 280
+#define ARITH_BNOT 281
+#define YYERRCODE 256
+static const short yylhs[] = { -1,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1,
+};
+static const short yylen[] = { 2,
+ 1, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 2, 2, 2, 1,
+};
+static const short yydefred[] = { 0,
+ 25, 0, 0, 0, 0, 0, 0, 0, 0, 24,
+ 23, 21, 22, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 2, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 18, 19, 20,
+};
+static const short yydgoto[] = { 7,
+ 8,
+};
+static const short yysindex[] = { -255,
+ 0, -255, -255, -255, -255, -255, 0, -67, -85, 0,
+ 0, 0, 0, -255, -255, -255, -255, -255, -255, -255,
+ -255, -255, -255, -255, -255, -255, -255, -255, -255, -255,
+ -255, 0, -50, -34, -19, 141, -261, -233, -233, -223,
+ -223, -223, -223, -253, -253, -248, -248, 0, 0, 0,
+};
+static const short yyrindex[] = { 0,
+ 0, 0, 0, 0, 0, 0, 0, 30, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 143, 140, 136, 131, 125, 109, 117, 61,
+ 73, 85, 97, 33, 47, 1, 17, 0, 0, 0,
+};
+static const short yygindex[] = { 0,
+ 142,
+};
+#define YYTABLESIZE 418
+static const short yytable[] = { 0,
+ 16, 1, 2, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29, 30, 31, 17, 3, 4, 27,
+ 28, 29, 30, 31, 5, 6, 29, 30, 31, 1,
+ 0, 0, 14, 21, 22, 23, 24, 25, 26, 27,
+ 28, 29, 30, 31, 0, 0, 15, 25, 26, 27,
+ 28, 29, 30, 31, 0, 0, 0, 0, 0, 0,
+ 11, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 9, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 10, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 12, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 8, 0,
+ 0, 0, 0, 0, 0, 0, 13, 0, 0, 0,
+ 0, 0, 0, 0, 7, 0, 0, 0, 0, 0,
+ 6, 0, 0, 0, 0, 5, 0, 0, 0, 4,
+ 0, 0, 3, 9, 10, 11, 12, 13, 0, 0,
+ 0, 0, 0, 0, 0, 33, 34, 35, 36, 37,
+ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 32, 14, 15, 16, 17, 18, 19,
+ 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 31, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
+ 29, 30, 31, 17, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31, 0, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
+ 16, 16, 16, 16, 16, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 17, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 11,
+ 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+ 11, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 12, 12, 12, 12, 12,
+ 12, 12, 12, 12, 12, 12, 12, 8, 8, 8,
+ 8, 8, 8, 8, 8, 13, 13, 13, 13, 13,
+ 13, 13, 13, 7, 7, 7, 7, 7, 7, 6,
+ 6, 6, 6, 6, 5, 5, 5, 5, 4, 4,
+ 4, 3, 3, 0, 18, 19, 20, 21, 22, 23,
+ 24, 25, 26, 27, 28, 29, 30, 31,
+};
+static const short yycheck[] = { -1,
+ 0, 257, 258, 265, 266, 267, 268, 269, 270, 271,
+ 272, 273, 274, 275, 276, 277, 0, 273, 274, 273,
+ 274, 275, 276, 277, 280, 281, 275, 276, 277, 0,
+ -1, -1, 0, 267, 268, 269, 270, 271, 272, 273,
+ 274, 275, 276, 277, -1, -1, 0, 271, 272, 273,
+ 274, 275, 276, 277, -1, -1, -1, -1, -1, -1,
+ 0, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 0, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 0, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 0, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, 0, -1,
+ -1, -1, -1, -1, -1, -1, 0, -1, -1, -1,
+ -1, -1, -1, -1, 0, -1, -1, -1, -1, -1,
+ 0, -1, -1, -1, -1, 0, -1, -1, -1, 0,
+ -1, -1, 0, 2, 3, 4, 5, 6, -1, -1,
+ -1, -1, -1, -1, -1, 14, 15, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
+ 29, 30, 31, 259, 260, 261, 262, 263, 264, 265,
+ 266, 267, 268, 269, 270, 271, 272, 273, 274, 275,
+ 276, 277, 260, 261, 262, 263, 264, 265, 266, 267,
+ 268, 269, 270, 271, 272, 273, 274, 275, 276, 277,
+ 261, 262, 263, 264, 265, 266, 267, 268, 269, 270,
+ 271, 272, 273, 274, 275, 276, 277, 262, 263, 264,
+ 265, 266, 267, 268, 269, 270, 271, 272, 273, 274,
+ 275, 276, 277, 263, 264, 265, 266, 267, 268, 269,
+ 270, 271, 272, 273, 274, 275, 276, 277, -1, 259,
+ 260, 261, 262, 263, 264, 265, 266, 267, 268, 269,
+ 270, 271, 272, 273, 274, 259, 260, 261, 262, 263,
+ 264, 265, 266, 267, 268, 269, 270, 271, 272, 273,
+ 274, 259, 260, 261, 262, 263, 264, 265, 266, 267,
+ 268, 269, 270, 271, 272, 259, 260, 261, 262, 263,
+ 264, 265, 266, 267, 268, 269, 270, 271, 272, 259,
+ 260, 261, 262, 263, 264, 265, 266, 267, 268, 269,
+ 270, 259, 260, 261, 262, 263, 264, 265, 266, 267,
+ 268, 269, 270, 259, 260, 261, 262, 263, 264, 265,
+ 266, 267, 268, 269, 270, 259, 260, 261, 262, 263,
+ 264, 265, 266, 267, 268, 269, 270, 259, 260, 261,
+ 262, 263, 264, 265, 266, 259, 260, 261, 262, 263,
+ 264, 265, 266, 259, 260, 261, 262, 263, 264, 259,
+ 260, 261, 262, 263, 259, 260, 261, 262, 259, 260,
+ 261, 259, 260, -1, 264, 265, 266, 267, 268, 269,
+ 270, 271, 272, 273, 274, 275, 276, 277,
+};
+#define YYFINAL 7
+#ifndef YYDEBUG
+#define YYDEBUG 0
+#endif
+#define YYMAXTOKEN 281
+#if YYDEBUG
+static const char *yyname[] = {
+
+"end-of-file",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"ARITH_NUM","ARITH_LPAREN",
+"ARITH_RPAREN","ARITH_OR","ARITH_AND","ARITH_BOR","ARITH_BXOR","ARITH_BAND",
+"ARITH_EQ","ARITH_NE","ARITH_LT","ARITH_GT","ARITH_GE","ARITH_LE",
+"ARITH_LSHIFT","ARITH_RSHIFT","ARITH_ADD","ARITH_SUB","ARITH_MUL","ARITH_DIV",
+"ARITH_REM","ARITH_UNARYMINUS","ARITH_UNARYPLUS","ARITH_NOT","ARITH_BNOT",
+};
+static const char *yyrule[] = {
+"$accept : exp",
+"exp : expr",
+"expr : ARITH_LPAREN expr ARITH_RPAREN",
+"expr : expr ARITH_OR expr",
+"expr : expr ARITH_AND expr",
+"expr : expr ARITH_BOR expr",
+"expr : expr ARITH_BXOR expr",
+"expr : expr ARITH_BAND expr",
+"expr : expr ARITH_EQ expr",
+"expr : expr ARITH_GT expr",
+"expr : expr ARITH_GE expr",
+"expr : expr ARITH_LT expr",
+"expr : expr ARITH_LE expr",
+"expr : expr ARITH_NE expr",
+"expr : expr ARITH_LSHIFT expr",
+"expr : expr ARITH_RSHIFT expr",
+"expr : expr ARITH_ADD expr",
+"expr : expr ARITH_SUB expr",
+"expr : expr ARITH_MUL expr",
+"expr : expr ARITH_DIV expr",
+"expr : expr ARITH_REM expr",
+"expr : ARITH_NOT expr",
+"expr : ARITH_BNOT expr",
+"expr : ARITH_SUB expr",
+"expr : ARITH_ADD expr",
+"expr : ARITH_NUM",
+
+};
+#endif
+#ifndef YYSTYPE
+typedef int YYSTYPE;
+#endif
+#if YYDEBUG
+#include <stdio.h>
+#endif
+
+/* define the initial stack-sizes */
+#ifdef YYSTACKSIZE
+#undef YYMAXDEPTH
+#define YYMAXDEPTH YYSTACKSIZE
+#else
+#ifdef YYMAXDEPTH
+#define YYSTACKSIZE YYMAXDEPTH
+#else
+#define YYSTACKSIZE 500
+#define YYMAXDEPTH 500
+#endif
+#endif
+
+#define YYINITSTACKSIZE 500
+
+int yydebug;
+int yynerrs;
+int yyerrflag;
+int yychar;
+short *yyssp;
+YYSTYPE *yyvsp;
+YYSTYPE yyval;
+YYSTYPE yylval;
+
+/* variables for the parser stack */
+static short *yyss;
+static short *yysslim;
+static YYSTYPE *yyvs;
+static unsigned yystacksize;
+int
+arith(shinstance *psh, const char *s)
+{
+ long result;
+
+ INTOFF;
+/* todo lock */
+ arith_psh = psh;
+ arith_buf = arith_startbuf = s;
+ result = yyparse();
+ arith_lex_reset(); /* reprime lex */
+ arith_psh = NULL;
+/* todo unlock */
+ INTON;
+
+ return (result);
+}
+
+
+/*
+ * The exp(1) builtin.
+ */
+int
+expcmd(shinstance *psh, int argc, char **argv)
+{
+ const char *p;
+ char *concat;
+ char **ap;
+ long i;
+
+ if (argc > 1) {
+ p = argv[1];
+ if (argc > 2) {
+ /*
+ * concatenate arguments
+ */
+ STARTSTACKSTR(psh, concat);
+ ap = argv + 2;
+ for (;;) {
+ while (*p)
+ STPUTC(psh, *p++, concat);
+ if ((p = *ap++) == NULL)
+ break;
+ STPUTC(psh, ' ', concat);
+ }
+ STPUTC(psh, '\0', concat);
+ p = grabstackstr(psh, concat);
+ }
+ } else
+ p = "";
+
+ i = arith(psh, p);
+
+ out1fmt(psh, "%ld\n", i);
+ return (! i);
+}
+
+/*************************/
+#ifdef TEST_ARITH
+#include <stdio.h>
+main(argc, argv)
+ char *argv[];
+{
+ printf("%d\n", exp(argv[1]));
+}
+error(s)
+ char *s;
+{
+ fprintf(stderr, "exp: %s\n", s);
+ exit(1);
+}
+#endif
+
+void
+yyerror(const char *s)
+{
+ shinstance *psh = arith_psh;
+#ifndef YYBISON /* yyerrok references yyerrstatus which is a local variable in yyparse().*/
+ yyerrok;
+#endif
+ yyclearin;
+ arith_lex_reset(); /* reprime lex */
+/** @todo unlock */
+ error(psh, "arithmetic expression: %s: \"%s\"", s, arith_startbuf);
+ /* NOTREACHED */
+}
+/* allocate initial stack or double stack size, up to YYMAXDEPTH */
+static int yygrowstack(void)
+{
+ int i;
+ unsigned newsize;
+ short *newss;
+ YYSTYPE *newvs;
+
+ if ((newsize = yystacksize) == 0)
+ newsize = YYINITSTACKSIZE;
+ else if (newsize >= YYMAXDEPTH)
+ return -1;
+ else if ((newsize *= 2) > YYMAXDEPTH)
+ newsize = YYMAXDEPTH;
+
+ i = yyssp - yyss;
+ newss = (yyss != 0)
+ ? (short *)realloc(yyss, newsize * sizeof(*newss))
+ : (short *)malloc(newsize * sizeof(*newss));
+ if (newss == 0)
+ return -1;
+
+ yyss = newss;
+ yyssp = newss + i;
+ newvs = (yyvs != 0)
+ ? (YYSTYPE *)realloc(yyvs, newsize * sizeof(*newvs))
+ : (YYSTYPE *)malloc(newsize * sizeof(*newvs));
+ if (newvs == 0)
+ return -1;
+
+ yyvs = newvs;
+ yyvsp = newvs + i;
+ yystacksize = newsize;
+ yysslim = yyss + newsize - 1;
+ return 0;
+}
+
+#define YYABORT goto yyabort
+#define YYREJECT goto yyabort
+#define YYACCEPT goto yyaccept
+#define YYERROR goto yyerrlab
+
+int
+YYPARSE_DECL()
+{
+ int yym, yyn, yystate;
+#if YYDEBUG
+ const char *yys;
+
+ if ((yys = getenv("YYDEBUG")) != 0)
+ {
+ yyn = *yys;
+ if (yyn >= '0' && yyn <= '9')
+ yydebug = yyn - '0';
+ }
+#endif
+
+ yynerrs = 0;
+ yyerrflag = 0;
+ yychar = YYEMPTY;
+ yystate = 0;
+
+ if (yyss == NULL && yygrowstack()) goto yyoverflow;
+ yyssp = yyss;
+ yyvsp = yyvs;
+ yystate = 0;
+ *yyssp = 0;
+
+yyloop:
+ if ((yyn = yydefred[yystate]) != 0) goto yyreduce;
+ if (yychar < 0)
+ {
+ if ((yychar = yylex()) < 0) yychar = 0;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("%sdebug: state %d, reading %d (%s)\n",
+ YYPREFIX, yystate, yychar, yys);
+ }
+#endif
+ }
+ if ((yyn = yysindex[yystate]) && (yyn += yychar) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == yychar)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("%sdebug: state %d, shifting to state %d\n",
+ YYPREFIX, yystate, yytable[yyn]);
+#endif
+ if (yyssp >= yysslim && yygrowstack())
+ {
+ goto yyoverflow;
+ }
+ yystate = yytable[yyn];
+ *++yyssp = yytable[yyn];
+ *++yyvsp = yylval;
+ yychar = YYEMPTY;
+ if (yyerrflag > 0) --yyerrflag;
+ goto yyloop;
+ }
+ if ((yyn = yyrindex[yystate]) && (yyn += yychar) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == yychar)
+ {
+ yyn = yytable[yyn];
+ goto yyreduce;
+ }
+ if (yyerrflag) goto yyinrecovery;
+
+ yyerror("syntax error");
+
+ goto yyerrlab;
+
+yyerrlab:
+ ++yynerrs;
+
+yyinrecovery:
+ if (yyerrflag < 3)
+ {
+ yyerrflag = 3;
+ for (;;)
+ {
+ if ((yyn = yysindex[*yyssp]) && (yyn += YYERRCODE) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == YYERRCODE)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("%sdebug: state %d, error recovery shifting\
+ to state %d\n", YYPREFIX, *yyssp, yytable[yyn]);
+#endif
+ if (yyssp >= yysslim && yygrowstack())
+ {
+ goto yyoverflow;
+ }
+ yystate = yytable[yyn];
+ *++yyssp = yytable[yyn];
+ *++yyvsp = yylval;
+ goto yyloop;
+ }
+ else
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("%sdebug: error recovery discarding state %d\n",
+ YYPREFIX, *yyssp);
+#endif
+ if (yyssp <= yyss) goto yyabort;
+ --yyssp;
+ --yyvsp;
+ }
+ }
+ }
+ else
+ {
+ if (yychar == 0) goto yyabort;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("%sdebug: state %d, error recovery discards token %d (%s)\n",
+ YYPREFIX, yystate, yychar, yys);
+ }
+#endif
+ yychar = YYEMPTY;
+ goto yyloop;
+ }
+
+yyreduce:
+#if YYDEBUG
+ if (yydebug)
+ printf("%sdebug: state %d, reducing by rule %d (%s)\n",
+ YYPREFIX, yystate, yyn, yyrule[yyn]);
+#endif
+ yym = yylen[yyn];
+ if (yym)
+ yyval = yyvsp[1-yym];
+ else
+ memset(&yyval, 0, sizeof yyval);
+ switch (yyn)
+ {
+case 1:
+ {
+ return (yyvsp[0]);
+ }
+break;
+case 2:
+ { yyval = yyvsp[-1]; }
+break;
+case 3:
+ { yyval = yyvsp[-2] ? yyvsp[-2] : yyvsp[0] ? yyvsp[0] : 0; }
+break;
+case 4:
+ { yyval = yyvsp[-2] ? ( yyvsp[0] ? yyvsp[0] : 0 ) : 0; }
+break;
+case 5:
+ { yyval = yyvsp[-2] | yyvsp[0]; }
+break;
+case 6:
+ { yyval = yyvsp[-2] ^ yyvsp[0]; }
+break;
+case 7:
+ { yyval = yyvsp[-2] & yyvsp[0]; }
+break;
+case 8:
+ { yyval = yyvsp[-2] == yyvsp[0]; }
+break;
+case 9:
+ { yyval = yyvsp[-2] > yyvsp[0]; }
+break;
+case 10:
+ { yyval = yyvsp[-2] >= yyvsp[0]; }
+break;
+case 11:
+ { yyval = yyvsp[-2] < yyvsp[0]; }
+break;
+case 12:
+ { yyval = yyvsp[-2] <= yyvsp[0]; }
+break;
+case 13:
+ { yyval = yyvsp[-2] != yyvsp[0]; }
+break;
+case 14:
+ { yyval = yyvsp[-2] << yyvsp[0]; }
+break;
+case 15:
+ { yyval = yyvsp[-2] >> yyvsp[0]; }
+break;
+case 16:
+ { yyval = yyvsp[-2] + yyvsp[0]; }
+break;
+case 17:
+ { yyval = yyvsp[-2] - yyvsp[0]; }
+break;
+case 18:
+ { yyval = yyvsp[-2] * yyvsp[0]; }
+break;
+case 19:
+ {
+ if (yyvsp[0] == 0)
+ yyerror("division by zero");
+ yyval = yyvsp[-2] / yyvsp[0];
+ }
+break;
+case 20:
+ {
+ if (yyvsp[0] == 0)
+ yyerror("division by zero");
+ yyval = yyvsp[-2] % yyvsp[0];
+ }
+break;
+case 21:
+ { yyval = !(yyvsp[0]); }
+break;
+case 22:
+ { yyval = ~(yyvsp[0]); }
+break;
+case 23:
+ { yyval = -(yyvsp[0]); }
+break;
+case 24:
+ { yyval = yyvsp[0]; }
+break;
+ }
+ yyssp -= yym;
+ yystate = *yyssp;
+ yyvsp -= yym;
+ yym = yylhs[yyn];
+ if (yystate == 0 && yym == 0)
+ {
+#if YYDEBUG
+ if (yydebug)
+ printf("%sdebug: after reduction, shifting from state 0 to\
+ state %d\n", YYPREFIX, YYFINAL);
+#endif
+ yystate = YYFINAL;
+ *++yyssp = YYFINAL;
+ *++yyvsp = yyval;
+ if (yychar < 0)
+ {
+ if ((yychar = yylex()) < 0) yychar = 0;
+#if YYDEBUG
+ if (yydebug)
+ {
+ yys = 0;
+ if (yychar <= YYMAXTOKEN) yys = yyname[yychar];
+ if (!yys) yys = "illegal-symbol";
+ printf("%sdebug: state %d, reading %d (%s)\n",
+ YYPREFIX, YYFINAL, yychar, yys);
+ }
+#endif
+ }
+ if (yychar == 0) goto yyaccept;
+ goto yyloop;
+ }
+ if ((yyn = yygindex[yym]) && (yyn += yystate) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == yystate)
+ yystate = yytable[yyn];
+ else
+ yystate = yydgoto[yym];
+#if YYDEBUG
+ if (yydebug)
+ printf("%sdebug: after reduction, shifting from state %d \
+to state %d\n", YYPREFIX, *yyssp, yystate);
+#endif
+ if (yyssp >= yysslim && yygrowstack())
+ {
+ goto yyoverflow;
+ }
+ *++yyssp = (short) yystate;
+ *++yyvsp = yyval;
+ goto yyloop;
+
+yyoverflow:
+ yyerror("yacc stack overflow");
+
+yyabort:
+ return (1);
+
+yyaccept:
+ return (0);
+}
diff --git a/src/kash/generated/arith.h b/src/kash/generated/arith.h
new file mode 100644
index 0000000..e1c5356
--- /dev/null
+++ b/src/kash/generated/arith.h
@@ -0,0 +1,25 @@
+#define ARITH_NUM 257
+#define ARITH_LPAREN 258
+#define ARITH_RPAREN 259
+#define ARITH_OR 260
+#define ARITH_AND 261
+#define ARITH_BOR 262
+#define ARITH_BXOR 263
+#define ARITH_BAND 264
+#define ARITH_EQ 265
+#define ARITH_NE 266
+#define ARITH_LT 267
+#define ARITH_GT 268
+#define ARITH_GE 269
+#define ARITH_LE 270
+#define ARITH_LSHIFT 271
+#define ARITH_RSHIFT 272
+#define ARITH_ADD 273
+#define ARITH_SUB 274
+#define ARITH_MUL 275
+#define ARITH_DIV 276
+#define ARITH_REM 277
+#define ARITH_UNARYMINUS 278
+#define ARITH_UNARYPLUS 279
+#define ARITH_NOT 280
+#define ARITH_BNOT 281
diff --git a/src/kash/generated/arith_lex.c b/src/kash/generated/arith_lex.c
new file mode 100644
index 0000000..72a582c
--- /dev/null
+++ b/src/kash/generated/arith_lex.c
@@ -0,0 +1,1731 @@
+#line 2 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/out/darwin.x86/release/obj/kash/arith_lex.c"
+
+#line 4 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/out/darwin.x86/release/obj/kash/arith_lex.c"
+
+#define YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 5
+#define YY_FLEX_SUBMINOR_VERSION 33
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+/* First, we deal with platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if __STDC_VERSION__ >= 199901L
+
+/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
+ * if you want the limit (max/min) macros for int types.
+ */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <inttypes.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t;
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+#endif /* ! C99 */
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX (4294967295U)
+#endif
+
+#endif /* ! FLEXINT_H */
+
+#ifdef __cplusplus
+
+/* The "const" storage-class-modifier is valid. */
+#define YY_USE_CONST
+
+#else /* ! __cplusplus */
+
+#if __STDC__
+
+#define YY_USE_CONST
+
+#endif /* __STDC__ */
+#endif /* ! __cplusplus */
+
+#ifdef YY_USE_CONST
+#define yyconst const
+#else
+#define yyconst
+#endif
+
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+
+/* Promotes a possibly negative, possibly signed char to an unsigned
+ * integer for use as an array index. If the signed char is negative,
+ * we want to instead treat it as an 8-bit unsigned char, hence the
+ * double cast.
+ */
+#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c)
+
+/* Enter a start condition. This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN (yy_start) = 1 + 2 *
+
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state. The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START (((yy_start) - 1) / 2)
+#define YYSTATE YY_START
+
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE yyrestart(yyin )
+
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#define YY_BUF_SIZE 16384
+#endif
+
+/* The state buf must be large enough to hold one state per character in the main buffer.
+ */
+#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type))
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+extern int yyleng;
+
+extern FILE *yyin, *yyout;
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+ #define YY_LESS_LINENO(n)
+
+/* Return all but the first "n" matched characters back to the input stream. */
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ int yyless_macro_arg = (n); \
+ YY_LESS_LINENO(yyless_macro_arg);\
+ *yy_cp = (yy_hold_char); \
+ YY_RESTORE_YY_MORE_OFFSET \
+ (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \
+ YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+ } \
+ while ( 0 )
+
+#define unput(c) yyunput( c, (yytext_ptr) )
+
+/* The following is because we cannot portably get our hands on size_t
+ * (without autoconf's help, which isn't available because we want
+ * flex-generated scanners to compile on their own).
+ */
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef unsigned int yy_size_t;
+#endif
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+ {
+ FILE *yy_input_file;
+
+ char *yy_ch_buf; /* input buffer */
+ char *yy_buf_pos; /* current position in input buffer */
+
+ /* Size of input buffer in bytes, not including room for EOB
+ * characters.
+ */
+ yy_size_t yy_buf_size;
+
+ /* Number of characters read into yy_ch_buf, not including EOB
+ * characters.
+ */
+ int yy_n_chars;
+
+ /* Whether we "own" the buffer - i.e., we know we created it,
+ * and can realloc() it to grow it, and should free() it to
+ * delete it.
+ */
+ int yy_is_our_buffer;
+
+ /* Whether this is an "interactive" input source; if so, and
+ * if we're using stdio for input, then we want to use getc()
+ * instead of fread(), to make sure we stop fetching input after
+ * each newline.
+ */
+ int yy_is_interactive;
+
+ /* Whether we're considered to be at the beginning of a line.
+ * If so, '^' rules will be active on the next match, otherwise
+ * not.
+ */
+ int yy_at_bol;
+
+ int yy_bs_lineno; /**< The line count. */
+ int yy_bs_column; /**< The column count. */
+
+ /* Whether to try to fill the input buffer when we reach the
+ * end of it.
+ */
+ int yy_fill_buffer;
+
+ int yy_buffer_status;
+
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+ /* When an EOF's been seen but there's still some text to process
+ * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+ * shouldn't try reading from the input source any more. We might
+ * still have a bunch of tokens to match, though, because of
+ * possible backing-up.
+ *
+ * When we actually see the EOF, we change the status to "new"
+ * (via yyrestart()), so that the user can continue scanning by
+ * just pointing yyin at a new input file.
+ */
+#define YY_BUFFER_EOF_PENDING 2
+
+ };
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+/* Stack of input buffers. */
+static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */
+static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */
+static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ *
+ * Returns the top of the stack, or NULL.
+ */
+#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \
+ ? (yy_buffer_stack)[(yy_buffer_stack_top)] \
+ : NULL)
+
+/* Same as previous macro, but useful when we know that the buffer stack is not
+ * NULL or when we need an lvalue. For internal use only.
+ */
+#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)]
+
+/* yy_hold_char holds the character lost when yytext is formed. */
+static char yy_hold_char;
+static int yy_n_chars; /* number of characters read into yy_ch_buf */
+int yyleng;
+
+/* Points to current character in buffer. */
+static char *yy_c_buf_p = (char *) 0;
+static int yy_init = 0; /* whether we need to initialize */
+static int yy_start = 0; /* start state number */
+
+/* Flag which is used to allow yywrap()'s to do buffer switches
+ * instead of setting up a fresh yyin. A bit of a hack ...
+ */
+static int yy_did_buffer_switch_on_eof;
+
+void yyrestart (FILE *input_file );
+void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer );
+YY_BUFFER_STATE yy_create_buffer (FILE *file,int size );
+void yy_delete_buffer (YY_BUFFER_STATE b );
+void yy_flush_buffer (YY_BUFFER_STATE b );
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer );
+void yypop_buffer_state (void );
+
+static void yyensure_buffer_stack (void );
+static void yy_load_buffer_state (void );
+static void yy_init_buffer (YY_BUFFER_STATE b,FILE *file );
+
+#define YY_FLUSH_BUFFER yy_flush_buffer(YY_CURRENT_BUFFER )
+
+YY_BUFFER_STATE yy_scan_buffer (char *base,yy_size_t size );
+YY_BUFFER_STATE yy_scan_string (yyconst char *yy_str );
+YY_BUFFER_STATE yy_scan_bytes (yyconst char *bytes,int len );
+
+void *yyalloc (yy_size_t );
+void *yyrealloc (void *,yy_size_t );
+void yyfree (void * );
+
+#define yy_new_buffer yy_create_buffer
+
+#define yy_set_interactive(is_interactive) \
+ { \
+ if ( ! YY_CURRENT_BUFFER ){ \
+ yyensure_buffer_stack (); \
+ YY_CURRENT_BUFFER_LVALUE = \
+ yy_create_buffer(yyin,YY_BUF_SIZE ); \
+ } \
+ YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \
+ }
+
+#define yy_set_bol(at_bol) \
+ { \
+ if ( ! YY_CURRENT_BUFFER ){\
+ yyensure_buffer_stack (); \
+ YY_CURRENT_BUFFER_LVALUE = \
+ yy_create_buffer(yyin,YY_BUF_SIZE ); \
+ } \
+ YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \
+ }
+
+#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol)
+
+/* Begin user sect3 */
+
+#define yywrap(n) 1
+#define YY_SKIP_YYWRAP
+
+typedef unsigned char YY_CHAR;
+
+FILE *yyin = (FILE *) 0, *yyout = (FILE *) 0;
+
+typedef int yy_state_type;
+
+extern int yylineno;
+
+int yylineno = 1;
+
+extern char *yytext;
+#define yytext_ptr yytext
+
+static yy_state_type yy_get_previous_state (void );
+static yy_state_type yy_try_NUL_trans (yy_state_type current_state );
+static int yy_get_next_buffer (void );
+static void yy_fatal_error (yyconst char msg[] );
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+ (yytext_ptr) = yy_bp; \
+ yyleng = (size_t) (yy_cp - yy_bp); \
+ (yy_hold_char) = *yy_cp; \
+ *yy_cp = '\0'; \
+ (yy_c_buf_p) = yy_cp;
+
+#define YY_NUM_RULES 29
+#define YY_END_OF_BUFFER 30
+/* This struct is not used in this scanner,
+ but its presence is necessary. */
+struct yy_trans_info
+ {
+ flex_int32_t yy_verify;
+ flex_int32_t yy_nxt;
+ };
+static yyconst flex_int16_t yy_accept[39] =
+ { 0,
+ 0, 0, 30, 28, 1, 1, 27, 23, 12, 6,
+ 7, 21, 24, 25, 22, 3, 4, 17, 28, 15,
+ 5, 11, 10, 26, 14, 9, 3, 0, 4, 19,
+ 18, 13, 16, 20, 5, 8, 2, 0
+ } ;
+
+static yyconst flex_int32_t yy_ec[256] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 2, 3,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 2, 4, 1, 1, 1, 5, 6, 1, 7,
+ 8, 9, 10, 1, 11, 1, 12, 13, 14, 14,
+ 14, 14, 14, 14, 14, 15, 15, 1, 1, 16,
+ 17, 18, 1, 1, 19, 19, 19, 19, 19, 19,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 1, 1, 1, 21, 20, 1, 19, 19, 19, 19,
+
+ 19, 19, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 22,
+ 20, 20, 1, 23, 1, 24, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1
+ } ;
+
+static yyconst flex_int32_t yy_meta[25] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 2, 2, 2, 1, 1, 1, 2, 3,
+ 1, 3, 1, 1
+ } ;
+
+static yyconst flex_int16_t yy_base[41] =
+ { 0,
+ 0, 0, 47, 48, 48, 48, 29, 48, 39, 48,
+ 48, 48, 48, 48, 48, 12, 14, 14, 27, 15,
+ 0, 48, 20, 48, 48, 48, 22, 0, 24, 48,
+ 48, 48, 48, 48, 0, 48, 0, 48, 38, 40
+ } ;
+
+static yyconst flex_int16_t yy_def[41] =
+ { 0,
+ 38, 1, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 39, 38, 38, 38, 38, 38, 38, 40, 38, 38,
+ 38, 38, 38, 38, 39, 38, 40, 0, 38, 38
+ } ;
+
+static yyconst flex_int16_t yy_nxt[73] =
+ { 0,
+ 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15, 16, 17, 17, 18, 19, 20, 21, 21,
+ 22, 21, 23, 24, 27, 27, 29, 29, 29, 30,
+ 31, 33, 34, 28, 27, 27, 29, 29, 29, 35,
+ 35, 37, 36, 32, 26, 25, 38, 3, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38
+ } ;
+
+static yyconst flex_int16_t yy_chk[73] =
+ { 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 16, 16, 17, 17, 17, 18,
+ 18, 20, 20, 16, 27, 27, 29, 29, 29, 39,
+ 39, 40, 23, 19, 9, 7, 3, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38, 38, 38, 38, 38, 38, 38, 38, 38,
+ 38, 38
+ } ;
+
+static yy_state_type yy_last_accepting_state;
+static char *yy_last_accepting_cpos;
+
+extern int yy_flex_debug;
+int yy_flex_debug = 0;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+char *yytext;
+#line 1 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+#define YY_NO_INPUT 1
+/** @todo %option reentrant */
+#line 33 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+/* $NetBSD: arith_lex.l,v 1.13 2005/03/21 22:37:09 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)arith_lex.l 8.3 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: arith_lex.l,v 1.13 2005/03/21 22:37:09 dsl Exp $");
+#endif /* not lint */
+#endif
+
+#include <stdio.h>
+#include "arith.h"
+#include "error.h"
+#include "expand.h"
+#include "var.h"
+#include "shinstance.h"
+
+extern int yylval;
+extern shinstance *arith_psh;
+extern char *arith_buf, *arith_startbuf;
+#undef YY_INPUT
+#define YY_INPUT(buf,result,max) \
+ result = (*buf = *arith_buf++) ? 1 : YY_NULL;
+#define YY_NO_UNPUT
+
+/* Avoid unnecessary libc bits. */
+#undef ECHO
+#define ECHO \
+ do {} while (0)
+#undef stdin
+#define stdin \
+ NULL
+#undef stdout
+#define stdout \
+ NULL
+#define YY_FATAL_ERROR(msg) \
+ error(arith_psh, "arith: fatal error: %s", msg)
+#line 554 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/out/darwin.x86/release/obj/kash/arith_lex.c"
+
+#define INITIAL 0
+
+#ifndef YY_NO_UNISTD_H
+/* Special case for "unistd.h", since it is non-ANSI. We include it way
+ * down here because we want the user's section 1 to have been scanned first.
+ * The user has a chance to override it with an option.
+ */
+#include <unistd.h>
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+static int yy_init_globals (void );
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int yywrap (void );
+#else
+extern int yywrap (void );
+#endif
+#endif
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char *,yyconst char *,int );
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * );
+#endif
+
+#ifndef YY_NO_INPUT
+
+#ifdef __cplusplus
+static int yyinput (void );
+#else
+static int input (void );
+#endif
+
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#define YY_READ_BUF_SIZE 8192
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+#ifndef ECHO
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO (void) fwrite( yytext, yyleng, 1, yyout )
+#endif
+
+/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
+ { \
+ int c = '*'; \
+ size_t n; \
+ for ( n = 0; n < max_size && \
+ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
+ buf[n] = (char) c; \
+ if ( c == '\n' ) \
+ buf[n++] = (char) c; \
+ if ( c == EOF && ferror( yyin ) ) \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ result = n; \
+ } \
+ else \
+ { \
+ errno=0; \
+ while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \
+ { \
+ if( errno != EINTR) \
+ { \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ break; \
+ } \
+ errno=0; \
+ clearerr(yyin); \
+ } \
+ }\
+\
+
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg )
+#endif
+
+/* end tables serialization structures and prototypes */
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+
+extern int yylex (void);
+
+#define YY_DECL int yylex (void)
+#endif /* !YY_DECL */
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK break;
+#endif
+
+#define YY_RULE_SETUP \
+ YY_USER_ACTION
+
+/** The main scanner function which does all the work.
+ */
+YY_DECL
+{
+ register yy_state_type yy_current_state;
+ register char *yy_cp, *yy_bp;
+ register int yy_act;
+
+#line 104 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+
+#line 707 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/out/darwin.x86/release/obj/kash/arith_lex.c"
+
+ if ( !(yy_init) )
+ {
+ (yy_init) = 1;
+
+#ifdef YY_USER_INIT
+ YY_USER_INIT;
+#endif
+
+ if ( ! (yy_start) )
+ (yy_start) = 1; /* first start state */
+
+ if ( ! yyin )
+ yyin = stdin;
+
+ if ( ! yyout )
+ yyout = stdout;
+
+ if ( ! YY_CURRENT_BUFFER ) {
+ yyensure_buffer_stack ();
+ YY_CURRENT_BUFFER_LVALUE =
+ yy_create_buffer(yyin,YY_BUF_SIZE );
+ }
+
+ yy_load_buffer_state( );
+ }
+
+ while ( 1 ) /* loops until end-of-file is reached */
+ {
+ yy_cp = (yy_c_buf_p);
+
+ /* Support of yytext. */
+ *yy_cp = (yy_hold_char);
+
+ /* yy_bp points to the position in yy_ch_buf of the start of
+ * the current run.
+ */
+ yy_bp = yy_cp;
+
+ yy_current_state = (yy_start);
+yy_match:
+ do
+ {
+ register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)];
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 39 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ ++yy_cp;
+ }
+ while ( yy_current_state != 38 );
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+
+yy_find_action:
+ yy_act = yy_accept[yy_current_state];
+
+ YY_DO_BEFORE_ACTION;
+
+do_action: /* This label is used only to access EOF actions. */
+
+ switch ( yy_act )
+ { /* beginning of action switch */
+ case 0: /* must back up */
+ /* undo the effects of YY_DO_BEFORE_ACTION */
+ *yy_cp = (yy_hold_char);
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+ goto yy_find_action;
+
+case 1:
+/* rule 1 can match eol */
+YY_RULE_SETUP
+#line 105 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ ; }
+ YY_BREAK
+case 2:
+YY_RULE_SETUP
+#line 106 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ yylval = strtol(yytext, 0, 0); return(ARITH_NUM); }
+ YY_BREAK
+case 3:
+YY_RULE_SETUP
+#line 107 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ yylval = strtol(yytext, 0, 0); return(ARITH_NUM); }
+ YY_BREAK
+case 4:
+YY_RULE_SETUP
+#line 108 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ yylval = strtol(yytext, 0, 0); return(ARITH_NUM); }
+ YY_BREAK
+case 5:
+YY_RULE_SETUP
+#line 109 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ char *v = lookupvar(arith_psh, yytext);
+ if (v) {
+ yylval = strtol(v, &v, 0);
+ if (*v == 0)
+ return ARITH_NUM;
+ }
+ error(arith_psh, "arith: syntax error: \"%s\"", arith_startbuf);
+ }
+ YY_BREAK
+case 6:
+YY_RULE_SETUP
+#line 117 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_LPAREN); }
+ YY_BREAK
+case 7:
+YY_RULE_SETUP
+#line 118 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_RPAREN); }
+ YY_BREAK
+case 8:
+YY_RULE_SETUP
+#line 119 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_OR); }
+ YY_BREAK
+case 9:
+YY_RULE_SETUP
+#line 120 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_AND); }
+ YY_BREAK
+case 10:
+YY_RULE_SETUP
+#line 121 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_BOR); }
+ YY_BREAK
+case 11:
+YY_RULE_SETUP
+#line 122 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_BXOR); }
+ YY_BREAK
+case 12:
+YY_RULE_SETUP
+#line 123 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_BAND); }
+ YY_BREAK
+case 13:
+YY_RULE_SETUP
+#line 124 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_EQ); }
+ YY_BREAK
+case 14:
+YY_RULE_SETUP
+#line 125 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_NE); }
+ YY_BREAK
+case 15:
+YY_RULE_SETUP
+#line 126 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_GT); }
+ YY_BREAK
+case 16:
+YY_RULE_SETUP
+#line 127 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_GE); }
+ YY_BREAK
+case 17:
+YY_RULE_SETUP
+#line 128 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_LT); }
+ YY_BREAK
+case 18:
+YY_RULE_SETUP
+#line 129 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_LE); }
+ YY_BREAK
+case 19:
+YY_RULE_SETUP
+#line 130 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_LSHIFT); }
+ YY_BREAK
+case 20:
+YY_RULE_SETUP
+#line 131 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_RSHIFT); }
+ YY_BREAK
+case 21:
+YY_RULE_SETUP
+#line 132 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_MUL); }
+ YY_BREAK
+case 22:
+YY_RULE_SETUP
+#line 133 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_DIV); }
+ YY_BREAK
+case 23:
+YY_RULE_SETUP
+#line 134 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_REM); }
+ YY_BREAK
+case 24:
+YY_RULE_SETUP
+#line 135 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_ADD); }
+ YY_BREAK
+case 25:
+YY_RULE_SETUP
+#line 136 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_SUB); }
+ YY_BREAK
+case 26:
+YY_RULE_SETUP
+#line 137 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_BNOT); }
+ YY_BREAK
+case 27:
+YY_RULE_SETUP
+#line 138 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ return(ARITH_NOT); }
+ YY_BREAK
+case 28:
+YY_RULE_SETUP
+#line 139 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+{ error(arith_psh, "arith: syntax error: \"%s\"", arith_startbuf); }
+ YY_BREAK
+case 29:
+YY_RULE_SETUP
+#line 140 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+ECHO;
+ YY_BREAK
+#line 939 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/out/darwin.x86/release/obj/kash/arith_lex.c"
+case YY_STATE_EOF(INITIAL):
+ yyterminate();
+
+ case YY_END_OF_BUFFER:
+ {
+ /* Amount of text matched not including the EOB char. */
+ int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1;
+
+ /* Undo the effects of YY_DO_BEFORE_ACTION. */
+ *yy_cp = (yy_hold_char);
+ YY_RESTORE_YY_MORE_OFFSET
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW )
+ {
+ /* We're scanning a new file or input source. It's
+ * possible that this happened because the user
+ * just pointed yyin at a new source and called
+ * yylex(). If so, then we have to assure
+ * consistency between YY_CURRENT_BUFFER and our
+ * globals. Here is the right place to do so, because
+ * this is the first action (other than possibly a
+ * back-up) that will match for the new input source.
+ */
+ (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+ YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin;
+ YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL;
+ }
+
+ /* Note that here we test for yy_c_buf_p "<=" to the position
+ * of the first EOB in the buffer, since yy_c_buf_p will
+ * already have been incremented past the NUL character
+ * (since all states make transitions on EOB to the
+ * end-of-buffer state). Contrast this with the test
+ * in input().
+ */
+ if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+ { /* This was really a NUL. */
+ yy_state_type yy_next_state;
+
+ (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state( );
+
+ /* Okay, we're now positioned to make the NUL
+ * transition. We couldn't have
+ * yy_get_previous_state() go ahead and do it
+ * for us because it doesn't know how to deal
+ * with the possibility of jamming (and we don't
+ * want to build jamming into it because then it
+ * will run more slowly).
+ */
+
+ yy_next_state = yy_try_NUL_trans( yy_current_state );
+
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+
+ if ( yy_next_state )
+ {
+ /* Consume the NUL. */
+ yy_cp = ++(yy_c_buf_p);
+ yy_current_state = yy_next_state;
+ goto yy_match;
+ }
+
+ else
+ {
+ yy_cp = (yy_last_accepting_cpos);
+ yy_current_state = (yy_last_accepting_state);
+ goto yy_find_action;
+ }
+ }
+
+ else switch ( yy_get_next_buffer( ) )
+ {
+ case EOB_ACT_END_OF_FILE:
+ {
+ (yy_did_buffer_switch_on_eof) = 0;
+
+ if ( yywrap( ) )
+ {
+ /* Note: because we've taken care in
+ * yy_get_next_buffer() to have set up
+ * yytext, we can now set up
+ * yy_c_buf_p so that if some total
+ * hoser (like flex itself) wants to
+ * call the scanner after we return the
+ * YY_NULL, it'll still work - another
+ * YY_NULL will get returned.
+ */
+ (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ;
+
+ yy_act = YY_STATE_EOF(YY_START);
+ goto do_action;
+ }
+
+ else
+ {
+ if ( ! (yy_did_buffer_switch_on_eof) )
+ YY_NEW_FILE;
+ }
+ break;
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ (yy_c_buf_p) =
+ (yytext_ptr) + yy_amount_of_matched_text;
+
+ yy_current_state = yy_get_previous_state( );
+
+ yy_cp = (yy_c_buf_p);
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+ goto yy_match;
+
+ case EOB_ACT_LAST_MATCH:
+ (yy_c_buf_p) =
+ &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)];
+
+ yy_current_state = yy_get_previous_state( );
+
+ yy_cp = (yy_c_buf_p);
+ yy_bp = (yytext_ptr) + YY_MORE_ADJ;
+ goto yy_find_action;
+ }
+ break;
+ }
+
+ default:
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--no action found" );
+ } /* end of action switch */
+ } /* end of scanning one token */
+} /* end of yylex */
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ * EOB_ACT_LAST_MATCH -
+ * EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ * EOB_ACT_END_OF_FILE - end of file
+ */
+static int yy_get_next_buffer (void)
+{
+ register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
+ register char *source = (yytext_ptr);
+ register int number_to_move, i;
+ int ret_val;
+
+ if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] )
+ YY_FATAL_ERROR(
+ "fatal flex scanner internal error--end of buffer missed" );
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 )
+ { /* Don't try to fill the buffer, so this is an EOF. */
+ if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 )
+ {
+ /* We matched a single character, the EOB, so
+ * treat this as a final EOF.
+ */
+ return EOB_ACT_END_OF_FILE;
+ }
+
+ else
+ {
+ /* We matched some text prior to the EOB, first
+ * process it.
+ */
+ return EOB_ACT_LAST_MATCH;
+ }
+ }
+
+ /* Try to read more data. */
+
+ /* First move last chars to start of buffer. */
+ number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr)) - 1;
+
+ for ( i = 0; i < number_to_move; ++i )
+ *(dest++) = *(source++);
+
+ if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+ /* don't do the read, it's not guaranteed to return an EOF,
+ * just force an EOF
+ */
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0;
+
+ else
+ {
+ int num_to_read =
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;
+
+ while ( num_to_read <= 0 )
+ { /* Not enough room in the buffer - grow it. */
+
+ /* just a shorter name for the current buffer */
+ YY_BUFFER_STATE b = YY_CURRENT_BUFFER;
+
+ int yy_c_buf_p_offset =
+ (int) ((yy_c_buf_p) - b->yy_ch_buf);
+
+ if ( b->yy_is_our_buffer )
+ {
+ int new_size = b->yy_buf_size * 2;
+
+ if ( new_size <= 0 )
+ b->yy_buf_size += b->yy_buf_size / 8;
+ else
+ b->yy_buf_size *= 2;
+
+ b->yy_ch_buf = (char *)
+ /* Include room in for 2 EOB chars. */
+ yyrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 );
+ }
+ else
+ /* Can't grow it, we don't own it. */
+ b->yy_ch_buf = 0;
+
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR(
+ "fatal error - scanner input buffer overflow" );
+
+ (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+ num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size -
+ number_to_move - 1;
+
+ }
+
+ if ( num_to_read > YY_READ_BUF_SIZE )
+ num_to_read = YY_READ_BUF_SIZE;
+
+ /* Read in more data. */
+ YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]),
+ (yy_n_chars), num_to_read );
+
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ if ( (yy_n_chars) == 0 )
+ {
+ if ( number_to_move == YY_MORE_ADJ )
+ {
+ ret_val = EOB_ACT_END_OF_FILE;
+ yyrestart(yyin );
+ }
+
+ else
+ {
+ ret_val = EOB_ACT_LAST_MATCH;
+ YY_CURRENT_BUFFER_LVALUE->yy_buffer_status =
+ YY_BUFFER_EOF_PENDING;
+ }
+ }
+
+ else
+ ret_val = EOB_ACT_CONTINUE_SCAN;
+
+ (yy_n_chars) += number_to_move;
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR;
+ YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR;
+
+ (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0];
+
+ return ret_val;
+}
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+ static yy_state_type yy_get_previous_state (void)
+{
+ register yy_state_type yy_current_state;
+ register char *yy_cp;
+
+ yy_current_state = (yy_start);
+
+ for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp )
+ {
+ register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 39 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ }
+
+ return yy_current_state;
+}
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ * next_state = yy_try_NUL_trans( current_state );
+ */
+ static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state )
+{
+ register int yy_is_jam;
+ register char *yy_cp = (yy_c_buf_p);
+
+ register YY_CHAR yy_c = 1;
+ if ( yy_accept[yy_current_state] )
+ {
+ (yy_last_accepting_state) = yy_current_state;
+ (yy_last_accepting_cpos) = yy_cp;
+ }
+ while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+ {
+ yy_current_state = (int) yy_def[yy_current_state];
+ if ( yy_current_state >= 39 )
+ yy_c = yy_meta[(unsigned int) yy_c];
+ }
+ yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+ yy_is_jam = (yy_current_state == 38);
+
+ return yy_is_jam ? 0 : yy_current_state;
+}
+
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+ static int yyinput (void)
+#else
+ static int input (void)
+#endif
+
+{
+ int c;
+
+ *(yy_c_buf_p) = (yy_hold_char);
+
+ if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR )
+ {
+ /* yy_c_buf_p now points to the character we want to return.
+ * If this occurs *before* the EOB characters, then it's a
+ * valid NUL; if not, then we've hit the end of the buffer.
+ */
+ if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] )
+ /* This was really a NUL. */
+ *(yy_c_buf_p) = '\0';
+
+ else
+ { /* need more input */
+ int offset = (yy_c_buf_p) - (yytext_ptr);
+ ++(yy_c_buf_p);
+
+ switch ( yy_get_next_buffer( ) )
+ {
+ case EOB_ACT_LAST_MATCH:
+ /* This happens because yy_g_n_b()
+ * sees that we've accumulated a
+ * token and flags that we need to
+ * try matching the token before
+ * proceeding. But for input(),
+ * there's no matching to consider.
+ * So convert the EOB_ACT_LAST_MATCH
+ * to EOB_ACT_END_OF_FILE.
+ */
+
+ /* Reset buffer status. */
+ yyrestart(yyin );
+
+ /*FALLTHROUGH*/
+
+ case EOB_ACT_END_OF_FILE:
+ {
+ if ( yywrap( ) )
+ return 0;
+
+ if ( ! (yy_did_buffer_switch_on_eof) )
+ YY_NEW_FILE;
+#ifdef __cplusplus
+ return yyinput();
+#else
+ return input();
+#endif
+ }
+
+ case EOB_ACT_CONTINUE_SCAN:
+ (yy_c_buf_p) = (yytext_ptr) + offset;
+ break;
+ }
+ }
+ }
+
+ c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */
+ *(yy_c_buf_p) = '\0'; /* preserve yytext */
+ (yy_hold_char) = *++(yy_c_buf_p);
+
+ return c;
+}
+#endif /* ifndef YY_NO_INPUT */
+
+/** Immediately switch to a different input stream.
+ * @param input_file A readable stream.
+ *
+ * @note This function does not reset the start condition to @c INITIAL .
+ */
+ void yyrestart (FILE * input_file )
+{
+
+ if ( ! YY_CURRENT_BUFFER ){
+ yyensure_buffer_stack ();
+ YY_CURRENT_BUFFER_LVALUE =
+ yy_create_buffer(yyin,YY_BUF_SIZE );
+ }
+
+ yy_init_buffer(YY_CURRENT_BUFFER,input_file );
+ yy_load_buffer_state( );
+}
+
+/** Switch to a different input buffer.
+ * @param new_buffer The new input buffer.
+ *
+ */
+ void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer )
+{
+
+ /* TODO. We should be able to replace this entire function body
+ * with
+ * yypop_buffer_state();
+ * yypush_buffer_state(new_buffer);
+ */
+ yyensure_buffer_stack ();
+ if ( YY_CURRENT_BUFFER == new_buffer )
+ return;
+
+ if ( YY_CURRENT_BUFFER )
+ {
+ /* Flush out information for old buffer. */
+ *(yy_c_buf_p) = (yy_hold_char);
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ YY_CURRENT_BUFFER_LVALUE = new_buffer;
+ yy_load_buffer_state( );
+
+ /* We don't actually know whether we did this switch during
+ * EOF (yywrap()) processing, but the only time this flag
+ * is looked at is after yywrap() is called, so it's safe
+ * to go ahead and always set it.
+ */
+ (yy_did_buffer_switch_on_eof) = 1;
+}
+
+static void yy_load_buffer_state (void)
+{
+ (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+ (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos;
+ yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file;
+ (yy_hold_char) = *(yy_c_buf_p);
+}
+
+/** Allocate and initialize an input buffer state.
+ * @param file A readable stream.
+ * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE.
+ *
+ * @return the allocated buffer state.
+ */
+ YY_BUFFER_STATE yy_create_buffer (FILE * file, int size )
+{
+ YY_BUFFER_STATE b;
+
+ b = (YY_BUFFER_STATE) yyalloc(sizeof( struct yy_buffer_state ) );
+ if ( ! b )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_buf_size = size;
+
+ /* yy_ch_buf has to be 2 characters longer than the size given because
+ * we need to put in 2 end-of-buffer characters.
+ */
+ b->yy_ch_buf = (char *) yyalloc(b->yy_buf_size + 2 );
+ if ( ! b->yy_ch_buf )
+ YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" );
+
+ b->yy_is_our_buffer = 1;
+
+ yy_init_buffer(b,file );
+
+ return b;
+}
+
+/** Destroy the buffer.
+ * @param b a buffer created with yy_create_buffer()
+ *
+ */
+ void yy_delete_buffer (YY_BUFFER_STATE b )
+{
+
+ if ( ! b )
+ return;
+
+ if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */
+ YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0;
+
+ if ( b->yy_is_our_buffer )
+ yyfree((void *) b->yy_ch_buf );
+
+ yyfree((void *) b );
+}
+
+/* Initializes or reinitializes a buffer.
+ * This function is sometimes called more than once on the same buffer,
+ * such as during a yyrestart() or at EOF.
+ */
+ static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file )
+
+{
+ int oerrno = errno;
+
+ yy_flush_buffer(b );
+
+ b->yy_input_file = file;
+ b->yy_fill_buffer = 1;
+
+ /* If b is the current buffer, then yy_init_buffer was _probably_
+ * called from yyrestart() or through yy_get_next_buffer.
+ * In that case, we don't want to reset the lineno or column.
+ */
+ if (b != YY_CURRENT_BUFFER){
+ b->yy_bs_lineno = 1;
+ b->yy_bs_column = 0;
+ }
+
+ b->yy_is_interactive = 0;
+
+ errno = oerrno;
+}
+
+/** Discard all buffered characters. On the next scan, YY_INPUT will be called.
+ * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER.
+ *
+ */
+ void yy_flush_buffer (YY_BUFFER_STATE b )
+{
+ if ( ! b )
+ return;
+
+ b->yy_n_chars = 0;
+
+ /* We always need two end-of-buffer characters. The first causes
+ * a transition to the end-of-buffer state. The second causes
+ * a jam in that state.
+ */
+ b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+ b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+ b->yy_buf_pos = &b->yy_ch_buf[0];
+
+ b->yy_at_bol = 1;
+ b->yy_buffer_status = YY_BUFFER_NEW;
+
+ if ( b == YY_CURRENT_BUFFER )
+ yy_load_buffer_state( );
+}
+
+/** Pushes the new state onto the stack. The new state becomes
+ * the current state. This function will allocate the stack
+ * if necessary.
+ * @param new_buffer The new state.
+ *
+ */
+void yypush_buffer_state (YY_BUFFER_STATE new_buffer )
+{
+ if (new_buffer == NULL)
+ return;
+
+ yyensure_buffer_stack();
+
+ /* This block is copied from yy_switch_to_buffer. */
+ if ( YY_CURRENT_BUFFER )
+ {
+ /* Flush out information for old buffer. */
+ *(yy_c_buf_p) = (yy_hold_char);
+ YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p);
+ YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars);
+ }
+
+ /* Only push if top exists. Otherwise, replace top. */
+ if (YY_CURRENT_BUFFER)
+ (yy_buffer_stack_top)++;
+ YY_CURRENT_BUFFER_LVALUE = new_buffer;
+
+ /* copied from yy_switch_to_buffer. */
+ yy_load_buffer_state( );
+ (yy_did_buffer_switch_on_eof) = 1;
+}
+
+/** Removes and deletes the top of the stack, if present.
+ * The next element becomes the new top.
+ *
+ */
+void yypop_buffer_state (void)
+{
+ if (!YY_CURRENT_BUFFER)
+ return;
+
+ yy_delete_buffer(YY_CURRENT_BUFFER );
+ YY_CURRENT_BUFFER_LVALUE = NULL;
+ if ((yy_buffer_stack_top) > 0)
+ --(yy_buffer_stack_top);
+
+ if (YY_CURRENT_BUFFER) {
+ yy_load_buffer_state( );
+ (yy_did_buffer_switch_on_eof) = 1;
+ }
+}
+
+/* Allocates the stack if it does not exist.
+ * Guarantees space for at least one push.
+ */
+static void yyensure_buffer_stack (void)
+{
+ int num_to_alloc;
+
+ if (!(yy_buffer_stack)) {
+
+ /* First allocation is just for 2 elements, since we don't know if this
+ * scanner will even need a stack. We use 2 instead of 1 to avoid an
+ * immediate realloc on the next call.
+ */
+ num_to_alloc = 1;
+ (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc
+ (num_to_alloc * sizeof(struct yy_buffer_state*)
+ );
+
+ memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*));
+
+ (yy_buffer_stack_max) = num_to_alloc;
+ (yy_buffer_stack_top) = 0;
+ return;
+ }
+
+ if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){
+
+ /* Increase the buffer to prepare for a possible push. */
+ int grow_size = 8 /* arbitrary grow size */;
+
+ num_to_alloc = (yy_buffer_stack_max) + grow_size;
+ (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc
+ ((yy_buffer_stack),
+ num_to_alloc * sizeof(struct yy_buffer_state*)
+ );
+
+ /* zero only the new slots.*/
+ memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*));
+ (yy_buffer_stack_max) = num_to_alloc;
+ }
+}
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+static void yy_fatal_error (yyconst char* msg )
+{
+ (void) fprintf( stderr, "%s\n", msg );
+ exit( YY_EXIT_FAILURE );
+}
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+ do \
+ { \
+ /* Undo effects of setting up yytext. */ \
+ int yyless_macro_arg = (n); \
+ YY_LESS_LINENO(yyless_macro_arg);\
+ yytext[yyleng] = (yy_hold_char); \
+ (yy_c_buf_p) = yytext + yyless_macro_arg; \
+ (yy_hold_char) = *(yy_c_buf_p); \
+ *(yy_c_buf_p) = '\0'; \
+ yyleng = yyless_macro_arg; \
+ } \
+ while ( 0 )
+
+/* Accessor methods (get/set functions) to struct members. */
+
+/** Get the current token.
+ *
+ */
+
+static int yy_init_globals (void)
+{
+ /* Initialization is the same as for the non-reentrant scanner.
+ * This function is called from yylex_destroy(), so don't allocate here.
+ */
+
+ (yy_buffer_stack) = 0;
+ (yy_buffer_stack_top) = 0;
+ (yy_buffer_stack_max) = 0;
+ (yy_c_buf_p) = (char *) 0;
+ (yy_init) = 0;
+ (yy_start) = 0;
+
+/* Defined in main.c */
+#ifdef YY_STDINIT
+ yyin = stdin;
+ yyout = stdout;
+#else
+ yyin = (FILE *) 0;
+ yyout = (FILE *) 0;
+#endif
+
+ /* For future reference: Set errno on error, since we are called by
+ * yylex_init()
+ */
+ return 0;
+}
+
+/* yylex_destroy is for both reentrant and non-reentrant scanners. */
+int yylex_destroy (void)
+{
+
+ /* Pop the buffer stack, destroying each element. */
+ while(YY_CURRENT_BUFFER){
+ yy_delete_buffer(YY_CURRENT_BUFFER );
+ YY_CURRENT_BUFFER_LVALUE = NULL;
+ yypop_buffer_state();
+ }
+
+ /* Destroy the stack itself. */
+ yyfree((yy_buffer_stack) );
+ (yy_buffer_stack) = NULL;
+
+ /* Reset the globals. This is important in a non-reentrant scanner so the next time
+ * yylex() is called, initialization will occur. */
+ yy_init_globals( );
+
+ return 0;
+}
+
+/*
+ * Internal utility routines.
+ */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char* s1, yyconst char * s2, int n )
+{
+ register int i;
+ for ( i = 0; i < n; ++i )
+ s1[i] = s2[i];
+}
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * s )
+{
+ register int n;
+ for ( n = 0; s[n]; ++n )
+ ;
+
+ return n;
+}
+#endif
+
+#define YYTABLES_NAME "yytables"
+
+#line 140 "/Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/arith_lex.l"
+
+
+
+void
+arith_lex_reset() {
+#ifdef YY_NEW_FILE
+ YY_NEW_FILE;
+#endif
+}
+
+void *
+yyalloc(yy_size_t cb)
+{
+ return sh_malloc(NULL, cb);
+}
+
+void *
+yyrealloc(void *pv,yy_size_t cb)
+{
+ return sh_realloc(NULL, pv, cb);
+}
+
+void
+yyfree(void *pv)
+{
+ sh_free(NULL, pv);
+}
+
+
diff --git a/src/kash/generated/builtins.c b/src/kash/generated/builtins.c
new file mode 100644
index 0000000..f9c2f60
--- /dev/null
+++ b/src/kash/generated/builtins.c
@@ -0,0 +1,63 @@
+/*
+ * This file was generated by the mkbuiltins program.
+ */
+
+#include "shell.h"
+#include "builtins.h"
+
+const struct builtincmd builtincmd[] = {
+
+ { "command", bltincmd },
+ { "bg", bgcmd },
+ { "cd", cdcmd },
+ { "chdir", cdcmd },
+ { "echo", echocmd },
+ { "exp", expcmd },
+ { "let", expcmd },
+ { "false", falsecmd },
+ { "fc", histcmd },
+ { "inputrc", inputrc },
+ { "fg", fgcmd },
+ { "getopts", getoptscmd },
+ { "hash", hashcmd },
+ { "jobid", jobidcmd },
+ { "jobs", jobscmd },
+ { "local", localcmd },
+#ifndef SMALL
+ { "printf", printfcmd },
+#endif
+ { "pwd", pwdcmd },
+ { "read", readcmd },
+ { "setvar", setvarcmd },
+ { "true", truecmd },
+ { "type", typecmd },
+ { "umask", umaskcmd },
+ { "unalias", unaliascmd },
+ { "wait", waitcmd },
+ { "alias", aliascmd },
+ { "ulimit", ulimitcmd },
+ { "test", testcmd },
+ { "[", testcmd },
+ { "kill", killcmd },
+ { "wordexp", wordexpcmd },
+ { 0, 0 },
+};
+
+const struct builtincmd splbltincmd[] = {
+ { "break", breakcmd },
+ { "continue", breakcmd },
+ { ".", dotcmd },
+ { "eval", evalcmd },
+ { "exec", execcmd },
+ { "exit", exitcmd },
+ { "export", exportcmd },
+ { "readonly", exportcmd },
+ { "return", returncmd },
+ { "set", setcmd },
+ { "shift", shiftcmd },
+ { "times", timescmd },
+ { "trap", trapcmd },
+ { ":", truecmd },
+ { "unset", unsetcmd },
+ { 0, 0 },
+};
diff --git a/src/kash/generated/builtins.h b/src/kash/generated/builtins.h
new file mode 100644
index 0000000..580d4b4
--- /dev/null
+++ b/src/kash/generated/builtins.h
@@ -0,0 +1,57 @@
+/*
+ * This file was generated by the mkbuiltins program.
+ */
+
+#include "shtypes.h"
+
+struct builtincmd {
+ const char *name;
+ int (*builtin)(shinstance *, int, char **);
+};
+
+extern const struct builtincmd builtincmd[];
+extern const struct builtincmd splbltincmd[];
+
+
+int bltincmd(shinstance *, int, char **);
+int bgcmd(shinstance *, int, char **);
+int breakcmd(shinstance *, int, char **);
+int cdcmd(shinstance *, int, char **);
+int dotcmd(shinstance *, int, char **);
+int echocmd(shinstance *, int, char **);
+int evalcmd(shinstance *, int, char **);
+int execcmd(shinstance *, int, char **);
+int exitcmd(shinstance *, int, char **);
+int expcmd(shinstance *, int, char **);
+int exportcmd(shinstance *, int, char **);
+int falsecmd(shinstance *, int, char **);
+int histcmd(shinstance *, int, char **);
+int inputrc(shinstance *, int, char **);
+int fgcmd(shinstance *, int, char **);
+int getoptscmd(shinstance *, int, char **);
+int hashcmd(shinstance *, int, char **);
+int jobidcmd(shinstance *, int, char **);
+int jobscmd(shinstance *, int, char **);
+int localcmd(shinstance *, int, char **);
+#ifndef SMALL
+int printfcmd(shinstance *, int, char **);
+#endif
+int pwdcmd(shinstance *, int, char **);
+int readcmd(shinstance *, int, char **);
+int returncmd(shinstance *, int, char **);
+int setcmd(shinstance *, int, char **);
+int setvarcmd(shinstance *, int, char **);
+int shiftcmd(shinstance *, int, char **);
+int timescmd(shinstance *, int, char **);
+int trapcmd(shinstance *, int, char **);
+int truecmd(shinstance *, int, char **);
+int typecmd(shinstance *, int, char **);
+int umaskcmd(shinstance *, int, char **);
+int unaliascmd(shinstance *, int, char **);
+int unsetcmd(shinstance *, int, char **);
+int waitcmd(shinstance *, int, char **);
+int aliascmd(shinstance *, int, char **);
+int ulimitcmd(shinstance *, int, char **);
+int testcmd(shinstance *, int, char **);
+int killcmd(shinstance *, int, char **);
+int wordexpcmd(shinstance *, int, char **);
diff --git a/src/kash/generated/init.c b/src/kash/generated/init.c
new file mode 100644
index 0000000..5b0f781
--- /dev/null
+++ b/src/kash/generated/init.c
@@ -0,0 +1,292 @@
+/*
+ * This file was generated by the mkinit program.
+ */
+
+#include "shell.h"
+#include "mystring.h"
+#include "init.h"
+#include "eval.h"
+#include <stdio.h>
+#include "input.h"
+#include "error.h"
+#include <stdlib.h>
+#include "options.h"
+#include "output.h"
+#include "memalloc.h"
+#include "redir.h"
+#include <signal.h>
+#include "trap.h"
+#include "var.h"
+#include "shinstance.h"
+
+
+
+#undef PROFILE
+#define PROFILE 0
+#undef SIGSSIZE
+#define SIGSSIZE (sizeof(sigs)/sizeof(sigs[0]))
+#undef MAXPWD
+#define MAXPWD 256
+#undef ALL
+#define ALL (E_OPEN|E_CREAT|E_EXEC)
+#undef EV_EXIT
+#define EV_EXIT 01 /* exit after evaluating tree */
+#undef EV_TESTED
+#define EV_TESTED 02 /* exit status is checked; ignore -e flag */
+#undef EV_BACKCMD
+#define EV_BACKCMD 04 /* command executing within back quotes */
+#undef NEWARGS
+#define NEWARGS 5
+#undef MAXHISTLOOPS
+#define MAXHISTLOOPS 4 /* max recursions through fc */
+#undef DEFEDITOR
+#define DEFEDITOR "ed" /* default editor *should* be $EDITOR */
+#undef editing
+#define editing (Eflag(psh) || Vflag(psh))
+#undef EOF_NLEFT
+#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */
+#undef DEFINE_OPTIONS
+#define DEFINE_OPTIONS
+#undef BLOCK_OUT
+#define BLOCK_OUT -2 /* output to a fixed block of memory */
+#undef OUTPUT_ERR
+#define OUTPUT_ERR 01 /* error occurred on output */
+#undef TEMPSIZE
+#define TEMPSIZE 32
+#undef HAVE_VASPRINTF
+#define HAVE_VASPRINTF 1
+#undef EOFMARKLEN
+#define EOFMARKLEN 79
+#undef OPENBRACE
+#define OPENBRACE '{'
+#undef CLOSEBRACE
+#define CLOSEBRACE '}'
+#undef EMPTY
+#define EMPTY -2 /* marks an unused slot in redirtab */
+#undef S_DFL
+#define S_DFL 1 /* default signal handling (SIG_DFL) */
+#undef S_CATCH
+#define S_CATCH 2 /* signal is caught */
+#undef S_IGN
+#define S_IGN 3 /* signal is ignored (SIG_IGN) */
+#undef S_HARD_IGN
+#define S_HARD_IGN 4 /* signal is ignored permenantly */
+#undef S_RESET
+#define S_RESET 5 /* temporary - to reset a hard ignored sig */
+#undef INCL_BASE
+#define INCL_BASE
+#undef LIBPATHSTRICT
+#define LIBPATHSTRICT 3
+#undef QHINF_EXEINFO
+#define QHINF_EXEINFO 1 /* NE exeinfo. */
+#undef QHINF_READRSRCTBL
+#define QHINF_READRSRCTBL 2 /* Reads from the resource table. */
+#undef QHINF_READFILE
+#define QHINF_READFILE 3 /* Reads from the executable file. */
+#undef QHINF_LIBPATHLENGTH
+#define QHINF_LIBPATHLENGTH 4 /* Gets the libpath length. */
+#undef QHINF_LIBPATH
+#define QHINF_LIBPATH 5 /* Gets the entire libpath. */
+#undef QHINF_FIXENTRY
+#define QHINF_FIXENTRY 6 /* NE only */
+#undef QHINF_STE
+#define QHINF_STE 7 /* NE only */
+#undef QHINF_MAPSEL
+#define QHINF_MAPSEL 8 /* NE only */
+#undef SET_LEN
+#define SET_LEN 6 /* initial # of bitcmd struct to malloc */
+#undef SET_LEN_INCR
+#define SET_LEN_INCR 4 /* # of bitcmd structs to add as needed */
+#undef CMD2_CLR
+#define CMD2_CLR 0x01
+#undef CMD2_SET
+#define CMD2_SET 0x02
+#undef CMD2_GBITS
+#define CMD2_GBITS 0x04
+#undef CMD2_OBITS
+#define CMD2_OBITS 0x08
+#undef CMD2_UBITS
+#define CMD2_UBITS 0x10
+#undef STANDARD_BITS
+#define STANDARD_BITS (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
+#undef SHMEMHDR_MAGIC_FREE
+#define SHMEMHDR_MAGIC_FREE 0xbeeff00d
+#undef SHMEMHDR_MAGIC_USED
+#define SHMEMHDR_MAGIC_USED 0xfeedface
+#undef SHMEMCHUNK_MAGIC
+#define SHMEMCHUNK_MAGIC 0x12345678
+#undef SHHEAP_MIN_CHUNK
+#define SHHEAP_MIN_CHUNK 0x80000 //(1024*1024)
+#undef SHFILE_MAX
+#define SHFILE_MAX 1024
+#undef SHFILE_GROW
+#define SHFILE_GROW 64
+#undef SHFILE_UNIX_MIN_FD
+#define SHFILE_UNIX_MIN_FD 32
+#undef SHFILE_MAX_PATH
+#define SHFILE_MAX_PATH 4096
+#undef YY_NO_UNPUT
+#define YY_NO_UNPUT
+
+
+
+extern void rmaliases(shinstance *psh);
+
+extern void deletefuncs(struct shinstance *);
+extern void hash_special_builtins(struct shinstance *);
+
+
+
+/*
+ * Initialization code.
+ */
+
+void
+init(shinstance *psh) {
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/exec.c: */
+ {
+ hash_special_builtins(psh);
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/input.c: */
+ {
+ psh->basepf.nextc = psh->basepf.buf = psh->basebuf;
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/options.c: */
+ {
+ memcpy(&psh->optlist[0], &ro_optlist[0], sizeof(psh->optlist));
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/var.c: */
+ {
+ char **envp;
+
+ initvar(psh);
+ for (envp = sh_environ(psh) ; *envp ; envp++) {
+ if (strchr(*envp, '=')) {
+ setvareq(psh, *envp, VEXPORT|VTEXTFIXED);
+ }
+ }
+ }
+}
+
+
+
+/*
+ * This routine is called when an error or an interrupt occurs in an
+ * interactive shell and control is returned to the main command loop.
+ */
+
+void
+reset(shinstance *psh) {
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/eval.c: */
+ {
+ psh->evalskip = 0;
+ psh->loopnest = 0;
+ psh->funcnest = 0;
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/input.c: */
+ {
+ if (psh->exception != EXSHELLPROC)
+ psh->parselleft = psh->parsenleft = 0; /* clear input buffer */
+ popallfiles(psh);
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/output.c: */
+ {
+ psh->out1 = &psh->output;
+ psh->out2 = &psh->errout;
+ if (psh->memout.buf != NULL) {
+ ckfree(psh, psh->memout.buf);
+ psh->memout.buf = NULL;
+ psh->memout.nextc = NULL;
+ }
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/parser.c: */
+ {
+ psh->tokpushback = 0;
+ psh->checkkwd = 0;
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/redir.c: */
+ {
+ while (psh->redirlist)
+ popredir(psh);
+ }
+}
+
+
+
+/*
+ * This routine is called to initialize the shell to run a shell procedure.
+ */
+
+void
+initshellproc(shinstance *psh) {
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/alias.c: */
+ {
+ rmaliases(psh);
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/eval.c: */
+ {
+ psh->exitstatus = 0;
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/exec.c: */
+ {
+ deletefuncs(psh);
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/input.c: */
+ {
+ popallfiles(psh);
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/jobs.c: */
+ {
+ psh->backgndpid = -1;
+#if JOBS
+ psh->jobctl = 0;
+#endif
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/options.c: */
+ {
+ int i;
+
+ for (i = 0; psh->optlist[i].name; i++)
+ psh->optlist[i].val = 0;
+# if DEBUG == 2
+ debug(psh) = 1;
+# endif
+ optschanged(psh);
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/redir.c: */
+ {
+ clearredir(psh);
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/trap.c: */
+ {
+ char *sm;
+
+ clear_traps(psh);
+ for (sm = psh->sigmode ; sm < psh->sigmode + NSIG ; sm++) {
+ if (*sm == S_IGN)
+ *sm = S_HARD_IGN;
+ }
+ }
+
+ /* from /Volumes/ScratchHFS/bird/kBuild/svn/trunk/src/kash/var.c: */
+ {
+ shprocvar(psh);
+ }
+}
diff --git a/src/kash/generated/nodes.c b/src/kash/generated/nodes.c
new file mode 100644
index 0000000..12d5172
--- /dev/null
+++ b/src/kash/generated/nodes.c
@@ -0,0 +1,366 @@
+/*
+ * This file was generated by mknodes.sh
+ */
+
+/* $NetBSD: nodes.c.pat,v 1.12 2004/06/15 22:57:27 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)nodes.c.pat 8.2 (Berkeley) 5/4/95
+ */
+
+#include <stdlib.h>
+/*
+ * Routine for dealing with parsed shell commands.
+ */
+
+#include "shell.h"
+#include "nodes.h"
+#include "memalloc.h"
+#include "machdep.h"
+#include "mystring.h"
+#include "shinstance.h"
+
+#ifndef KASH_SEPARATE_PARSER_ALLOCATOR
+
+size_t funcblocksize; /* size of structures in function */
+size_t funcstringsize; /* size of strings in node */
+pointer funcblock; /* block to allocate function from */
+char *funcstring; /* block to allocate strings from */
+
+static const short nodesize[26] = {
+ SHELL_ALIGN(sizeof (struct nbinary)),
+ SHELL_ALIGN(sizeof (struct ncmd)),
+ SHELL_ALIGN(sizeof (struct npipe)),
+ SHELL_ALIGN(sizeof (struct nredir)),
+ SHELL_ALIGN(sizeof (struct nredir)),
+ SHELL_ALIGN(sizeof (struct nredir)),
+ SHELL_ALIGN(sizeof (struct nbinary)),
+ SHELL_ALIGN(sizeof (struct nbinary)),
+ SHELL_ALIGN(sizeof (struct nif)),
+ SHELL_ALIGN(sizeof (struct nbinary)),
+ SHELL_ALIGN(sizeof (struct nbinary)),
+ SHELL_ALIGN(sizeof (struct nfor)),
+ SHELL_ALIGN(sizeof (struct ncase)),
+ SHELL_ALIGN(sizeof (struct nclist)),
+ SHELL_ALIGN(sizeof (struct narg)),
+ SHELL_ALIGN(sizeof (struct narg)),
+ SHELL_ALIGN(sizeof (struct nfile)),
+ SHELL_ALIGN(sizeof (struct nfile)),
+ SHELL_ALIGN(sizeof (struct nfile)),
+ SHELL_ALIGN(sizeof (struct nfile)),
+ SHELL_ALIGN(sizeof (struct nfile)),
+ SHELL_ALIGN(sizeof (struct ndup)),
+ SHELL_ALIGN(sizeof (struct ndup)),
+ SHELL_ALIGN(sizeof (struct nhere)),
+ SHELL_ALIGN(sizeof (struct nhere)),
+ SHELL_ALIGN(sizeof (struct nnot)),
+};
+
+
+STATIC void calcsize(union node *);
+STATIC void sizenodelist(struct nodelist *);
+STATIC union node *copynode(union node *);
+STATIC struct nodelist *copynodelist(struct nodelist *);
+STATIC char *nodesavestr(char *);
+
+#endif /* !KASH_SEPARATE_PARSER_ALLOCATOR */
+
+
+/*
+ * Make a copy of a parse tree.
+ */
+
+union node *
+copyfunc(psh, n)
+ struct shinstance *psh;
+ union node *n;
+{
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ if (n != NULL) {
+ unsigned refs = pstackretain(n->pblock);
+ TRACE2((psh, "copyfunc: %p - %u refs\n", n->pblock, refs)); K_NOREF(refs);
+ }
+ return n;
+#else
+ if (n == NULL)
+ return NULL;
+ funcblocksize = 0;
+ funcstringsize = 0;
+ calcsize(n);
+ funcblock = ckmalloc(psh, funcblocksize + funcstringsize);
+ funcstring = (char *) funcblock + funcblocksize;
+ return copynode(n);
+#endif
+}
+
+#ifndef KASH_SEPARATE_PARSER_ALLOCATOR
+
+STATIC void
+calcsize(n)
+ union node *n;
+{
+ if (n == NULL)
+ return;
+ funcblocksize += nodesize[n->type];
+ switch (n->type) {
+ case NSEMI:
+ case NAND:
+ case NOR:
+ case NWHILE:
+ case NUNTIL:
+ calcsize(n->nbinary.ch2);
+ calcsize(n->nbinary.ch1);
+ break;
+ case NCMD:
+ calcsize(n->ncmd.redirect);
+ calcsize(n->ncmd.args);
+ break;
+ case NPIPE:
+ sizenodelist(n->npipe.cmdlist);
+ break;
+ case NREDIR:
+ case NBACKGND:
+ case NSUBSHELL:
+ calcsize(n->nredir.redirect);
+ calcsize(n->nredir.n);
+ break;
+ case NIF:
+ calcsize(n->nif.elsepart);
+ calcsize(n->nif.ifpart);
+ calcsize(n->nif.test);
+ break;
+ case NFOR:
+ funcstringsize += strlen(n->nfor.var) + 1;
+ calcsize(n->nfor.body);
+ calcsize(n->nfor.args);
+ break;
+ case NCASE:
+ calcsize(n->ncase.cases);
+ calcsize(n->ncase.expr);
+ break;
+ case NCLIST:
+ calcsize(n->nclist.body);
+ calcsize(n->nclist.pattern);
+ calcsize(n->nclist.next);
+ break;
+ case NDEFUN:
+ case NARG:
+ sizenodelist(n->narg.backquote);
+ funcstringsize += strlen(n->narg.text) + 1;
+ calcsize(n->narg.next);
+ break;
+ case NTO:
+ case NCLOBBER:
+ case NFROM:
+ case NFROMTO:
+ case NAPPEND:
+ calcsize(n->nfile.fname);
+ calcsize(n->nfile.next);
+ break;
+ case NTOFD:
+ case NFROMFD:
+ calcsize(n->ndup.vname);
+ calcsize(n->ndup.next);
+ break;
+ case NHERE:
+ case NXHERE:
+ calcsize(n->nhere.doc);
+ calcsize(n->nhere.next);
+ break;
+ case NNOT:
+ calcsize(n->nnot.com);
+ break;
+ };
+}
+
+
+
+STATIC void
+sizenodelist(lp)
+ struct nodelist *lp;
+{
+ while (lp) {
+ funcblocksize += SHELL_ALIGN(sizeof(struct nodelist));
+ calcsize(lp->n);
+ lp = lp->next;
+ }
+}
+
+
+
+STATIC union node *
+copynode(n)
+ union node *n;
+{
+ union node *new;
+
+ if (n == NULL)
+ return NULL;
+ new = funcblock;
+ funcblock = (char *) funcblock + nodesize[n->type];
+ switch (n->type) {
+ case NSEMI:
+ case NAND:
+ case NOR:
+ case NWHILE:
+ case NUNTIL:
+ new->nbinary.ch2 = copynode(n->nbinary.ch2);
+ new->nbinary.ch1 = copynode(n->nbinary.ch1);
+ break;
+ case NCMD:
+ new->ncmd.redirect = copynode(n->ncmd.redirect);
+ new->ncmd.args = copynode(n->ncmd.args);
+ new->ncmd.backgnd = n->ncmd.backgnd;
+ break;
+ case NPIPE:
+ new->npipe.cmdlist = copynodelist(n->npipe.cmdlist);
+ new->npipe.backgnd = n->npipe.backgnd;
+ break;
+ case NREDIR:
+ case NBACKGND:
+ case NSUBSHELL:
+ new->nredir.redirect = copynode(n->nredir.redirect);
+ new->nredir.n = copynode(n->nredir.n);
+ break;
+ case NIF:
+ new->nif.elsepart = copynode(n->nif.elsepart);
+ new->nif.ifpart = copynode(n->nif.ifpart);
+ new->nif.test = copynode(n->nif.test);
+ break;
+ case NFOR:
+ new->nfor.var = nodesavestr(n->nfor.var);
+ new->nfor.body = copynode(n->nfor.body);
+ new->nfor.args = copynode(n->nfor.args);
+ break;
+ case NCASE:
+ new->ncase.cases = copynode(n->ncase.cases);
+ new->ncase.expr = copynode(n->ncase.expr);
+ break;
+ case NCLIST:
+ new->nclist.body = copynode(n->nclist.body);
+ new->nclist.pattern = copynode(n->nclist.pattern);
+ new->nclist.next = copynode(n->nclist.next);
+ break;
+ case NDEFUN:
+ case NARG:
+ new->narg.backquote = copynodelist(n->narg.backquote);
+ new->narg.text = nodesavestr(n->narg.text);
+ new->narg.next = copynode(n->narg.next);
+ break;
+ case NTO:
+ case NCLOBBER:
+ case NFROM:
+ case NFROMTO:
+ case NAPPEND:
+ new->nfile.fname = copynode(n->nfile.fname);
+ new->nfile.next = copynode(n->nfile.next);
+ new->nfile.fd = n->nfile.fd;
+ break;
+ case NTOFD:
+ case NFROMFD:
+ new->ndup.vname = copynode(n->ndup.vname);
+ new->ndup.dupfd = n->ndup.dupfd;
+ new->ndup.next = copynode(n->ndup.next);
+ new->ndup.fd = n->ndup.fd;
+ break;
+ case NHERE:
+ case NXHERE:
+ new->nhere.doc = copynode(n->nhere.doc);
+ new->nhere.next = copynode(n->nhere.next);
+ new->nhere.fd = n->nhere.fd;
+ break;
+ case NNOT:
+ new->nnot.com = copynode(n->nnot.com);
+ break;
+ };
+ new->type = n->type;
+ return new;
+}
+
+
+STATIC struct nodelist *
+copynodelist(lp)
+ struct nodelist *lp;
+{
+ struct nodelist *start;
+ struct nodelist **lpp;
+
+ lpp = &start;
+ while (lp) {
+ *lpp = funcblock;
+ funcblock = (char *) funcblock +
+ SHELL_ALIGN(sizeof(struct nodelist));
+ (*lpp)->n = copynode(lp->n);
+ lp = lp->next;
+ lpp = &(*lpp)->next;
+ }
+ *lpp = NULL;
+ return start;
+}
+
+
+
+STATIC char *
+nodesavestr(s)
+ char *s;
+{
+ register char *p = s;
+ register char *q = funcstring;
+ char *rtn = funcstring;
+
+ while ((*q++ = *p++) != 0)
+ continue;
+ funcstring = q;
+ return rtn;
+}
+
+#endif /* !KASH_SEPARATE_PARSER_ALLOCATOR */
+
+
+/*
+ * Free a parse tree.
+ */
+
+void
+freefunc(psh, n)
+ shinstance *psh;
+ union node *n;
+{
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ if (n)
+ pstackrelease(psh, n->pblock, "freefunc");
+#else
+ if (n)
+ ckfree(psh, n);
+#endif
+}
diff --git a/src/kash/generated/nodes.h b/src/kash/generated/nodes.h
new file mode 100644
index 0000000..324fcbe
--- /dev/null
+++ b/src/kash/generated/nodes.h
@@ -0,0 +1,207 @@
+/*
+ * This file was generated by mknodes.sh
+ */
+
+#define NSEMI 0
+#define NCMD 1
+#define NPIPE 2
+#define NREDIR 3
+#define NBACKGND 4
+#define NSUBSHELL 5
+#define NAND 6
+#define NOR 7
+#define NIF 8
+#define NWHILE 9
+#define NUNTIL 10
+#define NFOR 11
+#define NCASE 12
+#define NCLIST 13
+#define NDEFUN 14
+#define NARG 15
+#define NTO 16
+#define NCLOBBER 17
+#define NFROM 18
+#define NFROMTO 19
+#define NAPPEND 20
+#define NTOFD 21
+#define NFROMFD 22
+#define NHERE 23
+#define NXHERE 24
+#define NNOT 25
+
+
+
+struct nbinary {
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ struct pstack_block *pblock;
+#endif
+ int type;
+ union node *ch1;
+ union node *ch2;
+};
+
+
+struct ncmd {
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ struct pstack_block *pblock;
+#endif
+ int type;
+ int backgnd;
+ union node *args;
+ union node *redirect;
+};
+
+
+struct npipe {
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ struct pstack_block *pblock;
+#endif
+ int type;
+ int backgnd;
+ struct nodelist *cmdlist;
+};
+
+
+struct nredir {
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ struct pstack_block *pblock;
+#endif
+ int type;
+ union node *n;
+ union node *redirect;
+};
+
+
+struct nif {
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ struct pstack_block *pblock;
+#endif
+ int type;
+ union node *test;
+ union node *ifpart;
+ union node *elsepart;
+};
+
+
+struct nfor {
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ struct pstack_block *pblock;
+#endif
+ int type;
+ union node *args;
+ union node *body;
+ char *var;
+};
+
+
+struct ncase {
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ struct pstack_block *pblock;
+#endif
+ int type;
+ union node *expr;
+ union node *cases;
+};
+
+
+struct nclist {
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ struct pstack_block *pblock;
+#endif
+ int type;
+ union node *next;
+ union node *pattern;
+ union node *body;
+};
+
+
+struct narg {
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ struct pstack_block *pblock;
+#endif
+ int type;
+ union node *next;
+ char *text;
+ struct nodelist *backquote;
+};
+
+
+struct nfile {
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ struct pstack_block *pblock;
+#endif
+ int type;
+ int fd;
+ union node *next;
+ union node *fname;
+};
+
+
+struct ndup {
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ struct pstack_block *pblock;
+#endif
+ int type;
+ int fd;
+ union node *next;
+ int dupfd;
+ union node *vname;
+};
+
+
+struct nhere {
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ struct pstack_block *pblock;
+#endif
+ int type;
+ int fd;
+ union node *next;
+ union node *doc;
+};
+
+
+struct nnot {
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ struct pstack_block *pblock;
+#endif
+ int type;
+ union node *com;
+};
+
+
+union node {
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+# ifdef __GNUC__
+ __extension__
+# endif
+ struct {
+ struct pstack_block *pblock;
+ int type;
+ };
+#else
+ int type;
+#endif
+ struct nbinary nbinary;
+ struct ncmd ncmd;
+ struct npipe npipe;
+ struct nredir nredir;
+ struct nif nif;
+ struct nfor nfor;
+ struct ncase ncase;
+ struct nclist nclist;
+ struct narg narg;
+ struct nfile nfile;
+ struct ndup ndup;
+ struct nhere nhere;
+ struct nnot nnot;
+};
+
+
+struct nodelist {
+ struct nodelist *next;
+ union node *n;
+};
+
+
+union node *copyfunc(struct shinstance *, union node *);
+void freefunc(struct shinstance *, union node *);
diff --git a/src/kash/generated/token.h b/src/kash/generated/token.h
new file mode 100644
index 0000000..c961f01
--- /dev/null
+++ b/src/kash/generated/token.h
@@ -0,0 +1,112 @@
+#define TEOF 0
+#define TNL 1
+#define TSEMI 2
+#define TBACKGND 3
+#define TAND 4
+#define TOR 5
+#define TPIPE 6
+#define TLP 7
+#define TRP 8
+#define TENDCASE 9
+#define TENDBQUOTE 10
+#define TREDIR 11
+#define TWORD 12
+#define TIF 13
+#define TTHEN 14
+#define TELSE 15
+#define TELIF 16
+#define TFI 17
+#define TWHILE 18
+#define TUNTIL 19
+#define TFOR 20
+#define TDO 21
+#define TDONE 22
+#define TBEGIN 23
+#define TEND 24
+#define TCASE 25
+#define TESAC 26
+#define TNOT 27
+
+/* Array indicating which tokens mark the end of a list */
+const char tokendlist[] = {
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 1,
+ 0,
+ 1,
+ 0,
+};
+
+const char *const tokname[] = {
+ "end of file",
+ "newline",
+ "\";\"",
+ "\"&\"",
+ "\"&&\"",
+ "\"||\"",
+ "\"|\"",
+ "\"(\"",
+ "\")\"",
+ "\";;\"",
+ "\"`\"",
+ "redirection",
+ "word",
+ "\"if\"",
+ "\"then\"",
+ "\"else\"",
+ "\"elif\"",
+ "\"fi\"",
+ "\"while\"",
+ "\"until\"",
+ "\"for\"",
+ "\"do\"",
+ "\"done\"",
+ "\"{\"",
+ "\"}\"",
+ "\"case\"",
+ "\"esac\"",
+ "\"!\"",
+};
+
+#define KWDOFFSET 13
+
+const char *const parsekwd[] = {
+ "if",
+ "then",
+ "else",
+ "elif",
+ "fi",
+ "while",
+ "until",
+ "for",
+ "do",
+ "done",
+ "{",
+ "}",
+ "case",
+ "esac",
+ "!",
+ 0
+};
diff --git a/src/kash/histedit.c b/src/kash/histedit.c
new file mode 100644
index 0000000..dc02e55
--- /dev/null
+++ b/src/kash/histedit.c
@@ -0,0 +1,546 @@
+/* $NetBSD: histedit.c,v 1.36 2005/05/09 11:35:19 christos Exp $ */
+
+/*-
+ * Copyright (c) 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: histedit.c,v 1.36 2005/05/09 11:35:19 christos Exp $");
+#endif /* not lint */
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+/*
+ * Editline and history functions (and glue).
+ */
+#include "shell.h"
+#include "parser.h"
+#include "var.h"
+#include "options.h"
+#include "main.h"
+#include "output.h"
+#include "mystring.h"
+#include "myhistedit.h"
+#include "error.h"
+
+#ifndef SMALL
+#include "eval.h"
+#include "memalloc.h"
+
+#define MAXHISTLOOPS 4 /* max recursions through fc */
+#define DEFEDITOR "ed" /* default editor *should* be $EDITOR */
+
+History *hist; /* history cookie */
+EditLine *el; /* editline cookie */
+int displayhist;
+static FILE *el_in, *el_out;
+unsigned char _el_fn_complete(EditLine *, int);
+
+STATIC const char *fc_replace(const char *, char *, char *);
+
+#ifdef DEBUG
+extern FILE *tracefile;
+#endif
+
+/*
+ * Set history and editing status. Called whenever the status may
+ * have changed (figures out what to do).
+ */
+void
+histedit(void)
+{
+ FILE *el_err;
+
+#define editing (Eflag(psh) || Vflag(psh))
+
+ if (iflag(psh)) {
+ if (!hist) {
+ /*
+ * turn history on
+ */
+ INTOFF;
+ hist = history_init();
+ INTON;
+
+ if (hist != NULL)
+ sethistsize(histsizeval());
+ else
+ out2str(psh, "sh: can't initialize history\n");
+ }
+ if (editing && !el && isatty(0)) { /* && isatty(2) ??? */
+ /*
+ * turn editing on
+ */
+ char *term, *shname;
+
+ INTOFF;
+ if (el_in == NULL)
+ el_in = fdopen(0, "r");
+ if (el_out == NULL)
+ el_out = fdopen(2, "w");
+ if (el_in == NULL || el_out == NULL)
+ goto bad;
+ el_err = el_out;
+#if DEBUG
+ if (tracefile)
+ el_err = tracefile;
+#endif
+ term = lookupvar(psh, "TERM");
+ if (term)
+ setenv("TERM", term, 1);
+ else
+ unsetenv("TERM");
+ shname = psh->arg0;
+ if (shname[0] == '-')
+ shname++;
+ el = el_init(shname, el_in, el_out, el_err);
+ if (el != NULL) {
+ if (hist)
+ el_set(el, EL_HIST, history, hist);
+ el_set(el, EL_PROMPT, getprompt);
+ el_set(el, EL_SIGNAL, 1);
+ el_set(el, EL_ADDFN, "rl-complete",
+ "ReadLine compatible completion function",
+ _el_fn_complete);
+ } else {
+bad:
+ out2str(psh, "sh: can't initialize editing\n");
+ }
+ INTON;
+ } else if (!editing && el) {
+ INTOFF;
+ el_end(el);
+ el = NULL;
+ INTON;
+ }
+ if (el) {
+ if (Vflag(psh))
+ el_set(el, EL_EDITOR, "vi");
+ else if (Eflag(psh))
+ el_set(el, EL_EDITOR, "emacs");
+ el_set(el, EL_BIND, "^I",
+ tabcomplete(psh) ? "rl-complete" : "ed-insert", NULL);
+ el_source(el, NULL);
+ }
+ } else {
+ INTOFF;
+ if (el) { /* no editing if not interactive */
+ el_end(el);
+ el = NULL;
+ }
+ if (hist) {
+ history_end(hist);
+ hist = NULL;
+ }
+ INTON;
+ }
+}
+
+
+void
+sethistsize(shinstance *psh, const char *hs)
+{
+ int histsize;
+ HistEvent he;
+
+ if (hist != NULL) {
+ if (hs == NULL || *hs == '\0' ||
+ (histsize = atoi(hs)) < 0)
+ histsize = 100;
+ history(hist, &he, H_SETSIZE, histsize);
+ }
+}
+
+void
+setterm(shinstance *psh, const char *term)
+{
+ if (el != NULL && term != NULL)
+ if (el_set(el, EL_TERMINAL, term) != 0) {
+ outfmt(psh->out2, "sh: Can't set terminal type %s\n", term);
+ outfmt(psh->out2, "sh: Using dumb terminal settings.\n");
+ }
+}
+
+int
+inputrc(argc, argv)
+ int argc;
+ char **argv;
+{
+ if (argc != 2) {
+ out2str(psh, "usage: inputrc file\n");
+ return 1;
+ }
+ if (el != NULL) {
+ if (el_source(el, argv[1])) {
+ out2str(psh, "inputrc: failed\n");
+ return 1;
+ } else
+ return 0;
+ } else {
+ out2str(psh, "sh: inputrc ignored, not editing\n");
+ return 1;
+ }
+}
+
+/*
+ * This command is provided since POSIX decided to standardize
+ * the Korn shell fc command. Oh well...
+ */
+int
+histcmd(int argc, char **argv)
+{
+ int ch;
+ const char *editor = NULL;
+ HistEvent he;
+ int lflg = 0, nflg = 0, rflg = 0, sflg = 0;
+ int i, retval;
+ const char *firststr, *laststr;
+ int first, last, direction;
+ char *pat = NULL, *repl; /* ksh "fc old=new" crap */
+ static int active = 0;
+ struct jmploc jmploc;
+ struct jmploc *volatile savehandler;
+ char editfile[MAXPATHLEN + 1];
+ FILE *efp;
+#ifdef __GNUC__
+ /* Avoid longjmp clobbering */
+ (void) &editor;
+ (void) &lflg;
+ (void) &nflg;
+ (void) &rflg;
+ (void) &sflg;
+ (void) &firststr;
+ (void) &laststr;
+ (void) &pat;
+ (void) &repl;
+ (void) &efp;
+ (void) &argc;
+ (void) &argv;
+#endif
+
+ if (hist == NULL)
+ error(psh, "history not active");
+
+ if (argc == 1)
+ error(psh, "missing history argument");
+
+ optreset = 1; optind = 1; /* initialize getopt */
+ while (not_fcnumber(argv[optind]) &&
+ (ch = getopt(argc, argv, ":e:lnrs")) != -1)
+ switch ((char)ch) {
+ case 'e':
+ editor = optionarg;
+ break;
+ case 'l':
+ lflg = 1;
+ break;
+ case 'n':
+ nflg = 1;
+ break;
+ case 'r':
+ rflg = 1;
+ break;
+ case 's':
+ sflg = 1;
+ break;
+ case ':':
+ error(psh, "option -%c expects argument", optopt);
+ /* NOTREACHED */
+ case '?':
+ default:
+ error(psh, "unknown option: -%c", optopt);
+ /* NOTREACHED */
+ }
+ argc -= optind, argv += optind;
+
+ /*
+ * If executing...
+ */
+ if (lflg == 0 || editor || sflg) {
+ lflg = 0; /* ignore */
+ editfile[0] = '\0';
+ /*
+ * Catch interrupts to reset active counter and
+ * cleanup temp files.
+ */
+ if (setjmp(jmploc.loc)) {
+ active = 0;
+ if (*editfile)
+ unlink(editfile);
+ handler = savehandler;
+ longjmp(handler->loc, 1);
+ }
+ savehandler = handler;
+ handler = &jmploc;
+ if (++active > MAXHISTLOOPS) {
+ active = 0;
+ displayhist = 0;
+ error(psh, "called recursively too many times");
+ }
+ /*
+ * Set editor.
+ */
+ if (sflg == 0) {
+ if (editor == NULL &&
+ (editor = bltinlookup(psh, "FCEDIT", 1)) == NULL &&
+ (editor = bltinlookup(psh, "EDITOR", 1)) == NULL)
+ editor = DEFEDITOR;
+ if (editor[0] == '-' && editor[1] == '\0') {
+ sflg = 1; /* no edit */
+ editor = NULL;
+ }
+ }
+ }
+
+ /*
+ * If executing, parse [old=new] now
+ */
+ if (lflg == 0 && argc > 0 &&
+ ((repl = strchr(argv[0], '=')) != NULL)) {
+ pat = argv[0];
+ *repl++ = '\0';
+ argc--, argv++;
+ }
+ /*
+ * determine [first] and [last]
+ */
+ switch (argc) {
+ case 0:
+ firststr = lflg ? "-16" : "-1";
+ laststr = "-1";
+ break;
+ case 1:
+ firststr = argv[0];
+ laststr = lflg ? "-1" : argv[0];
+ break;
+ case 2:
+ firststr = argv[0];
+ laststr = argv[1];
+ break;
+ default:
+ error(psh, "too many args");
+ /* NOTREACHED */
+ }
+ /*
+ * Turn into event numbers.
+ */
+ first = str_to_event(firststr, 0);
+ last = str_to_event(laststr, 1);
+
+ if (rflg) {
+ i = last;
+ last = first;
+ first = i;
+ }
+ /*
+ * XXX - this should not depend on the event numbers
+ * always increasing. Add sequence numbers or offset
+ * to the history element in next (diskbased) release.
+ */
+ direction = first < last ? H_PREV : H_NEXT;
+
+ /*
+ * If editing, grab a temp file.
+ */
+ if (editor) {
+ int fd;
+ INTOFF; /* easier */
+ snprintf(editfile, sizeof(editfile), "%s_shXXXXXX", _PATH_TMP);
+ if ((fd = mkstemp(editfile)) < 0)
+ error(psh, "can't create temporary file %s", editfile);
+ if ((efp = fdopen(fd, "w")) == NULL) {
+ shfile_close(&psh->fdtab, fd);
+ error(psh, "can't allocate stdio buffer for temp");
+ }
+ }
+
+ /*
+ * Loop through selected history events. If listing or executing,
+ * do it now. Otherwise, put into temp file and call the editor
+ * after.
+ *
+ * The history interface needs rethinking, as the following
+ * convolutions will demonstrate.
+ */
+ history(hist, &he, H_FIRST);
+ retval = history(hist, &he, H_NEXT_EVENT, first);
+ for (;retval != -1; retval = history(hist, &he, direction)) {
+ if (lflg) {
+ if (!nflg)
+ out1fmt(psh, "%5d ", he.num);
+ out1str(psh, he.str);
+ } else {
+ const char *s = pat ?
+ fc_replace(he.str, pat, repl) : he.str;
+
+ if (sflg) {
+ if (displayhist) {
+ out2str(psh, s);
+ }
+
+ evalstring(psh, strcpy(stalloc(psh, strlen(s) + 1), s), 0);
+ if (displayhist && hist) {
+ /*
+ * XXX what about recursive and
+ * relative histnums.
+ */
+ history(hist, &he, H_ENTER, s);
+ }
+ } else
+ fputs(s, efp);
+ }
+ /*
+ * At end? (if we were to lose last, we'd sure be
+ * messed up).
+ */
+ if (he.num == last)
+ break;
+ }
+ if (editor) {
+ char *editcmd;
+
+ fclose(efp);
+ editcmd = stalloc(psh, strlen(editor) + strlen(editfile) + 2);
+ sprintf(editcmd, "%s %s", editor, editfile);
+ evalstring(psh, editcmd, 0); /* XXX - should use no JC command */
+ INTON;
+ readcmdfile(psh, editfile); /* XXX - should read back - quick tst */
+ unlink(editfile);
+ }
+
+ if (lflg == 0 && active > 0)
+ --active;
+ if (displayhist)
+ displayhist = 0;
+ return 0;
+}
+
+STATIC const char *
+fc_replace(const char *s, char *p, char *r)
+{
+ char *dest;
+ int plen = strlen(p);
+
+ STARTSTACKSTR(psh, dest);
+ while (*s) {
+ if (*s == *p && strncmp(s, p, plen) == 0) {
+ while (*r)
+ STPUTC(psh, *r++, dest);
+ s += plen;
+ *p = '\0'; /* so no more matches */
+ } else
+ STPUTC(psh, *s++, dest);
+ }
+ STACKSTRNUL(psh, dest);
+ dest = grabstackstr(psh, dest);
+
+ return (dest);
+}
+
+int
+not_fcnumber(char *s)
+{
+ if (s == NULL)
+ return 0;
+ if (*s == '-')
+ s++;
+ return (!is_number(s));
+}
+
+int
+str_to_event(const char *str, int last)
+{
+ HistEvent he;
+ const char *s = str;
+ int relative = 0;
+ int i, retval;
+
+ retval = history(hist, &he, H_FIRST);
+ switch (*s) {
+ case '-':
+ relative = 1;
+ /*FALLTHROUGH*/
+ case '+':
+ s++;
+ }
+ if (is_number(s)) {
+ i = atoi(s);
+ if (relative) {
+ while (retval != -1 && i--) {
+ retval = history(hist, &he, H_NEXT);
+ }
+ if (retval == -1)
+ retval = history(hist, &he, H_LAST);
+ } else {
+ retval = history(hist, &he, H_NEXT_EVENT, i);
+ if (retval == -1) {
+ /*
+ * the notion of first and last is
+ * backwards to that of the history package
+ */
+ retval = history(hist, &he,
+ last ? H_FIRST : H_LAST);
+ }
+ }
+ if (retval == -1)
+ error(psh, "history number %s not found (internal error)",
+ str);
+ } else {
+ /*
+ * pattern
+ */
+ retval = history(hist, &he, H_PREV_STR, str);
+ if (retval == -1)
+ error(psh, "history pattern not found: %s", str);
+ }
+ return (he.num);
+}
+#else /* SMALL */
+int
+histcmd(shinstance *psh, int argc, char **argv)
+{
+ error(psh, "not compiled with history support");
+ /* NOTREACHED */
+ return -1;
+}
+int
+inputrc(shinstance *psh, int argc, char **argv)
+{
+ error(psh, "not compiled with history support");
+ /* NOTREACHED */
+ return -1;
+}
+#endif /* SMALL */
diff --git a/src/kash/init.h b/src/kash/init.h
new file mode 100644
index 0000000..5187028
--- /dev/null
+++ b/src/kash/init.h
@@ -0,0 +1,39 @@
+/* $NetBSD: init.h,v 1.10 2003/08/07 09:05:32 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)init.h 8.2 (Berkeley) 5/4/95
+ */
+
+void init(struct shinstance *);
+void reset(struct shinstance *);
+void initshellproc(struct shinstance *);
diff --git a/src/kash/input.c b/src/kash/input.c
new file mode 100644
index 0000000..fc9716b
--- /dev/null
+++ b/src/kash/input.c
@@ -0,0 +1,574 @@
+/* $NetBSD: input.c,v 1.39 2003/08/07 09:05:32 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)input.c 8.3 (Berkeley) 6/9/95";
+#else
+__RCSID("$NetBSD: input.c,v 1.39 2003/08/07 09:05:32 agc Exp $");
+#endif /* not lint */
+#endif
+
+#include <stdio.h> /* defines BUFSIZ */
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * This file implements the input routines used by the parser.
+ */
+
+#include "shell.h"
+#include "redir.h"
+#include "syntax.h"
+#include "input.h"
+#include "output.h"
+#include "options.h"
+#include "memalloc.h"
+#include "error.h"
+#include "alias.h"
+#include "parser.h"
+#include "myhistedit.h"
+#include "shinstance.h"
+
+#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */
+
+//MKINIT
+//struct strpush {
+// struct strpush *prev; /* preceding string on stack */
+// char *prevstring;
+// int prevnleft;
+// int prevlleft;
+// struct alias *ap; /* if push was associated with an alias */
+//};
+//
+///*
+// * The parsefile structure pointed to by the global variable parsefile
+// * contains information about the current file being read.
+// */
+//
+//MKINIT
+//struct parsefile {
+// struct parsefile *prev; /* preceding file on stack */
+// int linno; /* current line */
+// int fd; /* file descriptor (or -1 if string) */
+// int nleft; /* number of chars left in this line */
+// int lleft; /* number of chars left in this buffer */
+// char *nextc; /* next char in buffer */
+// char *buf; /* input buffer */
+// struct strpush *strpush; /* for pushing strings at this level */
+// struct strpush basestrpush; /* so pushing one is fast */
+//};
+//
+//
+//int plinno = 1; /* input line number */
+//int parsenleft; /* copy of parsefile->nleft */
+//MKINIT int parselleft; /* copy of parsefile->lleft */
+//char *parsenextc; /* copy of parsefile->nextc */
+//MKINIT struct parsefile basepf; /* top level input file */
+//MKINIT char basebuf[BUFSIZ]; /* buffer for top level input file */
+//struct parsefile *parsefile = &basepf; /* current input file */
+//int init_editline = 0; /* editline library initialized? */
+//int whichprompt; /* 1 == PS1, 2 == PS2 */
+//
+//#ifndef SMALL
+//EditLine *el; /* cookie for editline package */
+//#endif
+
+STATIC void pushfile(shinstance *psh);
+static int preadfd(shinstance *psh);
+
+#ifdef mkinit
+INCLUDE <stdio.h>
+INCLUDE "input.h"
+INCLUDE "error.h"
+
+INIT {
+ psh->basepf.nextc = psh->basepf.buf = psh->basebuf;
+}
+
+RESET {
+ if (psh->exception != EXSHELLPROC)
+ psh->parselleft = psh->parsenleft = 0; /* clear input buffer */
+ popallfiles(psh);
+}
+
+SHELLPROC {
+ popallfiles(psh);
+}
+#endif
+
+
+/*
+ * Read a line from the script.
+ */
+
+char *
+pfgets(shinstance *psh, char *line, int len)
+{
+ char *p = line;
+ int nleft = len;
+ int c;
+
+ while (--nleft > 0) {
+ c = pgetc_macro(psh);
+ if (c == PEOF) {
+ if (p == line)
+ return NULL;
+ break;
+ }
+ *p++ = c;
+ if (c == '\n')
+ break;
+ }
+ *p = '\0';
+ return line;
+}
+
+
+
+/*
+ * Read a character from the script, returning PEOF on end of file.
+ * Nul characters in the input are silently discarded.
+ */
+
+int
+pgetc(shinstance *psh)
+{
+ return pgetc_macro(psh);
+}
+
+
+static int
+preadfd_inner(shinstance *psh, char *buf, int bufsize)
+{
+ int nr;
+retry:
+#ifndef SMALL
+ if (psh->parsefile->fd == 0 && psh->el) {
+ static const char *rl_cp;
+ static int el_len;
+
+ if (rl_cp == NULL)
+ rl_cp = el_gets(psh->el, &el_len);
+ if (rl_cp == NULL)
+ nr = 0;
+ else {
+ nr = el_len;
+ if (nr > bufsize)
+ nr = bufsize;
+ memcpy(buf, rl_cp, nr);
+ if (nr != el_len) {
+ el_len -= nr;
+ rl_cp += nr;
+ } else
+ rl_cp = 0;
+ }
+
+ } else
+#endif
+ nr = shfile_read(&psh->fdtab, psh->parsefile->fd, buf, bufsize);
+
+
+ if (nr <= 0) {
+ if (nr < 0) {
+ if (errno == EINTR)
+ goto retry;
+ if (psh->parsefile->fd == 0 && errno == EWOULDBLOCK) {
+ int flags = shfile_fcntl(&psh->fdtab, 0, F_GETFL, 0);
+ if (flags >= 0 && flags & O_NONBLOCK) {
+ flags &=~ O_NONBLOCK;
+ if (shfile_fcntl(&psh->fdtab, 0, F_SETFL, flags) >= 0) {
+ out2str(psh, "sh: turning off NDELAY mode\n");
+ goto retry;
+ }
+ }
+ }
+ }
+ nr = -1;
+ }
+ return nr;
+}
+
+
+
+static int
+preadfd(shinstance *psh)
+{
+ int nr;
+ char *buf = psh->parsefile->buf;
+ psh->parsenextc = buf;
+
+#ifdef SH_DEAL_WITH_CRLF
+ /* Convert CRLF to LF. */
+ nr = preadfd_inner(psh, buf, BUFSIZ - 9);
+ if (nr > 0) {
+ char *cr = memchr(buf, '\r', nr);
+ while (cr) {
+ size_t left = nr - (cr - buf);
+
+ if (left > 1 && cr[1] == '\n') {
+ left--;
+ nr--;
+ memmove(cr, cr + 1, left);
+ cr = memchr(cr, '\r', left);
+ } else if (left == 1) {
+ /* Special case: \r at buffer end. Read one more char. Screw \r\r\n sequences. */
+ int nr2 = preadfd_inner(psh, cr + 1, 1);
+ if (nr2 != 1)
+ break;
+ if (cr[1] == '\n') {
+ *cr = '\n';
+ } else {
+ nr++;
+ }
+ break;
+ } else {
+ cr = memchr(cr + 1, '\r', left);
+ }
+ }
+ }
+#else
+ nr = preadfd_inner(psh, buf, BUFSIZ - 8);
+#endif
+ return nr;
+}
+
+/*
+ * Refill the input buffer and return the next input character:
+ *
+ * 1) If a string was pushed back on the input, pop it;
+ * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading
+ * from a string so we can't refill the buffer, return EOF.
+ * 3) If the is more stuff in this buffer, use it else call read to fill it.
+ * 4) Process input up to the next newline, deleting nul characters.
+ */
+
+int
+preadbuffer(shinstance *psh)
+{
+ char *p, *q;
+ int more;
+#ifndef SMALL
+ int something;
+#endif
+ char savec;
+
+ if (psh->parsefile->strpush) {
+ popstring(psh);
+ if (--psh->parsenleft >= 0)
+ return (*psh->parsenextc++);
+ }
+ if (psh->parsenleft == EOF_NLEFT || psh->parsefile->buf == NULL)
+ return PEOF;
+ flushout(&psh->output);
+ flushout(&psh->errout);
+
+again:
+ if (psh->parselleft <= 0) {
+ if ((psh->parselleft = preadfd(psh)) == -1) {
+ psh->parselleft = psh->parsenleft = EOF_NLEFT;
+ return PEOF;
+ }
+ }
+
+ q = p = psh->parsenextc;
+
+ /* delete nul characters */
+#ifndef SMALL
+ something = 0;
+#endif
+ for (more = 1; more;) {
+ switch (*p) {
+ case '\0':
+ p++; /* Skip nul */
+ goto check;
+
+ case '\t':
+ case ' ':
+ break;
+
+ case '\n':
+ psh->parsenleft = (int)(q - psh->parsenextc);
+ more = 0; /* Stop processing here */
+ break;
+
+ default:
+#ifndef SMALL
+ something = 1;
+#endif
+ break;
+ }
+
+ *q++ = *p++;
+check:
+ if (--psh->parselleft <= 0) {
+ psh->parsenleft = (int)(q - psh->parsenextc - 1);
+ if (psh->parsenleft < 0)
+ goto again;
+ *q = '\0';
+ more = 0;
+ }
+ }
+
+ savec = *q;
+ *q = '\0';
+
+#ifndef SMALL
+ if (psh->parsefile->fd == 0 && hist && something) {
+ HistEvent he;
+ INTOFF;
+ history(hist, &he, psh->whichprompt == 1? H_ENTER : H_APPEND,
+ psh->parsenextc);
+ INTON;
+ }
+#endif
+
+ if (vflag(psh)) {
+ out2str(psh, psh->parsenextc);
+ flushout(psh->out2);
+ }
+
+ *q = savec;
+
+ return *psh->parsenextc++;
+}
+
+/*
+ * Undo the last call to pgetc. Only one character may be pushed back.
+ * PEOF may be pushed back.
+ */
+
+void
+pungetc(shinstance *psh)
+{
+ psh->parsenleft++;
+ psh->parsenextc--;
+}
+
+/*
+ * Push a string back onto the input at this current parsefile level.
+ * We handle aliases this way.
+ */
+void
+pushstring(shinstance *psh, char *s, size_t len, void *ap)
+{
+ struct strpush *sp;
+
+ INTOFF;
+/*dprintf("*** calling pushstring: %s, %d\n", s, len);*/
+ if (psh->parsefile->strpush) {
+ sp = ckmalloc(psh, sizeof (struct strpush));
+ sp->prev = psh->parsefile->strpush;
+ psh->parsefile->strpush = sp;
+ } else
+ sp = psh->parsefile->strpush = &(psh->parsefile->basestrpush);
+ sp->prevstring = psh->parsenextc;
+ sp->prevnleft = psh->parsenleft;
+ sp->prevlleft = psh->parselleft;
+ sp->ap = (struct alias *)ap;
+ if (ap)
+ ((struct alias *)ap)->flag |= ALIASINUSE;
+ psh->parsenextc = s;
+ psh->parsenleft = (int)len;
+ INTON;
+}
+
+void
+popstring(shinstance *psh)
+{
+ struct strpush *sp = psh->parsefile->strpush;
+
+ INTOFF;
+ psh->parsenextc = sp->prevstring;
+ psh->parsenleft = sp->prevnleft;
+ psh->parselleft = sp->prevlleft;
+/*dprintf("*** calling popstring: restoring to '%s'\n", psh->parsenextc);*/
+ if (sp->ap)
+ sp->ap->flag &= ~ALIASINUSE;
+ psh->parsefile->strpush = sp->prev;
+ if (sp != &(psh->parsefile->basestrpush))
+ ckfree(psh, sp);
+ INTON;
+}
+
+/*
+ * Set the input to take input from a file. If push is set, push the
+ * old input onto the stack first.
+ */
+
+void
+setinputfile(shinstance *psh, const char *fname, int push)
+{
+ int fd;
+ int fd2;
+
+ INTOFF;
+/** @todo shfile fixme */
+ if ((fd = shfile_open(&psh->fdtab, fname, O_RDONLY, 0)) < 0)
+ error(psh, "Can't open %s", fname);
+ if (fd < 10) {
+ fd2 = movefd_above(psh, fd, 10);
+ if (fd2 < 0)
+ error(psh, "Out of file descriptors");
+ fd = fd2;
+ }
+ setinputfd(psh, fd, push);
+ INTON;
+}
+
+
+/*
+ * Like setinputfile, but takes an open file descriptor. Call this with
+ * interrupts off.
+ */
+
+void
+setinputfd(shinstance *psh, int fd, int push)
+{
+ (void) shfile_cloexec(&psh->fdtab, fd, 1 /* close it */);
+ if (push) {
+ pushfile(psh);
+ psh->parsefile->buf = ckmalloc(psh, BUFSIZ);
+ }
+ if (psh->parsefile->fd > 0)
+ shfile_close(&psh->fdtab, psh->parsefile->fd);
+ psh->parsefile->fd = fd;
+ if (psh->parsefile->buf == NULL)
+ psh->parsefile->buf = ckmalloc(psh, BUFSIZ);
+ psh->parselleft = psh->parsenleft = 0;
+ psh->plinno = 1;
+}
+
+
+/*
+ * Like setinputfile, but takes input from a string.
+ */
+
+void
+setinputstring(shinstance *psh, char *string, int push)
+{
+ INTOFF;
+ if (push)
+ pushfile(psh);
+ psh->parsenextc = string;
+ psh->parselleft = psh->parsenleft = (int)strlen(string);
+ psh->parsefile->buf = NULL;
+ psh->plinno = 1;
+ INTON;
+}
+
+
+
+/*
+ * To handle the "." command, a stack of input files is used. Pushfile
+ * adds a new entry to the stack and popfile restores the previous level.
+ */
+
+STATIC void
+pushfile(shinstance *psh)
+{
+ struct parsefile *pf;
+
+ psh->parsefile->nleft = psh->parsenleft;
+ psh->parsefile->lleft = psh->parselleft;
+ psh->parsefile->nextc = psh->parsenextc;
+ psh->parsefile->linno = psh->plinno;
+ pf = (struct parsefile *)ckmalloc(psh, sizeof (struct parsefile));
+ pf->prev = psh->parsefile;
+ pf->fd = -1;
+ pf->strpush = NULL;
+ pf->basestrpush.prev = NULL;
+ psh->parsefile = pf;
+}
+
+
+void
+popfile(shinstance *psh)
+{
+ struct parsefile *pf = psh->parsefile;
+
+ INTOFF;
+ if (pf->fd >= 0)
+ shfile_close(&psh->fdtab, pf->fd);
+ if (pf->buf)
+ ckfree(psh, pf->buf);
+ while (pf->strpush)
+ popstring(psh);
+ psh->parsefile = pf->prev;
+ ckfree(psh, pf);
+ psh->parsenleft = psh->parsefile->nleft;
+ psh->parselleft = psh->parsefile->lleft;
+ psh->parsenextc = psh->parsefile->nextc;
+ psh->plinno = psh->parsefile->linno;
+ INTON;
+}
+
+
+/*
+ * Return to top level.
+ */
+
+void
+popallfiles(shinstance *psh)
+{
+ while (psh->parsefile != &psh->basepf)
+ popfile(psh);
+}
+
+
+
+/*
+ * Close the file(s) that the shell is reading commands from. Called
+ * after a fork is done.
+ *
+ * Takes one arg, vfork, which tells it to not modify its global vars
+ * as it is still running in the parent.
+ *
+ * This code is (probably) unnecessary as the 'close on exec' flag is
+ * set and should be enough. In the vfork case it is definitely wrong
+ * to close the fds as another fork() may be done later to feed data
+ * from a 'here' document into a pipe and we don't want to close the
+ * pipe!
+ */
+
+void
+closescript(shinstance *psh)
+{
+ popallfiles(psh);
+ if (psh->parsefile->fd > 0) {
+ shfile_close(&psh->fdtab, psh->parsefile->fd);
+ psh->parsefile->fd = 0;
+ }
+}
diff --git a/src/kash/input.h b/src/kash/input.h
new file mode 100644
index 0000000..5d0d64e
--- /dev/null
+++ b/src/kash/input.h
@@ -0,0 +1,62 @@
+/* $NetBSD: input.h,v 1.15 2003/08/07 09:05:33 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)input.h 8.2 (Berkeley) 5/4/95
+ */
+
+/* PEOF (the end of file marker) is defined in syntax.h */
+
+/*
+ * The input line number. Input.c just defines this variable, and saves
+ * and restores it when files are pushed and popped. The user of this
+ * package must set its value.
+ */
+//extern int plinno;
+//extern int parsenleft; /* number of characters left in input buffer */
+//extern char *parsenextc; /* next character in input buffer */
+//extern int init_editline; /* 0 == not setup, 1 == OK, -1 == failed */
+
+char *pfgets(struct shinstance *, char *, int);
+int pgetc(struct shinstance *);
+int preadbuffer(struct shinstance *);
+void pungetc(struct shinstance *);
+void pushstring(struct shinstance *, char *, size_t, void *);
+void popstring(struct shinstance *);
+void setinputfile(struct shinstance *, const char *, int);
+void setinputfd(struct shinstance *, int, int);
+void setinputstring(struct shinstance *, char *, int);
+void popfile(struct shinstance *);
+void popallfiles(struct shinstance *);
+void closescript(struct shinstance *);
+
+#define pgetc_macro(psh) (--(psh)->parsenleft >= 0? *(psh)->parsenextc++ : preadbuffer(psh))
diff --git a/src/kash/jobs.c b/src/kash/jobs.c
new file mode 100644
index 0000000..43f359b
--- /dev/null
+++ b/src/kash/jobs.c
@@ -0,0 +1,1521 @@
+/* $NetBSD: jobs.c,v 1.63 2005/06/01 15:41:19 lukem Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: jobs.c,v 1.63 2005/06/01 15:41:19 lukem Exp $");
+#endif /* not lint */
+#endif
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include "shell.h"
+#if JOBS && !defined(_MSC_VER)
+# include <termios.h>
+#endif
+#include "redir.h"
+#include "show.h"
+#include "main.h"
+#include "parser.h"
+#include "nodes.h"
+#include "jobs.h"
+#include "options.h"
+#include "trap.h"
+#include "syntax.h"
+#include "input.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "init.h"
+#include "shinstance.h"
+
+//static struct job *jobtab; /* array of jobs */
+//static int njobs; /* size of array */
+//static int jobs_invalid; /* set in child */
+//MKINIT pid_t backgndpid = -1; /* pid of last background process */
+#if JOBS
+//int initialpgrp; /* pgrp of shell on invocation */
+//static int curjob = -1; /* current job */
+#endif
+//static int ttyfd = -1;
+
+STATIC void restartjob(shinstance *, struct job *);
+STATIC void freejob(shinstance *, struct job *);
+STATIC struct job *getjob(shinstance *, const char *, int);
+STATIC shpid dowait(shinstance *, int, struct job *);
+STATIC shpid waitproc(shinstance *, int, struct job *, int *);
+STATIC void cmdtxt(shinstance *, union node *);
+STATIC void cmdlist(shinstance *, union node *, int);
+STATIC void cmdredirlist(shinstance *, union node *, int);
+STATIC void cmdputs(shinstance *, const char *);
+STATIC shpid forkparent(shinstance *psh, struct job *jp, union node *n, int mode, shpid pid);
+STATIC void forkchild(shinstance *psh, shpid pgrp, union node *n, int mode);
+#ifdef KASH_USE_FORKSHELL2
+# ifndef SH_FORKED_MODE
+struct forkshell2args
+{
+ shinstance *psh;
+ int mode;
+ shpid pgrp; /**< The forkchild() pgrp argument (-1 if not in group). */
+ union node *n;
+ void *argp; /**< Points to child callback data following this structure. */
+ int (* child)(shinstance *, union node *, void *);
+ struct stackmark smark; /* do we need this? */
+};
+static int forkshell2_thread(shinstance *psh, void *argp);
+# endif
+#endif
+
+
+/*
+ * Turn job control on and off.
+ *
+ * Note: This code assumes that the third arg to ioctl is a character
+ * pointer, which is true on Berkeley systems but not System V. Since
+ * System V doesn't have job control yet, this isn't a problem now.
+ */
+
+//MKINIT int jobctl;
+
+void
+setjobctl(shinstance *psh, int on)
+{
+ if (on == psh->jobctl || psh->rootshell == 0)
+ return;
+ if (on) {
+ int err;
+ int i;
+ if (psh->ttyfd != -1)
+ shfile_close(&psh->fdtab, psh->ttyfd);
+ if ((psh->ttyfd = shfile_open(&psh->fdtab, "/dev/tty", O_RDWR, 0)) == -1) {
+ for (i = 0; i < 3; i++) {
+ if (shfile_isatty(&psh->fdtab, i)
+ && (psh->ttyfd = shfile_dup(&psh->fdtab, i)) != -1)
+ break;
+ }
+ if (i == 3)
+ goto out;
+ }
+ /* Move to a high fd */
+ for (i = 10; i > 2; i--) {
+ if ((err = shfile_fcntl(&psh->fdtab, psh->ttyfd, F_DUPFD, (1 << i) - 1)) != -1)
+ break;
+ }
+ if (err != -1) {
+ shfile_close(&psh->fdtab, psh->ttyfd);
+ psh->ttyfd = err;
+ }
+ err = shfile_cloexec(&psh->fdtab, psh->ttyfd, 1);
+ if (err == -1) {
+ shfile_close(&psh->fdtab, psh->ttyfd);
+ psh->ttyfd = -1;
+ goto out;
+ }
+ do { /* while we are in the background */
+ if ((psh->initialpgrp = sh_tcgetpgrp(psh, psh->ttyfd)) < 0) {
+out:
+ out2str(psh, "sh: can't access tty; job control turned off\n");
+ mflag(psh) = 0;
+ return;
+ }
+ if (psh->initialpgrp == -1)
+ psh->initialpgrp = sh_getpgrp(psh);
+ else if (psh->initialpgrp != sh_getpgrp(psh)) {
+ sh_killpg(psh, 0, SIGTTIN);
+ continue;
+ }
+ } while (0);
+
+ setsignal(psh, SIGTSTP);
+ setsignal(psh, SIGTTOU);
+ setsignal(psh, SIGTTIN);
+ if (sh_getpgid(psh, 0) != psh->rootpid && sh_setpgid(psh, 0, psh->rootpid) == -1)
+ error(psh, "Cannot set process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ if (sh_tcsetpgrp(psh, psh->ttyfd, psh->rootpid) == -1)
+ error(psh, "Cannot set tty process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ } else { /* turning job control off */
+ if (sh_getpgid(psh, 0) != psh->initialpgrp && sh_setpgid(psh, 0, psh->initialpgrp) == -1)
+ error(psh, "Cannot set process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ if (sh_tcsetpgrp(psh, psh->ttyfd, psh->initialpgrp) == -1)
+ error(psh, "Cannot set tty process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ shfile_close(&psh->fdtab, psh->ttyfd);
+ psh->ttyfd = -1;
+ setsignal(psh, SIGTSTP);
+ setsignal(psh, SIGTTOU);
+ setsignal(psh, SIGTTIN);
+ }
+ psh->jobctl = on;
+}
+
+
+#ifdef mkinit
+INCLUDE <stdlib.h>
+
+SHELLPROC {
+ psh->backgndpid = -1;
+#if JOBS
+ psh->jobctl = 0;
+#endif
+}
+
+#endif
+
+
+
+#if JOBS
+int
+fgcmd(shinstance *psh, int argc, char **argv)
+{
+ struct job *jp;
+ int i;
+ int status;
+
+ nextopt(psh, "");
+ jp = getjob(psh, *psh->argptr, 0);
+ if (jp->jobctl == 0)
+ error(psh, "job not created under job control");
+ out1fmt(psh, "%s", jp->ps[0].cmd);
+ for (i = 1; i < jp->nprocs; i++)
+ out1fmt(psh, " | %s", jp->ps[i].cmd );
+ out1c(psh, '\n');
+ output_flushall(psh);
+
+ for (i = 0; i < jp->nprocs; i++)
+ if (sh_tcsetpgrp(psh, psh->ttyfd, jp->ps[i].pid) != -1)
+ break;
+
+ if (i >= jp->nprocs) {
+ error(psh, "Cannot set tty process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ }
+ restartjob(psh, jp);
+ INTOFF;
+ status = waitforjob(psh, jp);
+ INTON;
+ return status;
+}
+
+static void
+set_curjob(shinstance *psh, struct job *jp, int mode)
+{
+ struct job *jp1, *jp2;
+ int i, ji;
+
+ ji = (int)(jp - psh->jobtab);
+
+ /* first remove from list */
+ if (ji == psh->curjob)
+ psh->curjob = jp->prev_job;
+ else {
+ for (i = 0; i < psh->njobs; i++) {
+ if (psh->jobtab[i].prev_job != ji)
+ continue;
+ psh->jobtab[i].prev_job = jp->prev_job;
+ break;
+ }
+ }
+
+ /* Then re-insert in correct position */
+ switch (mode) {
+ case 0: /* job being deleted */
+ jp->prev_job = -1;
+ break;
+ case 1: /* newly created job or backgrounded job,
+ put after all stopped jobs. */
+ if (psh->curjob != -1 && psh->jobtab[psh->curjob].state == JOBSTOPPED) {
+ for (jp1 = psh->jobtab + psh->curjob; ; jp1 = jp2) {
+ if (jp1->prev_job == -1)
+ break;
+ jp2 = psh->jobtab + jp1->prev_job;
+ if (jp2->state != JOBSTOPPED)
+ break;
+ }
+ jp->prev_job = jp1->prev_job;
+ jp1->prev_job = ji;
+ break;
+ }
+ /* FALLTHROUGH */
+ case 2: /* newly stopped job - becomes psh->curjob */
+ jp->prev_job = psh->curjob;
+ psh->curjob = ji;
+ break;
+ }
+}
+
+int
+bgcmd(shinstance *psh, int argc, char **argv)
+{
+ struct job *jp;
+ int i;
+
+ nextopt(psh, "");
+ do {
+ jp = getjob(psh, *psh->argptr, 0);
+ if (jp->jobctl == 0)
+ error(psh, "job not created under job control");
+ set_curjob(psh, jp, 1);
+ out1fmt(psh, "[%ld] %s", (long)(jp - psh->jobtab + 1), jp->ps[0].cmd);
+ for (i = 1; i < jp->nprocs; i++)
+ out1fmt(psh, " | %s", jp->ps[i].cmd );
+ out1c(psh, '\n');
+ output_flushall(psh);
+ restartjob(psh, jp);
+ } while (*psh->argptr && *++psh->argptr);
+ return 0;
+}
+
+
+STATIC void
+restartjob(shinstance *psh, struct job *jp)
+{
+ struct procstat *ps;
+ int i;
+
+ if (jp->state == JOBDONE)
+ return;
+ INTOFF;
+ for (i = 0; i < jp->nprocs; i++)
+ if (sh_killpg(psh, jp->ps[i].pid, SIGCONT) != -1)
+ break;
+ if (i >= jp->nprocs)
+ error(psh, "Cannot continue job (%s)", sh_strerror(psh, errno));
+ for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) {
+ if (WIFSTOPPED(ps->status)) {
+ ps->status = -1;
+ jp->state = JOBRUNNING;
+ }
+ }
+ INTON;
+}
+#endif
+
+static void
+showjob(shinstance *psh, struct output *out, struct job *jp, int mode)
+{
+ int procno;
+ int st;
+ struct procstat *ps;
+ size_t col;
+ char s[64];
+
+#if JOBS
+ if (mode & SHOW_PGID) {
+ /* just output process (group) id of pipeline */
+ outfmt(out, "%" SHPID_PRI "\n", jp->ps->pid);
+ return;
+ }
+#endif
+
+ procno = jp->nprocs;
+ if (!procno)
+ return;
+
+ if (mode & SHOW_PID)
+ mode |= SHOW_MULTILINE;
+
+ if ((procno > 1 && !(mode & SHOW_MULTILINE))
+ || (mode & SHOW_SIGNALLED)) {
+ /* See if we have more than one status to report */
+ ps = jp->ps;
+ st = ps->status;
+ do {
+ int st1 = ps->status;
+ if (st1 != st)
+ /* yes - need multi-line output */
+ mode |= SHOW_MULTILINE;
+ if (st1 == -1 || !(mode & SHOW_SIGNALLED) || WIFEXITED(st1))
+ continue;
+ if (WIFSTOPPED(st1) || ((st1 = WTERMSIG(st1) & 0x7f)
+ && st1 != SIGINT && st1 != SIGPIPE))
+ mode |= SHOW_ISSIG;
+
+ } while (ps++, --procno);
+ procno = jp->nprocs;
+ }
+
+ if (mode & SHOW_SIGNALLED && !(mode & SHOW_ISSIG)) {
+ if (jp->state == JOBDONE && !(mode & SHOW_NO_FREE)) {
+ TRACE((psh, "showjob: freeing job %d\n", jp - psh->jobtab + 1));
+ freejob(psh, jp);
+ }
+ return;
+ }
+
+ for (ps = jp->ps; --procno >= 0; ps++) { /* for each process */
+ if (ps == jp->ps)
+ fmtstr(s, 16, "[%ld] %c ",
+ (long)(jp - psh->jobtab + 1),
+#if JOBS
+ jp == psh->jobtab + psh->curjob ? '+' :
+ psh->curjob != -1 && jp == psh->jobtab +
+ psh->jobtab[psh->curjob].prev_job ? '-' :
+#endif
+ ' ');
+ else
+ fmtstr(s, 16, " " );
+ col = strlen(s);
+ if (mode & SHOW_PID) {
+ fmtstr(s + col, 16, "%" SHPID_PRI " ", ps->pid);
+ col += strlen(s + col);
+ }
+ if (ps->status == -1) {
+ scopy("Running", s + col);
+ } else if (WIFEXITED(ps->status)) {
+ st = WEXITSTATUS(ps->status);
+ if (st)
+ fmtstr(s + col, 16, "Done(%d)", st);
+ else
+ fmtstr(s + col, 16, "Done");
+ } else {
+ const char *pszSigNm;
+#if JOBS
+ if (WIFSTOPPED(ps->status))
+ st = WSTOPSIG(ps->status);
+ else /* WIFSIGNALED(ps->status) */
+#endif
+ st = WTERMSIG(ps->status);
+ st &= 0x7f;
+ pszSigNm = st < NSIG ? strsignal(st) : NULL;
+ if (pszSigNm)
+ scopyn(pszSigNm, s + col, 32);
+ else
+ fmtstr(s + col, 16, "Signal %d", st);
+ if (WCOREDUMP(ps->status)) {
+ col += strlen(s + col);
+ scopyn(" (core dumped)", s + col, 64 - col);
+ }
+ }
+ col += strlen(s + col);
+ outstr(s, out);
+ do {
+ outc(' ', out);
+ col++;
+ } while (col < 30);
+ outstr(ps->cmd, out);
+ if (mode & SHOW_MULTILINE) {
+ if (procno > 0) {
+ outc(' ', out);
+ outc('|', out);
+ }
+ } else {
+ while (--procno >= 0)
+ outfmt(out, " | %s", (++ps)->cmd );
+ }
+ outc('\n', out);
+ }
+ flushout(out);
+ jp->changed = 0;
+ if (jp->state == JOBDONE && !(mode & SHOW_NO_FREE))
+ freejob(psh, jp);
+}
+
+
+int
+jobscmd(shinstance *psh, int argc, char **argv)
+{
+ int mode, m;
+ int sv = psh->jobs_invalid;
+
+ psh->jobs_invalid = 0;
+ mode = 0;
+ while ((m = nextopt(psh, "lp")))
+ if (m == 'l')
+ mode = SHOW_PID;
+ else
+ mode = SHOW_PGID;
+ if (*psh->argptr)
+ do
+ showjob(psh, psh->out1, getjob(psh, *psh->argptr,0), mode);
+ while (*++psh->argptr);
+ else
+ showjobs(psh, psh->out1, mode);
+ psh->jobs_invalid = sv;
+ return 0;
+}
+
+
+/*
+ * Print a list of jobs. If "change" is nonzero, only print jobs whose
+ * statuses have changed since the last call to showjobs.
+ *
+ * If the shell is interrupted in the process of creating a job, the
+ * result may be a job structure containing zero processes. Such structures
+ * will be freed here.
+ */
+
+void
+showjobs(shinstance *psh, struct output *out, int mode)
+{
+ int jobno;
+ struct job *jp;
+ int silent = 0;
+ shpid gotpid;
+
+ TRACE((psh, "showjobs(%x) called\n", mode));
+
+ /* If not even one one job changed, there is nothing to do */
+ gotpid = dowait(psh, 0, NULL);
+ while (dowait(psh, 0, NULL) > 0)
+ continue;
+#ifdef JOBS
+ /*
+ * Check if we are not in our foreground group, and if not
+ * put us in it.
+ */
+ if (mflag(psh) && gotpid != -1 && sh_tcgetpgrp(psh, psh->ttyfd) != sh_getpid(psh)) {
+ if (sh_tcsetpgrp(psh, psh->ttyfd, sh_getpid(psh)) == -1)
+ error(psh, "Cannot set tty process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ TRACE((psh, "repaired tty process group\n"));
+ silent = 1;
+ }
+#endif
+ if (psh->jobs_invalid)
+ return;
+
+ for (jobno = 1, jp = psh->jobtab ; jobno <= psh->njobs ; jobno++, jp++) {
+ if (!jp->used)
+ continue;
+ if (jp->nprocs == 0) {
+ freejob(psh, jp);
+ continue;
+ }
+ if ((mode & SHOW_CHANGED) && !jp->changed)
+ continue;
+ if (silent && jp->changed) {
+ jp->changed = 0;
+ continue;
+ }
+ showjob(psh, out, jp, mode);
+ }
+}
+
+/*
+ * Mark a job structure as unused.
+ */
+
+STATIC void
+freejob(shinstance *psh, struct job *jp)
+{
+ INTOFF;
+ if (jp->ps != &jp->ps0) {
+ ckfree(psh, jp->ps);
+ jp->ps = &jp->ps0;
+ }
+ jp->nprocs = 0;
+ jp->used = 0;
+#if JOBS
+ set_curjob(psh, jp, 0);
+#endif
+ INTON;
+}
+
+
+
+int
+waitcmd(shinstance *psh, int argc, char **argv)
+{
+ struct job *job;
+ int status, retval;
+ struct job *jp;
+
+ nextopt(psh, "");
+
+ if (!*psh->argptr) {
+ /* wait for all jobs */
+ jp = psh->jobtab;
+ if (psh->jobs_invalid)
+ return 0;
+ for (;;) {
+ if (jp >= psh->jobtab + psh->njobs) {
+ /* no running procs */
+ return 0;
+ }
+ if (!jp->used || jp->state != JOBRUNNING) {
+ jp++;
+ continue;
+ }
+ if (dowait(psh, 1, (struct job *)NULL) == -1)
+ return 128 + SIGINT;
+ jp = psh->jobtab;
+ }
+ }
+
+ retval = 127; /* XXXGCC: -Wuninitialized */
+ for (; *psh->argptr; psh->argptr++) {
+ job = getjob(psh, *psh->argptr, 1);
+ if (!job) {
+ retval = 127;
+ continue;
+ }
+ /* loop until process terminated or stopped */
+ while (job->state == JOBRUNNING) {
+ if (dowait(psh, 1, (struct job *)NULL) == -1)
+ return 128 + SIGINT;
+ }
+ status = job->ps[job->nprocs].status;
+ if (WIFEXITED(status))
+ retval = WEXITSTATUS(status);
+#if JOBS
+ else if (WIFSTOPPED(status))
+ retval = WSTOPSIG(status) + 128;
+#endif
+ else {
+ /* XXX: limits number of signals */
+ retval = WTERMSIG(status) + 128;
+ }
+ if (!iflag(psh))
+ freejob(psh, job);
+ }
+ return retval;
+}
+
+
+
+int
+jobidcmd(shinstance *psh, int argc, char **argv)
+{
+ struct job *jp;
+ int i;
+
+ nextopt(psh, "");
+ jp = getjob(psh, *psh->argptr, 0);
+ for (i = 0 ; i < jp->nprocs ; ) {
+ out1fmt(psh, "%" SHPID_PRI, jp->ps[i].pid);
+ out1c(psh, ++i < jp->nprocs ? ' ' : '\n');
+ }
+ return 0;
+}
+
+shpid
+getjobpgrp(shinstance *psh, const char *name)
+{
+ struct job *jp;
+
+ jp = getjob(psh, name, 1);
+ if (jp == 0)
+ return 0;
+ return -jp->ps[0].pid;
+}
+
+/*
+ * Convert a job name to a job structure.
+ */
+
+STATIC struct job *
+getjob(shinstance *psh, const char *name, int noerror)
+{
+ int jobno = -1;
+ struct job *jp;
+ int pid;
+ int i;
+ const char *err_msg = "No such job: %s";
+
+ if (name == NULL) {
+#if JOBS
+ jobno = psh->curjob;
+#endif
+ err_msg = "No current job";
+ } else if (name[0] == '%') {
+ if (is_number(name + 1)) {
+ jobno = number(psh, name + 1) - 1;
+ } else if (!name[2]) {
+ switch (name[1]) {
+#if JOBS
+ case 0:
+ case '+':
+ case '%':
+ jobno = psh->curjob;
+ err_msg = "No current job";
+ break;
+ case '-':
+ jobno = psh->curjob;
+ if (jobno != -1)
+ jobno = psh->jobtab[jobno].prev_job;
+ err_msg = "No previous job";
+ break;
+#endif
+ default:
+ goto check_pattern;
+ }
+ } else {
+ struct job *found;
+ check_pattern:
+ found = NULL;
+ for (jp = psh->jobtab, i = psh->njobs ; --i >= 0 ; jp++) {
+ if (!jp->used || jp->nprocs <= 0)
+ continue;
+ if ((name[1] == '?'
+ && strstr(jp->ps[0].cmd, name + 2))
+ || prefix(name + 1, jp->ps[0].cmd)) {
+ if (found) {
+ err_msg = "%s: ambiguous";
+ found = 0;
+ break;
+ }
+ found = jp;
+ }
+ }
+ if (found)
+ return found;
+ }
+
+ } else if (is_number(name)) {
+ pid = number(psh, name);
+ for (jp = psh->jobtab, i = psh->njobs ; --i >= 0 ; jp++) {
+ if (jp->used && jp->nprocs > 0
+ && jp->ps[jp->nprocs - 1].pid == pid)
+ return jp;
+ }
+ }
+
+ if (!psh->jobs_invalid && jobno >= 0 && jobno < psh->njobs) {
+ jp = psh->jobtab + jobno;
+ if (jp->used)
+ return jp;
+ }
+ if (!noerror)
+ error(psh, err_msg, name);
+ return 0;
+}
+
+
+
+/*
+ * Return a new job structure,
+ */
+
+struct job *
+makejob(shinstance *psh, union node *node, int nprocs)
+{
+ int i;
+ struct job *jp;
+
+ if (psh->jobs_invalid) {
+ for (i = psh->njobs, jp = psh->jobtab ; --i >= 0 ; jp++) {
+ if (jp->used)
+ freejob(psh, jp);
+ }
+ psh->jobs_invalid = 0;
+ }
+
+ for (i = psh->njobs, jp = psh->jobtab ; ; jp++) {
+ if (--i < 0) {
+ INTOFF;
+ if (psh->njobs == 0) {
+ psh->jobtab = ckmalloc(psh, 4 * sizeof psh->jobtab[0]);
+ } else {
+ jp = ckmalloc(psh, (psh->njobs + 4) * sizeof psh->jobtab[0]);
+ memcpy(jp, psh->jobtab, psh->njobs * sizeof jp[0]);
+ /* Relocate `ps' pointers */
+ for (i = 0; i < psh->njobs; i++)
+ if (jp[i].ps == &psh->jobtab[i].ps0)
+ jp[i].ps = &jp[i].ps0;
+ ckfree(psh, psh->jobtab);
+ psh->jobtab = jp;
+ }
+ jp = psh->jobtab + psh->njobs;
+ for (i = 4 ; --i >= 0 ; psh->jobtab[psh->njobs++].used = 0) { /*empty*/ }
+ INTON;
+ break;
+ }
+ if (jp->used == 0)
+ break;
+ }
+ INTOFF;
+ jp->state = JOBRUNNING;
+ jp->used = 1;
+ jp->changed = 0;
+ jp->nprocs = 0;
+#if JOBS
+ jp->jobctl = psh->jobctl;
+ set_curjob(psh, jp, 1);
+#endif
+ if (nprocs > 1) {
+ jp->ps = ckmalloc(psh, nprocs * sizeof (struct procstat));
+ } else {
+ jp->ps = &jp->ps0;
+ }
+ INTON;
+ TRACE((psh, "makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs,
+ jp - psh->jobtab + 1));
+ return jp;
+}
+
+
+/*
+ * Fork off a subshell. If we are doing job control, give the subshell its
+ * own process group. Jp is a job structure that the job is to be added to.
+ * N is the command that will be evaluated by the child. Both jp and n may
+ * be NULL. The mode parameter can be one of the following:
+ * FORK_FG - Fork off a foreground process.
+ * FORK_BG - Fork off a background process.
+ * FORK_NOJOB - Like FORK_FG, but don't give the process its own
+ * process group even if job control is on.
+ *
+ * When job control is turned off, background processes have their standard
+ * input redirected to /dev/null (except for the second and later processes
+ * in a pipeline).
+ */
+
+#ifndef KASH_USE_FORKSHELL2
+shpid
+forkshell(shinstance *psh, struct job *jp, union node *n, int mode)
+{
+ int pid;
+
+ TRACE((psh, "forkshell(%%%d, %p, %d) called\n", jp - psh->jobtab, n, mode));
+ switch ((pid = sh_fork(psh))) {
+ case -1:
+ TRACE((psh, "Fork failed, errno=%d\n", errno));
+ INTON;
+ error(psh, "Cannot fork");
+ return -1; /* won't get here */
+ case 0:
+ forkchild(psh, jp == NULL || jp->nprocs == 0 ? -1 : jp->ps[0].pid, n, mode);
+ return 0;
+ default:
+ return forkparent(psh, jp, n, mode, pid);
+ }
+}
+#else /* KASH_USE_FORKSHELL2 */
+shpid
+forkshell2(shinstance *psh, struct job *jp, union node *n, int mode,
+ int (*child)(struct shinstance *, void *, union node *),
+ union node *nchild, void *argp, size_t arglen,
+ void (*setupchild)(struct shinstance *, struct shinstance *, void *))
+{
+ shpid pid;
+
+# ifdef SH_FORKED_MODE
+ /*
+ * fork variant.
+ */
+ pid = sh_fork(psh);
+ if (pid == 0)
+ {
+ /* child */
+ forkchild(psh, jp == NULL || jp->nprocs == 0 ? -1 : jp->ps[0].pid, n, mode);
+ sh__exit(psh, child(psh, nchild, argp));
+ return 0;
+ }
+
+ /* parent */
+ if (pid != -1)
+ return forkparent(psh, jp, n, mode, pid);
+ TRACE((psh, "Fork failed, errno=%d\n", errno));
+ INTON;
+ (void)arglen;
+ (void)setupchild;
+ error(psh, "Cannot fork");
+ return -1; /* won't get here */
+
+# else
+ /*
+ * Clone the shell and start a thread to service the subshell.
+ */
+ struct shinstance *pshchild;
+
+ TRACE((psh, "forkshell2(%%%d, %p, %d, %p, %p, %p, %d) called\n",
+ jp - psh->jobtab, n, mode, child, nchild, argp, (int)arglen));
+
+ pshchild = sh_create_child_shell(psh);
+ if (pshchild) {
+ /* pack arguments */
+ struct forkshell2args *args = (struct forkshell2args *)sh_calloc(pshchild, sizeof(*args) + arglen, 1);
+ args->psh = pshchild;
+ args->argp = memcpy(args + 1, argp, arglen);
+ args->child = child;
+ args->mode = mode;
+ args->pgrp = jp == NULL || jp->nprocs == 0 ? -1 : jp->ps[0].pid;
+ setstackmark(pshchild, &args->smark);
+ args->n = copyparsetree(pshchild, n);
+ if (setupchild)
+ setupchild(pshchild, psh, args->argp);
+
+ /* start the thread */
+ pid = sh_thread_start(psh, pshchild, forkshell2_thread, args);
+ if (pid >= 0)
+ return forkparent(psh, jp, n, mode, pid);
+ error(psh, "sh_start_child_thread failed (%d)!", (int)pid);
+ }
+ else
+ error(psh, "sh_create_child_shell failed!");
+ return -1;
+# endif
+}
+#endif
+
+STATIC shpid
+forkparent(shinstance *psh, struct job *jp, union node *n, int mode, shpid pid)
+{
+ shpid pgrp;
+
+ if (psh->rootshell && mode != FORK_NOJOB && mflag(psh)) {
+ if (jp == NULL || jp->nprocs == 0)
+ pgrp = pid;
+ else
+ pgrp = jp->ps[0].pid;
+ /* This can fail because we are doing it in the child also */
+ (void)sh_setpgid(psh, pid, pgrp);
+ }
+ if (mode == FORK_BG)
+ psh->backgndpid = pid; /* set $! */
+ if (jp) {
+ struct procstat *ps = &jp->ps[jp->nprocs++];
+ ps->pid = pid;
+ ps->status = -1;
+ ps->cmd[0] = 0;
+ if (/* iflag && rootshell && */ n)
+ commandtext(psh, ps, n);
+ }
+ TRACE((psh, "In parent shell: child = %" SHPID_PRI "\n", pid));
+ return pid;
+}
+
+STATIC void
+forkchild(shinstance *psh, shpid pgrp, union node *n, int mode)
+{
+ int wasroot;
+ const char *devnull = _PATH_DEVNULL;
+ const char *nullerr = "Can't open %s";
+
+ wasroot = psh->rootshell;
+ TRACE((psh, "Child shell %" SHPID_PRI "\n", sh_getpid(psh)));
+ psh->rootshell = 0;
+
+ closescript(psh);
+ clear_traps(psh);
+#if JOBS
+ psh->jobctl = 0; /* do job control only in root shell */
+ if (wasroot && mode != FORK_NOJOB && mflag(psh)) {
+ if (pgrp == -1)
+ pgrp = sh_getpid(psh);
+ /* This can fail because we are doing it in the parent also.
+ And we must ignore SIGTTOU at this point or we'll be stopped! */
+ (void)sh_setpgid(psh, 0, pgrp);
+ if (mode == FORK_FG) {
+ if (sh_tcsetpgrp(psh, psh->ttyfd, pgrp) == -1)
+ error(psh, "Cannot set tty process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ }
+ setsignal(psh, SIGTSTP);
+ setsignal(psh, SIGTTOU);
+ } else
+#endif
+ if (mode == FORK_BG) {
+ ignoresig(psh, SIGINT);
+ ignoresig(psh, SIGQUIT);
+ if (pgrp == -1 && ! fd0_redirected_p(psh)) {
+ shfile_close(&psh->fdtab, 0);
+ if (shfile_open(&psh->fdtab, devnull, O_RDONLY, 0) != 0)
+ error(psh, nullerr, devnull);
+ }
+ }
+ if (wasroot && iflag(psh)) {
+ setsignal(psh, SIGINT);
+ setsignal(psh, SIGQUIT);
+ setsignal(psh, SIGTERM);
+ }
+
+ psh->jobs_invalid = 1;
+}
+
+#if defined(KASH_USE_FORKSHELL2) && !defined(SH_FORKED_MODE)
+/** thread procedure */
+static int forkshell2_thread(shinstance *psh, void *argp)
+{
+ struct forkshell2args * volatile volargs = (struct forkshell2args *)argp;
+ struct jmploc jmp;
+ TRACE2((psh, "forkshell2_thread:\n"));
+
+ if (setjmp(jmp.loc) == 0) {
+ struct forkshell2args * const args = volargs;
+
+ forkchild(psh, args->pgrp, args->n, args->mode);
+
+ psh->handler = &jmp;
+ return args->child(psh, args->n, args->argp);
+ }
+
+ /*
+ * (We longjmp'ed here.)
+ *
+ * This is copied from main() and simplified:
+ */
+ for (;;) {
+ psh = volargs->psh; /* longjmp paranoia */
+
+ if (psh->exception != EXSHELLPROC) {
+ if (psh->exception == EXEXEC)
+ psh->exitstatus = psh->exerrno;
+ else if (psh->exception == EXERROR)
+ psh->exitstatus = 2;
+ TRACE2((psh, "forkshell2_thread: exception=%d -> exitshell2(,%d)\n", psh->exception, psh->exitstatus));
+ return exitshell2(psh, psh->exitstatus);
+ }
+
+ /* EXSHELLPROC - tryexec gets us here and it wants to run a program
+ hoping (?) it's a shell script. We must reset the shell state and
+ turn ourselves into a root shell before doing so. */
+ TRACE2((psh, "forkshell2_thread: exception=EXSHELLPROC\n"));
+ psh->rootpid = /*getpid()*/ psh->pid;
+ psh->rootshell = 1;
+ psh->minusc = NULL;
+
+ reset(psh);
+ popstackmark(psh, &volargs->smark);
+
+ FORCEINTON; /* enable interrupts */
+
+ /* state3: */
+ if (sflag(psh) == 0) {
+# ifdef SIGTSTP
+ static int sigs[] = { SIGINT, SIGQUIT, SIGHUP, SIGPIPE, SIGTSTP };
+# else
+ static int sigs[] = { SIGINT, SIGQUIT, SIGHUP, SIGPIPE };
+# endif
+ unsigned i;
+ for (i = 0; i < K_ELEMENTS(sigs); i++)
+ setsignal(psh, sigs[i]);
+ }
+
+ if (setjmp(jmp.loc) == 0) {
+ psh->handler = &jmp;
+ cmdloop(psh, 1);
+ TRACE2((psh, "forkshell2_thread: cmdloop returned -> exitshell2(,%d)\n", psh->exitstatus));
+ return exitshell2(psh, psh->exitstatus);
+ }
+ }
+}
+#endif
+
+
+/*
+ * Wait for job to finish.
+ *
+ * Under job control we have the problem that while a child process is
+ * running interrupts generated by the user are sent to the child but not
+ * to the shell. This means that an infinite loop started by an inter-
+ * active user may be hard to kill. With job control turned off, an
+ * interactive user may place an interactive program inside a loop. If
+ * the interactive program catches interrupts, the user doesn't want
+ * these interrupts to also abort the loop. The approach we take here
+ * is to have the shell ignore interrupt signals while waiting for a
+ * forground process to terminate, and then send itself an interrupt
+ * signal if the child process was terminated by an interrupt signal.
+ * Unfortunately, some programs want to do a bit of cleanup and then
+ * exit on interrupt; unless these processes terminate themselves by
+ * sending a signal to themselves (instead of calling exit) they will
+ * confuse this approach.
+ */
+
+int
+waitforjob(shinstance *psh, struct job *jp)
+{
+#if JOBS
+ shpid mypgrp = sh_getpgrp(psh);
+#endif
+ int status;
+ int st;
+
+ INTOFF;
+ TRACE((psh, "waitforjob(%%%d) called\n", jp - psh->jobtab + 1));
+ while (jp->state == JOBRUNNING) {
+ dowait(psh, 1, jp);
+ }
+#if JOBS
+ if (jp->jobctl) {
+ if (sh_tcsetpgrp(psh, psh->ttyfd, mypgrp) == -1)
+ error(psh, "Cannot set tty process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ }
+ if (jp->state == JOBSTOPPED && psh->curjob != jp - psh->jobtab)
+ set_curjob(psh, jp, 2);
+#endif
+ status = jp->ps[jp->nprocs - 1].status;
+ /* convert to 8 bits */
+ if (WIFEXITED(status))
+ st = WEXITSTATUS(status);
+#if JOBS
+ else if (WIFSTOPPED(status))
+ st = WSTOPSIG(status) + 128;
+#endif
+ else
+ st = WTERMSIG(status) + 128;
+ TRACE((psh, "waitforjob: job %d, nproc %d, status %x, st %x\n",
+ jp - psh->jobtab + 1, jp->nprocs, status, st ));
+#if JOBS
+ if (jp->jobctl) {
+ /*
+ * This is truly gross.
+ * If we're doing job control, then we did a TIOCSPGRP which
+ * caused us (the shell) to no longer be in the controlling
+ * session -- so we wouldn't have seen any ^C/SIGINT. So, we
+ * intuit from the subprocess exit status whether a SIGINT
+ * occurred, and if so interrupt ourselves. Yuck. - mycroft
+ */
+ if (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT)
+ sh_raise_sigint(psh);/*raise(SIGINT);*/
+ }
+#endif
+ if (! JOBS || jp->state == JOBDONE)
+ freejob(psh, jp);
+ INTON;
+ return st;
+}
+
+
+
+/*
+ * Wait for a process to terminate.
+ */
+
+STATIC shpid
+dowait(shinstance *psh, int block, struct job *job)
+{
+ shpid pid;
+ int status;
+ struct procstat *sp;
+ struct job *jp;
+ struct job *thisjob;
+ int done;
+ int stopped;
+
+ TRACE((psh, "dowait(%d) called\n", block));
+ do {
+ pid = waitproc(psh, block, job, &status);
+ TRACE((psh, "wait returns pid %" SHPID_PRI ", status %d\n", pid, status));
+ } while (pid == -1 && errno == EINTR && psh->gotsig[SIGINT - 1] == 0);
+ if (pid <= 0)
+ return pid;
+ INTOFF;
+ thisjob = NULL;
+ for (jp = psh->jobtab ; jp < psh->jobtab + psh->njobs ; jp++) {
+ if (jp->used) {
+ done = 1;
+ stopped = 1;
+ for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) {
+ if (sp->pid == -1)
+ continue;
+ if (sp->pid == pid) {
+ TRACE((psh, "Job %d: changing status of proc %" SHPID_PRI " from 0x%x to 0x%x\n",
+ jp - psh->jobtab + 1, pid, sp->status, status));
+ sp->status = status;
+ thisjob = jp;
+ }
+ if (sp->status == -1)
+ stopped = 0;
+ else if (WIFSTOPPED(sp->status))
+ done = 0;
+ }
+ if (stopped) { /* stopped or done */
+ int state = done ? JOBDONE : JOBSTOPPED;
+ if (jp->state != state) {
+ TRACE((psh, "Job %d: changing state from %d to %d\n", jp - psh->jobtab + 1, jp->state, state));
+ jp->state = state;
+#if JOBS
+ if (done)
+ set_curjob(psh, jp, 0);
+#endif
+ }
+ }
+ }
+ }
+
+ if (thisjob && thisjob->state != JOBRUNNING) {
+ int mode = 0;
+ if (!psh->rootshell || !iflag(psh))
+ mode = SHOW_SIGNALLED;
+ if (job == thisjob)
+ mode = SHOW_SIGNALLED | SHOW_NO_FREE;
+ if (mode)
+ showjob(psh, psh->out2, thisjob, mode);
+ else {
+ TRACE((psh, "Not printing status, rootshell=%d, job=%p\n",
+ psh->rootshell, job));
+ thisjob->changed = 1;
+ }
+ }
+
+ INTON;
+ return pid;
+}
+
+
+
+/*
+ * Do a wait system call. If job control is compiled in, we accept
+ * stopped processes. If block is zero, we return a value of zero
+ * rather than blocking.
+ */
+STATIC shpid
+waitproc(shinstance *psh, int block, struct job *jp, int *status)
+{
+ int flags = 0;
+
+#if JOBS
+ if (jp != NULL && jp->jobctl)
+ flags |= WUNTRACED;
+#endif
+ if (block == 0)
+ flags |= WNOHANG;
+ return sh_waitpid(psh, -1, status, flags);
+}
+
+/*
+ * return 1 if there are stopped jobs, otherwise 0
+ */
+//int job_warning = 0;
+int
+stoppedjobs(shinstance *psh)
+{
+ int jobno;
+ struct job *jp;
+
+ if (psh->job_warning || psh->jobs_invalid)
+ return (0);
+ for (jobno = 1, jp = psh->jobtab; jobno <= psh->njobs; jobno++, jp++) {
+ if (jp->used == 0)
+ continue;
+ if (jp->state == JOBSTOPPED) {
+ out2str(psh, "You have stopped jobs.\n");
+ psh->job_warning = 2;
+ return (1);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * Return a string identifying a command (to be printed by the
+ * jobs command).
+ */
+
+//STATIC char *cmdnextc;
+//STATIC int cmdnleft;
+
+void
+commandtext(shinstance *psh, struct procstat *ps, union node *n)
+{
+ int len;
+
+ psh->cmdnextc = ps->cmd;
+ if (iflag(psh) || mflag(psh) || sizeof(ps->cmd) < 100)
+ len = sizeof(ps->cmd);
+ else
+ len = sizeof(ps->cmd) / 10;
+ psh->cmdnleft = len;
+ cmdtxt(psh, n);
+ if (psh->cmdnleft <= 0) {
+ char *p = ps->cmd + len - 4;
+ p[0] = '.';
+ p[1] = '.';
+ p[2] = '.';
+ p[3] = 0;
+ } else
+ *psh->cmdnextc = '\0';
+ TRACE((psh, "commandtext: ps->cmd %x, end %x, left %d\n\t\"%s\"\n",
+ ps->cmd, psh->cmdnextc, psh->cmdnleft, ps->cmd));
+}
+
+
+STATIC void
+cmdtxt(shinstance *psh, union node *n)
+{
+ union node *np;
+ struct nodelist *lp;
+ const char *p;
+ int i;
+ char s[2];
+
+ if (n == NULL || psh->cmdnleft <= 0)
+ return;
+ switch (n->type) {
+ case NSEMI:
+ cmdtxt(psh, n->nbinary.ch1);
+ cmdputs(psh, "; ");
+ cmdtxt(psh, n->nbinary.ch2);
+ break;
+ case NAND:
+ cmdtxt(psh, n->nbinary.ch1);
+ cmdputs(psh, " && ");
+ cmdtxt(psh, n->nbinary.ch2);
+ break;
+ case NOR:
+ cmdtxt(psh, n->nbinary.ch1);
+ cmdputs(psh, " || ");
+ cmdtxt(psh, n->nbinary.ch2);
+ break;
+ case NPIPE:
+ for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+ cmdtxt(psh, lp->n);
+ if (lp->next)
+ cmdputs(psh, " | ");
+ }
+ break;
+ case NSUBSHELL:
+ cmdputs(psh, "(");
+ cmdtxt(psh, n->nredir.n);
+ cmdputs(psh, ")");
+ break;
+ case NREDIR:
+ case NBACKGND:
+ cmdtxt(psh, n->nredir.n);
+ break;
+ case NIF:
+ cmdputs(psh, "if ");
+ cmdtxt(psh, n->nif.test);
+ cmdputs(psh, "; then ");
+ cmdtxt(psh, n->nif.ifpart);
+ if (n->nif.elsepart) {
+ cmdputs(psh, "; else ");
+ cmdtxt(psh, n->nif.elsepart);
+ }
+ cmdputs(psh, "; fi");
+ break;
+ case NWHILE:
+ cmdputs(psh, "while ");
+ goto until;
+ case NUNTIL:
+ cmdputs(psh, "until ");
+until:
+ cmdtxt(psh, n->nbinary.ch1);
+ cmdputs(psh, "; do ");
+ cmdtxt(psh, n->nbinary.ch2);
+ cmdputs(psh, "; done");
+ break;
+ case NFOR:
+ cmdputs(psh, "for ");
+ cmdputs(psh, n->nfor.var);
+ cmdputs(psh, " in ");
+ cmdlist(psh, n->nfor.args, 1);
+ cmdputs(psh, "; do ");
+ cmdtxt(psh, n->nfor.body);
+ cmdputs(psh, "; done");
+ break;
+ case NCASE:
+ cmdputs(psh, "case ");
+ cmdputs(psh, n->ncase.expr->narg.text);
+ cmdputs(psh, " in ");
+ for (np = n->ncase.cases; np; np = np->nclist.next) {
+ cmdtxt(psh, np->nclist.pattern);
+ cmdputs(psh, ") ");
+ cmdtxt(psh, np->nclist.body);
+ cmdputs(psh, ";; ");
+ }
+ cmdputs(psh, "esac");
+ break;
+ case NDEFUN:
+ cmdputs(psh, n->narg.text);
+ cmdputs(psh, "() { ... }");
+ break;
+ case NCMD:
+ cmdlist(psh, n->ncmd.args, 1);
+ cmdredirlist(psh, n->ncmd.redirect, 0);
+ break;
+ case NARG:
+ cmdputs(psh, n->narg.text);
+ break;
+ case NTO:
+ p = ">"; i = 1; goto redir;
+ case NCLOBBER:
+ p = ">|"; i = 1; goto redir;
+ case NAPPEND:
+ p = ">>"; i = 1; goto redir;
+ case NTOFD:
+ p = ">&"; i = 1; goto redir;
+ case NFROM:
+ p = "<"; i = 0; goto redir;
+ case NFROMFD:
+ p = "<&"; i = 0; goto redir;
+ case NFROMTO:
+ p = "<>"; i = 0; goto redir;
+redir:
+ if (n->nfile.fd != i) {
+ s[0] = n->nfile.fd + '0';
+ s[1] = '\0';
+ cmdputs(psh, s);
+ }
+ cmdputs(psh, p);
+ if (n->type == NTOFD || n->type == NFROMFD) {
+ s[0] = n->ndup.dupfd + '0';
+ s[1] = '\0';
+ cmdputs(psh, s);
+ } else {
+ cmdtxt(psh, n->nfile.fname);
+ }
+ break;
+ case NHERE:
+ case NXHERE:
+ cmdputs(psh, "<<...");
+ break;
+ default:
+ cmdputs(psh, "???");
+ break;
+ }
+}
+
+STATIC void
+cmdlist(shinstance *psh, union node *np, int sep)
+{
+ for (; np; np = np->narg.next) {
+ if (!sep)
+ cmdputs(psh, " ");
+ cmdtxt(psh, np);
+ if (sep && np->narg.next)
+ cmdputs(psh, " ");
+ }
+}
+
+STATIC void
+cmdredirlist(shinstance *psh, union node *np, int sep)
+{
+ for (; np; np = np->nfile.next) {
+ if (!sep)
+ cmdputs(psh, " ");
+ cmdtxt(psh, np);
+ if (sep && np->nfile.next)
+ cmdputs(psh, " ");
+ }
+}
+
+
+STATIC void
+cmdputs(shinstance *psh, const char *s)
+{
+ const char *p, *str = 0;
+ char c, cc[2] = " ";
+ char *nextc;
+ int nleft;
+ int subtype = 0;
+ int quoted = 0;
+ static char vstype[16][4] = { "", "}", "-", "+", "?", "=",
+ "#", "##", "%", "%%" };
+
+ p = s;
+ nextc = psh->cmdnextc;
+ nleft = psh->cmdnleft;
+ while (nleft > 0 && (c = *p++) != 0) {
+ switch (c) {
+ case CTLESC:
+ c = *p++;
+ break;
+ case CTLVAR:
+ subtype = *p++;
+ if ((subtype & VSTYPE) == VSLENGTH)
+ str = "${#";
+ else
+ str = "${";
+ if (!(subtype & VSQUOTE) != !(quoted & 1)) {
+ quoted ^= 1;
+ c = '"';
+ } else
+ c = *str++;
+ break;
+ case CTLENDVAR:
+ if (quoted & 1) {
+ c = '"';
+ str = "}";
+ } else
+ c = '}';
+ quoted >>= 1;
+ subtype = 0;
+ break;
+ case CTLBACKQ:
+ c = '$';
+ str = "(...)";
+ break;
+ case CTLBACKQ+CTLQUOTE:
+ c = '"';
+ str = "$(...)\"";
+ break;
+ case CTLARI:
+ c = '$';
+ str = "((";
+ break;
+ case CTLENDARI:
+ c = ')';
+ str = ")";
+ break;
+ case CTLQUOTEMARK:
+ quoted ^= 1;
+ c = '"';
+ break;
+ case '=':
+ if (subtype == 0)
+ break;
+ str = vstype[subtype & VSTYPE];
+ if (subtype & VSNUL)
+ c = ':';
+ else
+ c = *str++;
+ if (c != '}')
+ quoted <<= 1;
+ break;
+ case '\'':
+ case '\\':
+ case '"':
+ case '$':
+ /* These can only happen inside quotes */
+ cc[0] = c;
+ str = cc;
+ c = '\\';
+ break;
+ default:
+ break;
+ }
+ do {
+ *nextc++ = c;
+ } while (--nleft > 0 && str && (c = *str++));
+ str = 0;
+ }
+ if ((quoted & 1) && nleft) {
+ *nextc++ = '"';
+ nleft--;
+ }
+ psh->cmdnleft = nleft;
+ psh->cmdnextc = nextc;
+}
diff --git a/src/kash/jobs.h b/src/kash/jobs.h
new file mode 100644
index 0000000..d1236b3
--- /dev/null
+++ b/src/kash/jobs.h
@@ -0,0 +1,114 @@
+/* $NetBSD: jobs.h,v 1.19 2003/11/27 21:16:14 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)jobs.h 8.2 (Berkeley) 5/4/95
+ */
+
+#include "output.h"
+
+/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */
+#define FORK_FG 0
+#define FORK_BG 1
+#define FORK_NOJOB 2
+
+#define FORK_JUST_IO 4 /* forking I/O subprocess/thread (here doc). */
+
+/* mode flags for showjob(s) */
+#define SHOW_PGID 0x01 /* only show pgid - for jobs -p */
+#define SHOW_MULTILINE 0x02 /* one line per process */
+#define SHOW_PID 0x04 /* include process pid */
+#define SHOW_CHANGED 0x08 /* only jobs whose state has changed */
+#define SHOW_SIGNALLED 0x10 /* only if stopped/exited on signal */
+#define SHOW_ISSIG 0x20 /* job was signalled */
+#define SHOW_NO_FREE 0x40 /* do not free job */
+
+
+/*
+ * A job structure contains information about a job. A job is either a
+ * single process or a set of processes contained in a pipeline. In the
+ * latter case, pidlist will be non-NULL, and will point to a -1 terminated
+ * array of pids.
+ */
+#define MAXCMDTEXT 200
+
+struct procstat {
+ shpid pid; /* process id */
+ int status; /* last process status from wait() */
+ char cmd[MAXCMDTEXT];/* text of command being run */
+};
+
+struct job {
+ struct procstat ps0; /* status of process */
+ struct procstat *ps; /* status or processes when more than one */
+ int nprocs; /* number of processes */
+ shpid pgrp; /* process group of this job */ /**< @todo is job:pgrp used anywhere? */
+ char state;
+#define JOBRUNNING 0 /* at least one proc running */
+#define JOBSTOPPED 1 /* all procs are stopped */
+#define JOBDONE 2 /* all procs are completed */
+ char used; /* true if this entry is in used */
+ char changed; /* true if status has changed */
+#if JOBS
+ char jobctl; /* job running under job control */
+ int prev_job; /* previous job index */
+#endif
+};
+
+/*extern pid_t backgndpid;*/ /* pid of last background process */
+/*extern int job_warning;*/ /* user was warned about stopped jobs */
+
+void setjobctl(struct shinstance *, int);
+int fgcmd(struct shinstance *, int, char **);
+int bgcmd(struct shinstance *, int, char **);
+int jobscmd(struct shinstance *, int, char **);
+void showjobs(struct shinstance *, struct output *, int);
+int waitcmd(struct shinstance *, int, char **);
+int jobidcmd(struct shinstance *, int, char **);
+union node;
+struct job *makejob(struct shinstance *, union node *, int);
+#ifdef KASH_USE_FORKSHELL2
+shpid forkshell2(struct shinstance *, struct job *, union node *, int,
+ int (*child)(struct shinstance *, void *, union node *),
+ union node *, void *, size_t,
+ void (*setupchild)(struct shinstance *, struct shinstance *, void *));
+#else
+shpid forkshell(struct shinstance *, struct job *, union node *, int);
+#endif
+int waitforjob(struct shinstance *, struct job *);
+int stoppedjobs(struct shinstance *);
+void commandtext(struct shinstance *, struct procstat *, union node *);
+shpid getjobpgrp(struct shinstance *, const char *);
+
+#if ! JOBS
+#define setjobctl(psh, on) /* do nothing */
+#endif
diff --git a/src/kash/machdep.h b/src/kash/machdep.h
new file mode 100644
index 0000000..c61e7be
--- /dev/null
+++ b/src/kash/machdep.h
@@ -0,0 +1,56 @@
+/* $NetBSD: machdep.h,v 1.11 2003/08/07 09:05:33 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)machdep.h 8.2 (Berkeley) 5/4/95
+ */
+
+/*
+ * Most machines require the value returned from malloc to be aligned
+ * in some way. The following macro will get this right on many machines.
+ */
+
+/* For the purposes of the allocation stack(s), this is nonsensical given
+ that struct stack_block does not align the 'space' member accordingly.
+ That member will be aligned according to the pointer size, so we
+ should do the same here and not mix in any 'double' nonsense: */
+#if 0
+#define SHELL_SIZE (sizeof(union {int i; char *cp; double d; }) - 1)
+#else
+#define SHELL_SIZE (sizeof(void *) - 1)
+#endif
+
+/*
+ * It appears that grabstackstr() will barf with such alignments
+ * because stalloc() will return a string allocated in a new stackblock.
+ */
+#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE)
diff --git a/src/kash/mail.c b/src/kash/mail.c
new file mode 100644
index 0000000..4a50b38
--- /dev/null
+++ b/src/kash/mail.c
@@ -0,0 +1,119 @@
+/* $NetBSD: mail.c,v 1.16 2003/08/07 09:05:33 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)mail.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: mail.c,v 1.16 2003/08/07 09:05:33 agc Exp $");
+#endif /* not lint */
+#endif
+
+/*
+ * Routines to check for mail. (Perhaps make part of main.c?)
+ */
+#include <sys/types.h>
+#include <stdlib.h>
+
+#include "shell.h"
+#include "exec.h" /* defines padvance() */
+#include "var.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mail.h"
+#include "shinstance.h"
+
+
+/*#define MAXMBOXES 10*/
+
+
+/*STATIC int nmboxes;*/ /* number of mailboxes */
+/*STATIC time_t mailtime[MAXMBOXES];*/ /* times of mailboxes */
+
+
+
+/*
+ * Print appropriate message(s) if mail has arrived. If the argument is
+ * nozero, then the value of MAIL has changed, so we just update the
+ * values.
+ */
+
+void
+chkmail(shinstance *psh, int silent)
+{
+ int i;
+ const char *mpath;
+ char *p;
+ char *q;
+ struct stackmark smark;
+ struct stat statb;
+
+ if (silent)
+ psh->nmboxes = 10;
+ if (psh->nmboxes == 0)
+ return;
+ setstackmark(psh, &smark);
+ mpath = mpathset(psh) ? mpathval(psh) : mailval(psh);
+ for (i = 0 ; i < psh->nmboxes ; i++) {
+ p = padvance(psh, &mpath, nullstr);
+ if (p == NULL)
+ break;
+ if (*p == '\0')
+ continue;
+ for (q = p ; *q ; q++);
+ if (q[-1] != '/')
+ sh_abort(psh);
+ q[-1] = '\0'; /* delete trailing '/' */
+#ifdef notdef /* this is what the System V shell claims to do (it lies) */
+ if (shfile_stat(&psh->fdtab, p, &statb) < 0)
+ statb.st_mtime = 0;
+ if (statb.st_mtime > psh->mailtime[i] && ! silent) {
+ out2str(psh, psh->pathopt ? psh->pathopt : "you have mail");
+ out2c(psh, '\n');
+ }
+ psh->mailtime[i] = statb.st_mtime;
+#else /* this is what it should do */
+ if (shfile_stat(&psh->fdtab, p, &statb) < 0)
+ statb.st_size = 0;
+ if (statb.st_size > psh->mailtime[i] && ! silent) {
+ out2str(psh, psh->pathopt ? psh->pathopt : "you have mail");
+ out2c(psh, '\n');
+ }
+ psh->mailtime[i] = statb.st_size;
+#endif
+ }
+ psh->nmboxes = i;
+ popstackmark(psh, &smark);
+}
diff --git a/src/kash/mail.h b/src/kash/mail.h
new file mode 100644
index 0000000..78c9c5e
--- /dev/null
+++ b/src/kash/mail.h
@@ -0,0 +1,37 @@
+/* $NetBSD: mail.h,v 1.10 2003/08/07 09:05:34 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)mail.h 8.2 (Berkeley) 5/4/95
+ */
+
+void chkmail(struct shinstance *, int);
diff --git a/src/kash/main.c b/src/kash/main.c
new file mode 100644
index 0000000..4e0517f
--- /dev/null
+++ b/src/kash/main.c
@@ -0,0 +1,494 @@
+/* $NetBSD: main.c,v 1.48 2003/09/14 12:09:29 jmmv Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1991, 1993\n\
+ The Regents of the University of California. All rights reserved.\n");
+#endif /* not lint */
+#ifndef lint
+static char sccsid[] = "@(#)main.c 8.7 (Berkeley) 7/19/95";
+#else
+__RCSID("$NetBSD: main.c,v 1.48 2003/09/14 12:09:29 jmmv Exp $");
+#endif /* not lint */
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <locale.h>
+
+
+#include "shell.h"
+#include "main.h"
+#include "mail.h"
+#include "options.h"
+#include "output.h"
+#include "parser.h"
+#include "nodes.h"
+#include "expand.h"
+#include "eval.h"
+#include "jobs.h"
+#include "input.h"
+#include "trap.h"
+#include "var.h"
+#include "show.h"
+#include "memalloc.h"
+#include "error.h"
+#include "init.h"
+#include "mystring.h"
+#include "exec.h"
+#include "cd.h"
+#include "shinstance.h"
+
+#define PROFILE 0
+
+/*int rootpid;
+int rootshell;*/
+#ifdef unused_variables
+STATIC union node *curcmd;
+STATIC union node *prevcmd;
+#endif
+
+STATIC void read_profile(struct shinstance *, const char *);
+STATIC char *find_dot_file(struct shinstance *, char *);
+int main(int, char **, char **);
+SH_NORETURN_1 void shell_main(shinstance *, int, char **) SH_NORETURN_2;
+#ifdef _MSC_VER
+extern void init_syntax(void);
+#endif
+STATIC int usage(const char *argv0);
+STATIC int version(const char *argv0);
+
+/*
+ * Main routine. We initialize things, parse the arguments, execute
+ * profiles if we're a login shell, and then call cmdloop to execute
+ * commands. The setjmp call sets up the location to jump to when an
+ * exception occurs. When an exception occurs the variable "state"
+ * is used to figure out how far we had gotten.
+ */
+
+int
+#if K_OS == K_OS_WINDOWS && defined(SH_FORKED_MODE)
+real_main(int argc, char **argv, char **envp)
+#else
+main(int argc, char **argv, char **envp)
+#endif
+{
+ shinstance *psh;
+
+ /*
+ * Global initializations.
+ */
+ setlocale(LC_ALL, "");
+#ifdef _MSC_VER
+ init_syntax();
+#endif
+
+ /*
+ * Check for --version and --help.
+ */
+ if (argc > 1 && argv[1][0] == '-' && argv[1][1] == '-') {
+ if (!strcmp(argv[1], "--help"))
+ return usage(argv[0]);
+ if (!strcmp(argv[1], "--version"))
+ return version(argv[0]);
+ }
+
+ /*
+ * Create the root shell instance.
+ */
+ psh = sh_create_root_shell(argv, envp);
+ if (!psh)
+ return 2;
+ shthread_set_shell(psh);
+ shell_main(psh, argc, psh->orgargv);
+ /* Not reached. */
+ return 89;
+}
+
+SH_NORETURN_1 void
+shell_main(shinstance *psh, int argc, char **argv)
+{
+ struct jmploc jmploc;
+ struct stackmark smark;
+ volatile int state;
+ char *shinit;
+
+ state = 0;
+ if (setjmp(jmploc.loc)) {
+ /*
+ * When a shell procedure is executed, we raise the
+ * exception EXSHELLPROC to clean up before executing
+ * the shell procedure.
+ */
+ switch (psh->exception) {
+ case EXSHELLPROC:
+ psh->rootpid = /*getpid()*/ psh->pid;
+ psh->rootshell = 1;
+ psh->minusc = NULL;
+ state = 3;
+ break;
+
+ case EXEXEC:
+ psh->exitstatus = psh->exerrno;
+ break;
+
+ case EXERROR:
+ psh->exitstatus = 2;
+ break;
+
+ default:
+ break;
+ }
+
+ if (psh->exception != EXSHELLPROC) {
+ if (state == 0 || iflag(psh) == 0 || ! psh->rootshell)
+ exitshell(psh, psh->exitstatus);
+ }
+ reset(psh);
+ if (psh->exception == EXINT
+#if ATTY
+ && (! attyset(psh) || equal(termval(psh), "emacs"))
+#endif
+ ) {
+ out2c(psh, '\n');
+ flushout(&psh->errout);
+ }
+ popstackmark(psh, &smark);
+ FORCEINTON; /* enable interrupts */
+ if (state == 1)
+ goto state1;
+ else if (state == 2)
+ goto state2;
+ else if (state == 3)
+ goto state3;
+ else
+ goto state4;
+ }
+ psh->handler = &jmploc;
+ psh->rootpid = /*getpid()*/ psh->pid;
+ psh->rootshell = 1;
+#ifdef DEBUG
+#if DEBUG == 2
+ debug(psh) = 1;
+#endif
+ opentrace(psh);
+ trputs(psh, "Shell args: "); trargs(psh, argv);
+#endif
+
+ init(psh);
+ setstackmark(psh, &smark);
+ procargs(psh, argc, argv);
+ if (argv[0] && argv[0][0] == '-') {
+ state = 1;
+ read_profile(psh, "/etc/profile");
+state1:
+ state = 2;
+ read_profile(psh, ".profile");
+ }
+state2:
+ state = 3;
+ if (sh_getuid(psh) == sh_geteuid(psh) && sh_getgid(psh) == sh_getegid(psh)) {
+ if ((shinit = lookupvar(psh, "ENV")) != NULL && *shinit != '\0') {
+ state = 3;
+ read_profile(psh, shinit);
+ }
+ }
+state3:
+ state = 4;
+ if (sflag(psh) == 0 || psh->minusc) {
+ static int sigs[] = {
+ SIGINT, SIGQUIT, SIGHUP,
+#ifdef SIGTSTP
+ SIGTSTP,
+#endif
+ SIGPIPE
+ };
+#define SIGSSIZE (sizeof(sigs)/sizeof(sigs[0]))
+ unsigned i;
+
+ for (i = 0; i < SIGSSIZE; i++)
+ setsignal(psh, sigs[i]);
+ }
+
+ if (psh->minusc)
+ evalstring(psh, psh->minusc, 0);
+
+ if (sflag(psh) || psh->minusc == NULL) {
+state4: /* XXX ??? - why isn't this before the "if" statement */
+ cmdloop(psh, 1);
+ }
+ exitshell(psh, psh->exitstatus);
+ /* NOTREACHED */
+}
+
+
+/*
+ * Read and execute commands. "Top" is nonzero for the top level command
+ * loop; it turns on prompting if the shell is interactive.
+ */
+
+void
+cmdloop(struct shinstance *psh, int top)
+{
+ union node *n;
+ struct stackmark smark;
+ int inter;
+ int numeof = 0;
+
+ TRACE((psh, "cmdloop(%d) called\n", top));
+ setstackmark(psh, &smark);
+ for (;;) {
+ if (psh->pendingsigs)
+ dotrap(psh);
+ inter = 0;
+ if (iflag(psh) && top) {
+ inter = 1;
+ showjobs(psh, psh->out2, SHOW_CHANGED);
+ chkmail(psh, 0);
+ flushout(&psh->errout);
+ }
+ n = parsecmd(psh, inter);
+ /* showtree(n); DEBUG */
+ if (n == NEOF) {
+ if (!top || numeof >= 50)
+ break;
+ if (!stoppedjobs(psh)) {
+ if (!Iflag(psh))
+ break;
+ out2str(psh, "\nUse \"exit\" to leave shell.\n");
+ }
+ numeof++;
+ } else if (n != NULL && nflag(psh) == 0) {
+ psh->job_warning = (psh->job_warning == 2) ? 1 : 0;
+ numeof = 0;
+ evaltree(psh, n, 0);
+ }
+ popstackmark(psh, &smark);
+ setstackmark(psh, &smark);
+ if (psh->evalskip == SKIPFILE) {
+ psh->evalskip = 0;
+ break;
+ }
+ }
+ popstackmark(psh, &smark);
+}
+
+
+
+/*
+ * Read /etc/profile or .profile. Return on error.
+ */
+
+STATIC void
+read_profile(struct shinstance *psh, const char *name)
+{
+ int fd;
+ int xflag_set = 0;
+ int vflag_set = 0;
+
+ INTOFF;
+ if ((fd = shfile_open(&psh->fdtab, name, O_RDONLY, 0)) >= 0)
+ setinputfd(psh, fd, 1);
+ INTON;
+ if (fd < 0)
+ return;
+ /* -q turns off -x and -v just when executing init files */
+ if (qflag(psh)) {
+ if (xflag(psh))
+ xflag(psh) = 0, xflag_set = 1;
+ if (vflag(psh))
+ vflag(psh) = 0, vflag_set = 1;
+ }
+ cmdloop(psh, 0);
+ if (qflag(psh)) {
+ if (xflag_set)
+ xflag(psh) = 1;
+ if (vflag_set)
+ vflag(psh) = 1;
+ }
+ popfile(psh);
+}
+
+
+
+/*
+ * Read a file containing shell functions.
+ */
+
+void
+readcmdfile(struct shinstance *psh, char *name)
+{
+ int fd;
+
+ INTOFF;
+ if ((fd = shfile_open(&psh->fdtab, name, O_RDONLY, 0)) >= 0)
+ setinputfd(psh, fd, 1);
+ else
+ error(psh, "Can't open %s", name);
+ INTON;
+ cmdloop(psh, 0);
+ popfile(psh);
+}
+
+
+
+/*
+ * Take commands from a file. To be compatible we should do a path
+ * search for the file, which is necessary to find sub-commands.
+ */
+
+
+STATIC char *
+find_dot_file(struct shinstance *psh, char *basename)
+{
+ char *fullname;
+ const char *path = pathval(psh);
+
+ /* don't try this for absolute or relative paths */
+ if (strchr(basename, '/'))
+ return basename;
+
+ while ((fullname = padvance(psh, &path, basename)) != NULL) {
+ if (shfile_stat_isreg(&psh->fdtab, fullname) > 0) {
+ /*
+ * Don't bother freeing here, since it will
+ * be freed by the caller.
+ */
+ return fullname;
+ }
+ stunalloc(psh, fullname);
+ }
+
+ /* not found in the PATH */
+ error(psh, "%s: not found", basename);
+ /* NOTREACHED */
+ return NULL;
+}
+
+int
+dotcmd(struct shinstance *psh, int argc, char **argv)
+{
+ psh->exitstatus = 0;
+
+ if (argc >= 2) { /* That's what SVR2 does */
+ char * const savedcommandname = psh->commandname;
+ int const savedcommandnamemalloc = psh->commandnamemalloc;
+ char *fullname;
+ struct stackmark smark;
+
+ setstackmark(psh, &smark);
+ fullname = find_dot_file(psh, argv[1]);
+ setinputfile(psh, fullname, 1);
+ psh->commandname = fullname;
+ psh->commandnamemalloc = 0;
+ cmdloop(psh, 0);
+ popfile(psh);
+ psh->commandname = savedcommandname;
+ psh->commandnamemalloc = savedcommandnamemalloc;
+ popstackmark(psh, &smark);
+ }
+ return psh->exitstatus;
+}
+
+
+int
+exitcmd(struct shinstance *psh, int argc, char **argv)
+{
+ if (stoppedjobs(psh))
+ return 0;
+ if (argc > 1)
+ psh->exitstatus = number(psh, argv[1]);
+ exitshell(psh, psh->exitstatus);
+ /* NOTREACHED */
+ return 1;
+}
+
+
+STATIC const char *
+strip_argv0(const char *argv0, unsigned *lenp)
+{
+ const char *tmp;
+
+ /* skip the path */
+ for (tmp = strpbrk(argv0, "\\/:"); tmp; tmp = strpbrk(argv0, "\\/:"))
+ argv0 = tmp + 1;
+
+ /* find the end, ignoring extenions */
+ tmp = strrchr(argv0, '.');
+ if (!tmp)
+ tmp = strchr(argv0, '\0');
+ *lenp = (unsigned)(tmp - argv0);
+ return argv0;
+}
+
+STATIC int
+usage(const char *argv0)
+{
+ unsigned len;
+ argv0 = strip_argv0(argv0, &len);
+
+ fprintf(stdout,
+ "usage: %.*s [-aCefnuvxIimqVEb] [+aCefnuvxIimqVEb] [-o option_name]\n"
+ " [+o option_name] [command_file [argument ...]]\n"
+ " or: %.*s -c [-aCefnuvxIimqVEb] [+aCefnuvxIimqVEb] [-o option_name]\n"
+ " [+o option_name] command_string [command_name [argument ...]]\n"
+ " or: %.*s -s [-aCefnuvxIimqVEb] [+aCefnuvxIimqVEb] [-o option_name]\n"
+ " [+o option_name] [argument ...]\n"
+ " or: %.*s --help\n"
+ " or: %.*s --version\n",
+ len, argv0, len, argv0, len, argv0, len, argv0, len, argv0);
+ return 0;
+}
+
+STATIC int
+version(const char *argv0)
+{
+ unsigned len;
+ strip_argv0(argv0, &len);
+
+ fprintf(stdout,
+ "%.*s - kBuild version %d.%d.%d (r%u)\n",
+ len, argv0,
+ KBUILD_VERSION_MAJOR, KBUILD_VERSION_MINOR, KBUILD_VERSION_PATCH, KBUILD_SVN_REV);
+ return 0;
+}
+
+
+/*
+ * Local Variables:
+ * c-file-style: bsd
+ * End:
+ */
diff --git a/src/kash/main.h b/src/kash/main.h
new file mode 100644
index 0000000..c8f54e9
--- /dev/null
+++ b/src/kash/main.h
@@ -0,0 +1,44 @@
+/* $NetBSD: main.h,v 1.10 2003/08/07 09:05:34 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)main.h 8.2 (Berkeley) 5/4/95
+ */
+
+#include "shtypes.h"
+/*extern int rootpid;*/ /* pid of main shell */
+/*extern int rootshell;*/ /* true if we aren't a child of the main shell */
+
+void readcmdfile(struct shinstance *, char *);
+void cmdloop(struct shinstance *, int);
+int dotcmd(struct shinstance *, int, char **);
+int exitcmd(struct shinstance *, int, char **);
diff --git a/src/kash/memalloc.c b/src/kash/memalloc.c
new file mode 100644
index 0000000..9745ebf
--- /dev/null
+++ b/src/kash/memalloc.c
@@ -0,0 +1,725 @@
+/* $NetBSD: memalloc.c,v 1.28 2003/08/07 09:05:34 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)memalloc.c 8.3 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: memalloc.c,v 1.28 2003/08/07 09:05:34 agc Exp $");
+#endif /* not lint */
+#endif
+
+#include <stdlib.h>
+#include <stddef.h>
+
+#include "shell.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "machdep.h"
+#include "mystring.h"
+#include "shinstance.h"
+#include "nodes.h"
+
+/*
+ * Like malloc, but returns an error when out of space.
+ */
+
+pointer
+ckmalloc(shinstance *psh, size_t nbytes)
+{
+ pointer p;
+
+ p = sh_malloc(psh, nbytes);
+ if (p == NULL)
+ error(psh, "Out of space");
+ return p;
+}
+
+
+/*
+ * Same for realloc.
+ */
+
+pointer
+ckrealloc(struct shinstance *psh, pointer p, size_t nbytes)
+{
+ p = sh_realloc(psh, p, nbytes);
+ if (p == NULL)
+ error(psh, "Out of space");
+ return p;
+}
+
+
+/*
+ * Make a copy of a string in safe storage.
+ */
+
+char *
+savestr(struct shinstance *psh, const char *s)
+{
+ char *p;
+ size_t len = strlen(s);
+
+ p = ckmalloc(psh, len + 1);
+ memcpy(p, s, len + 1);
+ return p;
+}
+
+
+/*
+ * Parse trees for commands are allocated in lifo order, so we use a stack
+ * to make this more efficient, and also to avoid all sorts of exception
+ * handling code to handle interrupts in the middle of a parse.
+ *
+ * The size 504 was chosen because the Ultrix malloc handles that size
+ * well.
+ */
+
+//#define MINSIZE 504 /* minimum size of a block */
+
+//struct stack_block {
+// struct stack_block *prev;
+// char space[MINSIZE];
+//};
+
+//struct stack_block stackbase;
+//struct stack_block *stackp = &stackbase;
+//struct stackmark *markp;
+//char *stacknxt = stackbase.space;
+//int stacknleft = MINSIZE;
+//int sstrnleft;
+//int herefd = -1;
+
+pointer
+stalloc(shinstance *psh, size_t nbytes)
+{
+ char *p;
+
+ nbytes = SHELL_ALIGN(nbytes);
+ if (nbytes > (size_t)psh->stacknleft || psh->stacknleft < 0) {
+ size_t blocksize;
+ struct stack_block *sp;
+
+ blocksize = nbytes;
+ if (blocksize < MINSIZE)
+ blocksize = MINSIZE;
+ INTOFF;
+ sp = ckmalloc(psh, sizeof(struct stack_block) - MINSIZE + blocksize);
+ sp->prev = psh->stackp;
+ psh->stacknxt = sp->space;
+ psh->stacknleft = (int)blocksize;
+ psh->stackp = sp;
+ INTON;
+ }
+ p = psh->stacknxt;
+ psh->stacknxt += nbytes;
+ psh->stacknleft -= (int)nbytes;
+ return p;
+}
+
+
+char *
+stsavestr(struct shinstance *psh, const char *src)
+{
+ if (src) {
+ size_t size = strlen(src) + 1;
+ char *dst = (char *)stalloc(psh, size);
+ return (char *)memcpy(dst, src, size);
+ }
+ return NULL;
+}
+
+
+void
+stunalloc(shinstance *psh, pointer p)
+{
+ if (p == NULL) { /*DEBUG */
+ shfile_write(&psh->fdtab, 2, "stunalloc\n", 10);
+ sh_abort(psh);
+ }
+ psh->stacknleft += (int)(psh->stacknxt - (char *)p);
+ psh->stacknxt = p;
+}
+
+
+
+void
+setstackmark(shinstance *psh, struct stackmark *mark)
+{
+ mark->stackp = psh->stackp;
+ mark->stacknxt = psh->stacknxt;
+ mark->stacknleft = psh->stacknleft;
+ mark->marknext = psh->markp;
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ mark->pstacksize = psh->pstacksize;
+#endif
+ psh->markp = mark;
+}
+
+
+void
+popstackmark(shinstance *psh, struct stackmark *mark)
+{
+ struct stack_block *sp;
+
+ INTOFF;
+ psh->markp = mark->marknext;
+ while (psh->stackp != mark->stackp) {
+ sp = psh->stackp;
+ psh->stackp = sp->prev;
+ ckfree(psh, sp);
+ }
+ psh->stacknxt = mark->stacknxt;
+ psh->stacknleft = mark->stacknleft;
+
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ pstackpop(psh, mark->pstacksize);
+#endif
+ INTON;
+}
+
+
+/*
+ * When the parser reads in a string, it wants to stick the string on the
+ * stack and only adjust the stack pointer when it knows how big the
+ * string is. Stackblock (defined in stack.h) returns a pointer to a block
+ * of space on top of the stack and stackblocklen returns the length of
+ * this block. Growstackblock will grow this space by at least one byte,
+ * possibly moving it (like realloc). Grabstackblock actually allocates the
+ * part of the block that has been used.
+ */
+
+void
+growstackblock(shinstance *psh)
+{
+ int newlen = SHELL_ALIGN(psh->stacknleft * 2 + 100);
+
+ if (psh->stacknxt == psh->stackp->space && psh->stackp != &psh->stackbase) {
+ struct stack_block *oldstackp;
+ struct stackmark *xmark;
+ struct stack_block *sp;
+
+ INTOFF;
+ oldstackp = psh->stackp;
+ sp = psh->stackp;
+ psh->stackp = sp->prev;
+ sp = ckrealloc(psh, (pointer)sp,
+ sizeof(struct stack_block) - MINSIZE + newlen);
+ sp->prev = psh->stackp;
+ psh->stackp = sp;
+ psh->stacknxt = sp->space;
+ psh->stacknleft = newlen;
+
+ /*
+ * Stack marks pointing to the start of the old block
+ * must be relocated to point to the new block
+ */
+ xmark = psh->markp;
+ while (xmark != NULL && xmark->stackp == oldstackp) {
+ xmark->stackp = psh->stackp;
+ xmark->stacknxt = psh->stacknxt;
+ xmark->stacknleft = psh->stacknleft;
+ xmark = xmark->marknext;
+ }
+ INTON;
+ } else {
+ char *oldspace = psh->stacknxt;
+ int oldlen = psh->stacknleft;
+ char *p = stalloc(psh, newlen);
+
+ (void)memcpy(p, oldspace, oldlen);
+ psh->stacknxt = p; /* free the space */
+ psh->stacknleft += newlen; /* we just allocated */
+ }
+}
+
+void
+grabstackblock(shinstance *psh, int len)
+{
+ len = SHELL_ALIGN(len);
+ psh->stacknxt += len;
+ psh->stacknleft -= len;
+}
+
+/*
+ * The following routines are somewhat easier to use than the above.
+ * The user declares a variable of type STACKSTR, which may be declared
+ * to be a register. The macro STARTSTACKSTR initializes things. Then
+ * the user uses the macro STPUTC to add characters to the string. In
+ * effect, STPUTC(psh, c, p) is the same as *p++ = c except that the stack is
+ * grown as necessary. When the user is done, she can just leave the
+ * string there and refer to it using stackblock(psh). Or she can allocate
+ * the space for it using grabstackstr(). If it is necessary to allow
+ * someone else to use the stack temporarily and then continue to grow
+ * the string, the user should use grabstack to allocate the space, and
+ * then call ungrabstr(p) to return to the previous mode of operation.
+ *
+ * USTPUTC is like STPUTC except that it doesn't check for overflow.
+ * CHECKSTACKSPACE can be called before USTPUTC to ensure that there
+ * is space for at least one character.
+ */
+
+char *
+growstackstr(shinstance *psh)
+{
+ int len = stackblocksize(psh);
+ if (psh->herefd >= 0 && len >= 1024) {
+ xwrite(psh, psh->herefd, stackblock(psh), len);
+ psh->sstrnleft = len - 1;
+ return stackblock(psh);
+ }
+ growstackblock(psh);
+ psh->sstrnleft = stackblocksize(psh) - len - 1;
+ return stackblock(psh) + len;
+}
+
+/*
+ * Called from CHECKSTRSPACE.
+ */
+
+char *
+makestrspace(shinstance *psh)
+{
+ int len = stackblocksize(psh) - psh->sstrnleft;
+ growstackblock(psh);
+ psh->sstrnleft = stackblocksize(psh) - len;
+ return stackblock(psh) + len;
+}
+
+
+/*
+ * Got better control having a dedicated function for this.
+ *
+ * Was: #define grabstackstr(psh, p) stalloc((psh), stackblocksize(psh) - (psh)->sstrnleft)
+ */
+char *
+grabstackstr(shinstance *psh, char *end)
+{
+ char * const pstart = stackblock(psh);
+ size_t nbytes = (size_t)(end - pstart);
+
+ kHlpAssert((uintptr_t)end >= (uintptr_t)pstart);
+ /*kHlpAssert(end[-1] == '\0'); - not if it's followed by ungrabstrackstr(), sigh. */
+ kHlpAssert(SHELL_ALIGN((uintptr_t)pstart) == (uintptr_t)pstart);
+ kHlpAssert(stackblocksize(psh) - psh->sstrnleft >= (ssize_t)nbytes);
+
+ nbytes = SHELL_ALIGN(nbytes);
+ psh->stacknxt += nbytes;
+ psh->stacknleft -= (int)nbytes;
+ kHlpAssert(psh->stacknleft >= 0);
+
+ return pstart;
+}
+
+void
+ungrabstackstr(shinstance *psh, char *s, char *p)
+{
+ kHlpAssert((size_t)(psh->stacknxt - p) <= SHELL_SIZE);
+ kHlpAssert((uintptr_t)s >= (uintptr_t)&psh->stackp->space[0]);
+ kHlpAssert((uintptr_t)p >= (uintptr_t)s);
+
+ psh->stacknleft += (int)(psh->stacknxt - s);
+ psh->stacknxt = s;
+ psh->sstrnleft = (int)(psh->stacknleft - (p - s));
+
+}
+
+
+/*
+ * Parser stack allocator.
+ */
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+
+unsigned pstackrelease(shinstance *psh, pstack_block *pst, const char *caller)
+{
+ unsigned refs;
+ if (pst) {
+ refs = sh_atomic_dec(&pst->refs);
+ TRACE2((NULL, "pstackrelease: %p - %u refs (%s)\n", pst, refs, caller)); K_NOREF(caller);
+ if (refs == 0) {
+ struct stack_block *top;
+ while ((top = pst->top) != &pst->first) {
+ pst->top = top->prev;
+ kHlpAssert(pst->top);
+ top->prev = NULL;
+ sh_free(psh, top);
+ }
+ pst->nextbyte = NULL;
+ pst->top = NULL;
+
+ if (!psh->freepstack)
+ psh->freepstack = pst;
+ else
+ sh_free(psh, pst);
+ }
+ } else
+ refs = 0;
+ return refs;
+}
+
+void pstackpop(shinstance *psh, unsigned target)
+{
+ kHlpAssert(target <= psh->pstacksize);
+ while (target < psh->pstacksize) {
+ unsigned idx = --psh->pstacksize;
+ pstack_block *pst = psh->pstack[idx];
+ psh->pstack[idx] = NULL;
+ if (psh->curpstack == pst) {
+ pstack_block *pstnext;
+ if (idx <= 0 || (pstnext = psh->pstack[idx - 1])->done)
+ psh->curpstack = NULL;
+ else
+ psh->curpstack = pstnext;
+ }
+ pstackrelease(psh, pst, "popstackmark");
+ }
+
+# ifndef NDEBUG
+ if (psh->curpstack) {
+ unsigned i;
+ for (i = 0; i < psh->pstacksize; i++)
+ if (psh->curpstack == psh->pstack[i])
+ break;
+ kHlpAssert(i < psh->pstacksize);
+ }
+# endif
+}
+
+
+unsigned pstackretain(pstack_block *pst)
+{
+ unsigned refs = sh_atomic_inc(&pst->refs);
+ kHlpAssert(refs > 1);
+ kHlpAssert(refs < 256 /* bogus, but useful */);
+ return refs;
+}
+
+K_INLINE void pstackpush(shinstance *psh, pstack_block *pst)
+{
+ unsigned i = psh->pstacksize;
+ if (i + 1 < psh->pstackalloced) {
+ /* likely, except for the first time */
+ } else {
+ psh->pstack = (pstack_block **)ckrealloc(psh, psh->pstack, sizeof(psh->pstack[0]) * (i + 32));
+ memset(&psh->pstack[i], 0, sizeof(psh->pstack[0]) * 32);
+ }
+ psh->pstack[i] = pst;
+ psh->pstacksize = i + 1;
+}
+
+/* Does not make it current! */
+unsigned pstackretainpush(shinstance *psh, pstack_block *pst)
+{
+ unsigned refs = pstackretain(pst);
+ pstackpush(psh, pst);
+ TRACE2((psh, "pstackretainpush: %p - entry %u - %u refs\n", pst, psh->pstacksize - 1, refs));
+ return refs;
+}
+
+pstack_block *pstackallocpush(shinstance *psh)
+{
+ size_t const blocksize = offsetof(pstack_block, first.space) + MINSIZE;
+ pstack_block *pst;
+
+ INTOFF;
+
+ /*
+ * Allocate and initialize it.
+ */
+ pst = psh->freepstack;
+ if (pst)
+ psh->freepstack = NULL;
+ else
+ pst = (pstack_block *)ckmalloc(psh, blocksize);
+ pst->nextbyte = &pst->first.space[0];
+ pst->avail = blocksize - offsetof(pstack_block, first.space);
+ pst->topsize = blocksize - offsetof(pstack_block, first.space);
+ pst->strleft = 0;
+ pst->top = &pst->first;
+ pst->allocations = 0;
+ pst->bytesalloced = 0;
+ pst->nodesalloced = 0;
+ pst->entriesalloced = 0;
+ pst->strbytesalloced = 0;
+ pst->blocks = 0;
+ pst->fragmentation = 0;
+ pst->refs = 1;
+ pst->done = K_FALSE;
+ pst->first.prev = NULL;
+
+ /*
+ * Push it onto the stack and make it current.
+ */
+ pstackpush(psh, pst);
+ psh->curpstack = pst;
+
+ INTON;
+ TRACE2((psh, "pstackallocpush: %p - entry %u\n", pst, psh->pstacksize - 1));
+ return pst;
+}
+
+/**
+ * Marks the block as done, preventing it from being marked current again.
+ */
+void pstackmarkdone(pstack_block *pst)
+{
+ pst->done = K_TRUE;
+}
+
+/**
+ * Allocates and pushes a new block onto the stack, min payload size @a nbytes.
+ */
+static void pstallocnewblock(shinstance *psh, pstack_block *pst, size_t nbytes)
+{
+ /* Allocate a new stack node. */
+ struct stack_block *sp;
+ size_t const blocksize = nbytes <= MINSIZE
+ ? offsetof(struct stack_block, space) + MINSIZE
+ : K_ALIGN_Z(nbytes + offsetof(struct stack_block, space), 1024);
+
+ INTOFF;
+ sp = ckmalloc(psh, blocksize);
+ sp->prev = pst->top;
+ pst->fragmentation += pst->avail;
+ pst->topsize = blocksize - offsetof(struct stack_block, space);
+ pst->avail = blocksize - offsetof(struct stack_block, space);
+ pst->nextbyte = sp->space;
+ pst->top = sp;
+ pst->blocks += 1;
+ INTON;
+}
+
+/**
+ * Tries to grow the current stack block to hold a minimum of @a nbytes,
+ * will allocate a new block and copy over pending string bytes if that's not
+ * possible.
+ */
+static void pstgrowblock(shinstance *psh, pstack_block *pst, size_t nbytes, size_t tocopy)
+{
+ struct stack_block *top = pst->top;
+ size_t blocksize;
+
+ kHlpAssert(pst->avail < nbytes); /* only called when we need more space */
+ kHlpAssert(tocopy <= pst->avail);
+
+ /* Double the size used thus far and add some fudge and alignment. Make
+ sure to at least allocate MINSIZE. */
+ blocksize = K_MAX(K_ALIGN_Z(pst->avail * 2 + 100 + offsetof(struct stack_block, space), 64), MINSIZE);
+
+ /* If that isn't sufficient, do request size w/ some fudge and alignment. */
+ if (blocksize < nbytes + offsetof(struct stack_block, space))
+ blocksize = K_ALIGN_Z(nbytes + offsetof(struct stack_block, space) + 100, 1024);
+
+ /*
+ * Reallocate the current stack node if we can.
+ */
+ if ( pst->nextbyte == &top->space[0] /* can't have anything else in the block */
+ && top->prev != NULL /* first block is embedded in pst and cannot be reallocated */ ) {
+ top = (struct stack_block *)ckrealloc(psh, top, blocksize);
+ pst->top = top;
+ pst->topsize = blocksize - offsetof(struct stack_block, space);
+ pst->avail = blocksize - offsetof(struct stack_block, space);
+ pst->nextbyte = top->space;
+ }
+ /*
+ * Otherwise allocate a new node and copy over the avail bytes
+ * from the old one.
+ */
+ else {
+ char const * const copysrc = pst->nextbyte;
+ pstallocnewblock(psh, pst, nbytes);
+ kHlpAssert(pst->avail >= nbytes);
+ kHlpAssert(pst->avail >= tocopy);
+ memcpy(pst->nextbyte, copysrc, tocopy);
+ }
+}
+
+K_INLINE void *pstallocint(shinstance *psh, pstack_block *pst, size_t nbytes)
+{
+ void *ret;
+
+ /*
+ * Align the size and make sure we've got sufficient bytes available:
+ */
+ nbytes = SHELL_ALIGN(nbytes);
+ if (pst->avail >= nbytes && (ssize_t)pst->avail >= 0) { /* likely*/ }
+ else pstallocnewblock(psh, pst, nbytes);
+
+ /*
+ * Carve out the return block.
+ */
+ ret = pst->nextbyte;
+ pst->nextbyte += nbytes;
+ pst->avail -= nbytes;
+ pst->bytesalloced += nbytes;
+ pst->allocations += 1;
+ return ret;
+}
+
+#endif /* KASH_SEPARATE_PARSER_ALLOCATOR */
+
+
+void *pstalloc(struct shinstance *psh, size_t nbytes)
+{
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ return pstallocint(psh, psh->curpstack, nbytes);
+#else
+ return stalloc(psh, nbytes);
+#endif
+}
+
+union node *pstallocnode(struct shinstance *psh, size_t nbytes)
+{
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ pstack_block * const pst = psh->curpstack;
+ union node * const ret = (union node *)pstallocint(psh, pst, nbytes);
+ pst->nodesalloced++;
+ ret->pblock = pst;
+ return ret;
+#else
+ return (union node *)pstalloc(psh, nbytes);
+#endif
+}
+
+struct nodelist *pstalloclist(struct shinstance *psh)
+{
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ pstack_block *pst = psh->curpstack;
+ pst->entriesalloced++;
+ return (struct nodelist *)pstallocint(psh, pst, sizeof(struct nodelist));
+#endif
+ return (struct nodelist *)pstalloc(psh, sizeof(struct nodelist));
+}
+
+char *pstsavestr(struct shinstance *psh, const char *str)
+{
+ if (str) {
+ size_t const nbytes = strlen(str) + 1;
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ pstack_block *pst = psh->curpstack;
+ pst->strbytesalloced += SHELL_ALIGN(nbytes);
+ return (char *)memcpy(pstallocint(psh, pst, nbytes), str, nbytes);
+#else
+ return (char *)memcpy(pstalloc(psh, nbytes), str, nbytes);
+#endif
+ }
+ return NULL;
+}
+
+char *pstmakestrspace(struct shinstance *psh, size_t minbytes, char *end)
+{
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ pstack_block *pst = psh->curpstack;
+ size_t const len = end - pst->nextbyte;
+
+ kHlpAssert(pst->avail - pst->strleft == len);
+ TRACE2((psh, "pstmakestrspace: len=%u minbytes=%u (=> %u)\n", len, minbytes, len + minbytes));
+
+ pstgrowblock(psh, pst, minbytes + len, len);
+
+ pst->strleft = pst->avail - len;
+ return pst->nextbyte + len;
+
+#else
+ size_t const len = end - stackblock(psh);
+
+ kHlpAssert(stackblocksize(psh) - psh->sstrnleft == len);
+ TRACE2((psh, "pstmakestrspace: len=%u minbytes=%u (=> %u)\n", len, minbytes, len + minbytes));
+
+ minbytes += len;
+ while (stackblocksize(psh) < minbytes)
+ growstackblock(psh);
+
+ psh->sstrnleft = (int)(stackblocksize(psh) - len);
+ return (char *)stackblock(psh) + len;
+#endif
+}
+
+/* PSTPUTC helper */
+char *pstputcgrow(shinstance *psh, char *end, char c)
+{
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ pstack_block *pst = psh->curpstack;
+ pst->strleft++; /* PSTPUTC() already incremented it. */
+ end = pstmakestrspace(psh, 1, end);
+ kHlpAssert(pst->strleft > 0);
+ pst->strleft--;
+#else
+ psh->sstrnleft++; /* PSTPUTC() already incremented it. */
+ end = pstmakestrspace(psh, 1, end);
+ kHlpAssert(psh->sstrnleft > 0);
+ psh->sstrnleft--;
+#endif
+ *end++ = c;
+ return end;
+}
+
+
+char *pstgrabstr(struct shinstance *psh, char *end)
+{
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ pstack_block *pst = psh->curpstack;
+ char * const pstart = pst->nextbyte;
+ size_t nbytes = (size_t)(end - pstart);
+
+ kHlpAssert((uintptr_t)end > (uintptr_t)pstart);
+ kHlpAssert(end[-1] == '\0');
+ kHlpAssert(SHELL_ALIGN((uintptr_t)pstart) == (uintptr_t)pstart);
+ kHlpAssert(pst->avail - pst->strleft >= nbytes);
+
+ nbytes = SHELL_ALIGN(nbytes); /** @todo don't align strings, align the other allocations. */
+ pst->nextbyte += nbytes;
+ pst->avail -= nbytes;
+ pst->strbytesalloced += nbytes;
+
+ return pstart;
+
+#else
+ char * const pstart = stackblock(psh);
+ size_t nbytes = (size_t)(end - pstart);
+
+ kHlpAssert((uintptr_t)end > (uintptr_t)pstart);
+ kHlpAssert(end[-1] == '\0');
+ kHlpAssert(SHELL_ALIGN((uintptr_t)pstart) == (uintptr_t)pstart);
+ kHlpAssert(stackblocksize(psh) - psh->sstrnleft >= nbytes);
+
+ nbytes = SHELL_ALIGN(nbytes); /** @todo don't align strings, align the other allocations. */
+ psh->stacknxt += nbytes;
+ psh->stacknleft -= (int)nbytes;
+
+ return pstart;
+#endif
+}
+
diff --git a/src/kash/memalloc.h b/src/kash/memalloc.h
new file mode 100644
index 0000000..c85e3ac
--- /dev/null
+++ b/src/kash/memalloc.h
@@ -0,0 +1,147 @@
+/* $NetBSD: memalloc.h,v 1.14 2003/08/07 09:05:34 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)memalloc.h 8.2 (Berkeley) 5/4/95
+ */
+
+struct stackmark {
+ struct stack_block *stackp;
+ char *stacknxt;
+ int stacknleft;
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ unsigned pstacksize;
+#endif
+ struct stackmark *marknext;
+};
+
+
+/*extern char *stacknxt;
+extern int stacknleft;
+extern int sstrnleft;
+extern int herefd;*/
+
+pointer ckmalloc(struct shinstance *, size_t);
+pointer ckrealloc(struct shinstance *, pointer, size_t);
+char *savestr(struct shinstance *, const char *);
+#define ckfree(psh, p) sh_free(psh, (pointer)(p))
+
+#ifndef SH_MEMALLOC_NO_STACK
+pointer stalloc(struct shinstance *, size_t);
+char *stsavestr(struct shinstance *, const char *);
+void stunalloc(struct shinstance *, pointer);
+void setstackmark(struct shinstance *, struct stackmark *);
+void popstackmark(struct shinstance *, struct stackmark *);
+void growstackblock(struct shinstance *);
+void grabstackblock(struct shinstance *, int);
+char *growstackstr(struct shinstance *);
+char *makestrspace(struct shinstance *);
+char *grabstackstr(struct shinstance *, char *); /* was #define using stalloc */
+void ungrabstackstr(struct shinstance *, char *, char *);
+
+#define stackblock(psh) (psh)->stacknxt
+#define stackblocksize(psh) (psh)->stacknleft
+#define STARTSTACKSTR(psh, p) p = stackblock(psh), (psh)->sstrnleft = stackblocksize(psh)
+#define STPUTC(psh, c, p) (--(psh)->sstrnleft >= 0? (*p++ = (c)) : (p = growstackstr(psh), *p++ = (c)))
+#define CHECKSTRSPACE(psh, n, p) { if ((psh)->sstrnleft < n) p = makestrspace(psh); }
+#define USTPUTC(psh, c, p) do { kHlpAssert((psh)->sstrnleft > 0); \
+ kHlpAssert(p - (char *)stackblock(psh) == stackblocksize(psh) - (psh)->sstrnleft); \
+ --(psh)->sstrnleft; *p++ = (c); } while (0)
+#define STACKSTRNUL(psh, p) ((psh)->sstrnleft == 0? (p = growstackstr(psh), *p = '\0') : (*p = '\0'))
+#define STUNPUTC(psh, p) (++(psh)->sstrnleft, --p)
+#define STADJUST(psh, amount, p) (p += (amount), (psh)->sstrnleft -= (amount))
+#endif /* SH_MEMALLOC_NO_STACK */
+
+/** @name Stack allocator for parser.
+ * This is a stripped down version of the general stack allocator interface for
+ * the exclusive use of the parser, so that parsetrees can be shared with
+ * subshells by simple reference counting.
+ * @{ */
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+struct pstack_block;
+unsigned pstackretain(struct pstack_block *);
+void pstackpop(struct shinstance *, unsigned);
+unsigned pstackrelease(struct shinstance *, struct pstack_block *, const char *);
+unsigned pstackretainpush(struct shinstance *, struct pstack_block *);
+struct pstack_block *pstackallocpush(struct shinstance *);
+void pstackmarkdone(struct pstack_block *);
+#endif
+void *pstalloc(struct shinstance *, size_t);
+union node;
+union node *pstallocnode(struct shinstance *, size_t);
+struct nodelist;
+struct nodelist *pstalloclist(struct shinstance *);
+char *pstsavestr(struct shinstance *, const char *); /* was: stsavestr */
+char *pstmakestrspace(struct shinstance *, size_t, char *); /* was: makestrspace / growstackstr */
+char *pstputcgrow(struct shinstance *, char *, char);
+char *pstgrabstr(struct shinstance *, char *); /* was: grabstackstr / grabstackblock*/
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+# define PSTBLOCK(psh) ((psh)->curpstack->nextbyte)
+# define PSTARTSTACKSTR(psh, p) do { \
+ pstack_block *pstmacro = (psh)->curpstack; \
+ pstmacro->strleft = pstmacro->avail; \
+ (p) = pstmacro->nextbyte; \
+ } while (0)
+# define PSTCHECKSTRSPACE(psh, n, p) do { \
+ if ((psh)->curpstack->strleft >= (n)) {/*likely*/} \
+ else { (p) = pstmakestrspace(psh, (n), (p)); kHlpAssert((psh)->curpstack->strleft >= (n)); } \
+ } while (0)
+# define PSTUPUTC(psh, c, p) do { \
+ kHlpAssert((psh)->curpstack->strleft > 0); \
+ (psh)->curpstack->strleft -= 1; \
+ *(p)++ = (c); \
+ } while (0)
+# define PSTPUTC(psh, c, p) do { \
+ if ((ssize_t)--(psh)->curpstack->strleft >= 0) *(p)++ = (c); \
+ else (p) = pstputcgrow(psh, (p), (c)); \
+ } while (0)
+# define PSTPUTSTRN(psh, str, n, p) do { \
+ pstack_block *pstmacro = (psh)->curpstack; \
+ if (pstmacro->strleft >= (size_t)(n)) {/*likely?*/} \
+ else (p) = pstmakestrspace(psh, (n), (p)); \
+ pstmacro->strleft -= (n); \
+ memcpy((p), (str), (n)); \
+ (p) += (n); \
+ } while (0)
+#else
+# define PSTBLOCK(psh) ((psh)->stacknxt)
+# define PSTARTSTACKSTR(psh, p) do { (p) = (psh)->stacknxt; (psh)->sstrnleft = (psh)->stacknleft; } while (0)
+# define PSTCHECKSTRSPACE(psh, n, p) do { if ((psh)->sstrnleft >= (n)) {/*likely*/} \
+ else { (p) = pstmakestrspace(psh, (n), (p)); kHlpAssert((psh)->sstrnleft >= (n)); } } while (0)
+# define PSTUPUTC(psh, c, p) do { kHlpAssert((psh)->sstrnleft > 0); --(psh)->sstrnleft; *(p)++ = (c); } while (0)
+# define PSTPUTC(psh, c, p) do { if (--(psh)->sstrnleft >= 0) *(p)++ = (c); else (p) = pstputcgrow(psh, (p), (c)); } while (0)
+# define PSTPUTSTRN(psh, str, n, p) do { if ((psh)->sstrnleft >= (size_t)(n)) {/*likely?*/} else (p) = pstmakestrspace(psh, (n), (p)); \
+ memcpy((p), (str), (n)); (psh)->sstrnleft -= (n); (p) += (n); } while (0)
+#endif
+/** @} */
+
+
diff --git a/src/kash/miscbltin.c b/src/kash/miscbltin.c
new file mode 100644
index 0000000..e251859
--- /dev/null
+++ b/src/kash/miscbltin.c
@@ -0,0 +1,443 @@
+/* $NetBSD: miscbltin.c,v 1.35 2005/03/19 14:22:50 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)miscbltin.c 8.4 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: miscbltin.c,v 1.35 2005/03/19 14:22:50 dsl Exp $");
+#endif /* not lint */
+#endif
+
+/*
+ * Miscelaneous builtins.
+ */
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "shell.h"
+#include "options.h"
+#include "var.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "miscbltin.h"
+#include "mystring.h"
+#include "shinstance.h"
+#include "shfile.h"
+
+#undef rflag
+
+void *kash_setmode(shinstance *psh, const char *p);
+mode_t kash_getmode(const void *bbox, mode_t omode);
+
+
+/*
+ * The read builtin.
+ * Backslahes escape the next char unless -r is specified.
+ *
+ * This uses unbuffered input, which may be avoidable in some cases.
+ *
+ * Note that if IFS=' :' then read x y should work so that:
+ * 'a b' x='a', y='b'
+ * ' a b ' x='a', y='b'
+ * ':b' x='', y='b'
+ * ':' x='', y=''
+ * '::' x='', y=''
+ * ': :' x='', y=''
+ * ':::' x='', y='::'
+ * ':b c:' x='', y='b c:'
+ */
+
+int
+readcmd(shinstance *psh, int argc, char **argv)
+{
+ char **ap;
+ char c;
+ int rflag;
+ char *prompt;
+ const char *ifs;
+ char *p;
+ int startword;
+ int status;
+ int i;
+ int is_ifs;
+ int saveall = 0;
+
+ rflag = 0;
+ prompt = NULL;
+ while ((i = nextopt(psh, "p:r")) != '\0') {
+ if (i == 'p')
+ prompt = psh->optionarg;
+ else
+ rflag = 1;
+ }
+
+ if (prompt && shfile_isatty(&psh->fdtab, 0)) {
+ out2str(psh, prompt);
+ output_flushall(psh);
+ }
+
+ if (*(ap = psh->argptr) == NULL)
+ error(psh, "arg count");
+
+ if ((ifs = bltinlookup(psh, "IFS", 1)) == NULL)
+ ifs = " \t\n";
+
+ status = 0;
+ startword = 2;
+ STARTSTACKSTR(psh, p);
+ for (;;) {
+ if (shfile_read(&psh->fdtab, 0, &c, 1) != 1) {
+ status = 1;
+ break;
+ }
+ if (c == '\0')
+ continue;
+ if (c == '\\' && !rflag) {
+ if (shfile_read(&psh->fdtab, 0, &c, 1) != 1) {
+ status = 1;
+ break;
+ }
+ if (c != '\n')
+ STPUTC(psh, c, p);
+ continue;
+ }
+ if (c == '\n')
+ break;
+ if (strchr(ifs, c))
+ is_ifs = strchr(" \t\n", c) ? 1 : 2;
+ else
+ is_ifs = 0;
+
+ if (startword != 0) {
+ if (is_ifs == 1) {
+ /* Ignore leading IFS whitespace */
+ if (saveall)
+ STPUTC(psh, c, p);
+ continue;
+ }
+ if (is_ifs == 2 && startword == 1) {
+ /* Only one non-whitespace IFS per word */
+ startword = 2;
+ if (saveall)
+ STPUTC(psh, c, p);
+ continue;
+ }
+ }
+
+ if (is_ifs == 0) {
+ /* append this character to the current variable */
+ startword = 0;
+ if (saveall)
+ /* Not just a spare terminator */
+ saveall++;
+ STPUTC(psh, c, p);
+ continue;
+ }
+
+ /* end of variable... */
+ startword = is_ifs;
+
+ if (ap[1] == NULL) {
+ /* Last variable needs all IFS chars */
+ saveall++;
+ STPUTC(psh, c, p);
+ continue;
+ }
+
+ STACKSTRNUL(psh, p);
+ setvar(psh, *ap, stackblock(psh), 0);
+ ap++;
+ STARTSTACKSTR(psh, p);
+ }
+ STACKSTRNUL(psh, p);
+
+ /* Remove trailing IFS chars */
+ for (; stackblock(psh) <= --p; *p = 0) {
+ if (!strchr(ifs, *p))
+ break;
+ if (strchr(" \t\n", *p))
+ /* Always remove whitespace */
+ continue;
+ if (saveall > 1)
+ /* Don't remove non-whitespace unless it was naked */
+ break;
+ }
+ setvar(psh, *ap, stackblock(psh), 0);
+
+ /* Set any remaining args to "" */
+ while (*++ap != NULL)
+ setvar(psh, *ap, nullstr, 0);
+ return status;
+}
+
+
+
+int
+umaskcmd(shinstance *psh, int argc, char **argv)
+{
+ char *ap;
+ int mask;
+ int i;
+ int symbolic_mode = 0;
+
+ while ((i = nextopt(psh, "S")) != '\0') {
+ symbolic_mode = 1;
+ }
+
+ mask = shfile_get_umask(&psh->fdtab);
+
+ if ((ap = *psh->argptr) == NULL) {
+ if (symbolic_mode) {
+ char u[4], g[4], o[4];
+
+ i = 0;
+ if ((mask & S_IRUSR) == 0)
+ u[i++] = 'r';
+ if ((mask & S_IWUSR) == 0)
+ u[i++] = 'w';
+ if ((mask & S_IXUSR) == 0)
+ u[i++] = 'x';
+ u[i] = '\0';
+
+ i = 0;
+ if ((mask & S_IRGRP) == 0)
+ g[i++] = 'r';
+ if ((mask & S_IWGRP) == 0)
+ g[i++] = 'w';
+ if ((mask & S_IXGRP) == 0)
+ g[i++] = 'x';
+ g[i] = '\0';
+
+ i = 0;
+ if ((mask & S_IROTH) == 0)
+ o[i++] = 'r';
+ if ((mask & S_IWOTH) == 0)
+ o[i++] = 'w';
+ if ((mask & S_IXOTH) == 0)
+ o[i++] = 'x';
+ o[i] = '\0';
+
+ out1fmt(psh, "u=%s,g=%s,o=%s\n", u, g, o);
+ } else {
+ out1fmt(psh, "%.4o\n", mask);
+ }
+ } else {
+ if (isdigit((unsigned char)*ap)) {
+ mask = 0;
+ do {
+ if (*ap >= '8' || *ap < '0')
+ error(psh, "Illegal number: %s", argv[1]);
+ mask = (mask << 3) + (*ap - '0');
+ } while (*++ap != '\0');
+ shfile_set_umask(&psh->fdtab, mask);
+ } else {
+ void *set;
+
+ INTOFF;
+ if ((set = kash_setmode(psh, ap)) != 0) {
+ mask = kash_getmode(set, ~mask & 0777);
+ ckfree(psh, set);
+ }
+ INTON;
+ if (!set)
+ error(psh, "Illegal mode: %s", ap);
+
+ shfile_set_umask(&psh->fdtab, ~mask & 0777);
+ }
+ }
+ return 0;
+}
+
+/*
+ * ulimit builtin
+ *
+ * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and
+ * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with
+ * ash by J.T. Conklin.
+ *
+ * Public domain.
+ */
+
+struct limits {
+ const char *name;
+ int cmd;
+ int factor; /* multiply by to get rlim_{cur,max} values */
+ char option;
+};
+
+static const struct limits limits[] = {
+#ifdef RLIMIT_CPU
+ { "time(seconds)", RLIMIT_CPU, 1, 't' },
+#endif
+#ifdef RLIMIT_FSIZE
+ { "file(blocks)", RLIMIT_FSIZE, 512, 'f' },
+#endif
+#ifdef RLIMIT_DATA
+ { "data(kbytes)", RLIMIT_DATA, 1024, 'd' },
+#endif
+#ifdef RLIMIT_STACK
+ { "stack(kbytes)", RLIMIT_STACK, 1024, 's' },
+#endif
+#ifdef RLIMIT_CORE
+ { "coredump(blocks)", RLIMIT_CORE, 512, 'c' },
+#endif
+#ifdef RLIMIT_RSS
+ { "memory(kbytes)", RLIMIT_RSS, 1024, 'm' },
+#endif
+#ifdef RLIMIT_MEMLOCK
+ { "locked memory(kbytes)", RLIMIT_MEMLOCK, 1024, 'l' },
+#endif
+#ifdef RLIMIT_NPROC
+ { "process(processes)", RLIMIT_NPROC, 1, 'p' },
+#endif
+#ifdef RLIMIT_NOFILE
+ { "nofiles(descriptors)", RLIMIT_NOFILE, 1, 'n' },
+#endif
+#ifdef RLIMIT_VMEM
+ { "vmemory(kbytes)", RLIMIT_VMEM, 1024, 'v' },
+#endif
+#ifdef RLIMIT_SWAP
+ { "swap(kbytes)", RLIMIT_SWAP, 1024, 'w' },
+#endif
+#ifdef RLIMIT_SBSIZE
+ { "sbsize(bytes)", RLIMIT_SBSIZE, 1, 'b' },
+#endif
+ { (char *) 0, 0, 0, '\0' }
+};
+
+int
+ulimitcmd(shinstance *psh, int argc, char **argv)
+{
+ int c;
+ shrlim_t val = 0;
+ enum { SOFT = 0x1, HARD = 0x2 }
+ how = SOFT | HARD;
+ const struct limits *l;
+ int set, all = 0;
+ int optc, what;
+ shrlimit limit;
+
+ what = 'f';
+ while ((optc = nextopt(psh, "HSabtfdsmcnpl")) != '\0')
+ switch (optc) {
+ case 'H':
+ how = HARD;
+ break;
+ case 'S':
+ how = SOFT;
+ break;
+ case 'a':
+ all = 1;
+ break;
+ default:
+ what = optc;
+ }
+
+ for (l = limits; l->name && l->option != what; l++)
+ ;
+ if (!l->name)
+ error(psh, "internal error (%c)", what);
+
+ set = *psh->argptr ? 1 : 0;
+ if (set) {
+ char *p = *psh->argptr;
+
+ if (all || psh->argptr[1])
+ error(psh, "too many arguments");
+ if (strcmp(p, "unlimited") == 0)
+ val = RLIM_INFINITY;
+ else {
+ val = (shrlim_t) 0;
+
+ while ((c = *p++) >= '0' && c <= '9')
+ {
+ shrlim_t const prev = val;
+ val = (val * 10) + (long)(c - '0');
+ if (val < prev)
+ break;
+ }
+ if (c)
+ error(psh, "bad number");
+ val *= l->factor;
+ }
+ }
+ if (all) {
+ for (l = limits; l->name; l++) {
+ sh_getrlimit(psh, l->cmd, &limit);
+ if (how & SOFT)
+ val = limit.rlim_cur;
+ else if (how & HARD)
+ val = limit.rlim_max;
+
+ out1fmt(psh, "%-20s ", l->name);
+ if (val == RLIM_INFINITY)
+ out1fmt(psh, "unlimited\n");
+ else
+ {
+ val /= l->factor;
+ out1fmt(psh, "%lld\n", (long long) val);
+ }
+ }
+ return 0;
+ }
+
+ sh_getrlimit(psh, l->cmd, &limit);
+ if (set) {
+ if (how & HARD)
+ limit.rlim_max = val;
+ if (how & SOFT)
+ limit.rlim_cur = val;
+ if (sh_setrlimit(psh, l->cmd, &limit) < 0)
+ error(psh, "error setting limit (%s)", sh_strerror(psh, errno));
+ } else {
+ if (how & SOFT)
+ val = limit.rlim_cur;
+ else if (how & HARD)
+ val = limit.rlim_max;
+
+ if (val == RLIM_INFINITY)
+ out1fmt(psh, "unlimited\n");
+ else
+ {
+ val /= l->factor;
+ out1fmt(psh, "%lld\n", (long long) val);
+ }
+ }
+ return 0;
+}
diff --git a/src/kash/miscbltin.h b/src/kash/miscbltin.h
new file mode 100644
index 0000000..af30411
--- /dev/null
+++ b/src/kash/miscbltin.h
@@ -0,0 +1,31 @@
+/* $NetBSD: miscbltin.h,v 1.3 2003/08/21 17:57:53 christos Exp $ */
+
+/*
+ * Copyright (c) 1997 Christos Zoulas. 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. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+int readcmd(struct shinstance *, int, char **);
+int umaskcmd(struct shinstance *, int, char **);
+int ulimitcmd(struct shinstance *, int, char **);
diff --git a/src/kash/mkbuiltins b/src/kash/mkbuiltins
new file mode 100755
index 0000000..e304034
--- /dev/null
+++ b/src/kash/mkbuiltins
@@ -0,0 +1,136 @@
+#!/bin/sh -
+# $NetBSD: mkbuiltins,v 1.21 2004/06/06 07:03:11 christos Exp $
+#
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# 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.
+#
+# @(#)mkbuiltins 8.2 (Berkeley) 5/4/95
+
+havehist=1
+if [ "X$1" = "X-h" ]; then
+ havehist=0
+ shift
+fi
+
+shell=$1
+builtins=$2
+objdir=$3
+
+havejobs=0
+if grep '^#define JOBS[ ]*1' ${shell} > /dev/null
+then
+ havejobs=1
+fi
+
+exec <$builtins 3> ${objdir}/builtins.c 4> ${objdir}/builtins.h
+
+echo '/*
+ * This file was generated by the mkbuiltins program.
+ */
+
+#include "shell.h"
+#include "builtins.h"
+
+const struct builtincmd builtincmd[] = {
+' >&3
+
+echo '/*
+ * This file was generated by the mkbuiltins program.
+ */
+
+#include "shtypes.h"
+
+struct builtincmd {
+ const char *name;
+ int (*builtin)(shinstance *, int, char **);
+};
+
+extern const struct builtincmd builtincmd[];
+extern const struct builtincmd splbltincmd[];
+
+' >&4
+
+specials=
+
+while read line
+do
+ set -- $line
+ [ -z "$1" ] && continue
+ case "$1" in
+ \#if*|\#def*|\#end*)
+ echo $line >&3
+ echo $line >&4
+ continue
+ ;;
+ esac
+ l1="${line###}"
+ [ "$l1" != "$line" ] && continue
+
+
+ func=$1
+ shift
+ [ x"$1" = x'-j' ] && {
+ [ $havejobs = 0 ] && continue
+ shift
+ }
+ [ x"$1" = x'-h' ] && {
+ [ $havehist = 0 ] && continue
+ shift
+ }
+ echo 'int '"$func"'(shinstance *, int, char **);' >&4
+ while
+ [ $# != 0 -a "$1" != '#' ]
+ do
+ [ "$1" = '-s' ] && {
+ specials="$specials $2 $func"
+ shift 2
+ continue;
+ }
+ [ "$1" = '-u' ] && shift
+ echo ' { "'$1'", '"$func"' },' >&3
+ shift
+ done
+done
+
+echo ' { 0, 0 },' >&3
+echo '};' >&3
+echo >&3
+echo 'const struct builtincmd splbltincmd[] = {' >&3
+
+set -- $specials
+while
+ [ $# != 0 ]
+do
+ echo ' { "'$1'", '"$2"' },' >&3
+ shift 2
+done
+
+echo ' { 0, 0 },' >&3
+echo "};" >&3
diff --git a/src/kash/mkinit.sh b/src/kash/mkinit.sh
new file mode 100755
index 0000000..0bf4bed
--- /dev/null
+++ b/src/kash/mkinit.sh
@@ -0,0 +1,199 @@
+#! /bin/sh
+# $NetBSD: mkinit.sh,v 1.2 2004/06/15 23:09:54 dsl Exp $
+
+# Copyright (c) 2003 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# This code is derived from software contributed to The NetBSD Foundation
+# by David Laight.
+#
+# 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 NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+
+srcs="$*"
+
+nl='
+'
+openparen='('
+backslash='\'
+
+includes=' "shell.h" "mystring.h" "init.h" '
+defines=
+decles=
+event_init=
+event_reset=
+event_shellproc=
+
+for src in $srcs; do
+ exec <$src
+ decnl="$nl"
+ while IFS=; read -r line; do
+ [ "$line" = x ]
+ case "$line " in
+ INIT["{ "]* ) event=init;;
+ RESET["{ "]* ) event=reset;;
+ SHELLPROC["{ "]* ) event=shellproc;;
+ INCLUDE[\ \ ]* )
+ IFS=' '
+ set -- $line
+ # ignore duplicates
+ [ "${includes}" != "${includes%* $2 }" ] && continue
+ includes="$includes$2 "
+ continue
+ ;;
+ MKINIT\ )
+ # struct declaration
+ decles="$decles$nl"
+ while
+ read -r line
+ decles="${decles}${line}${nl}"
+ [ "$line" != "};" ]
+ do
+ :
+ done
+ decnl="$nl"
+ continue
+ ;;
+ MKINIT["{ "]* )
+ # strip initialiser
+ def=${line#MKINIT}
+ comment="${def#*;}"
+ def="${def%;$comment}"
+ def="${def%%=*}"
+ def="${def% }"
+ decles="${decles}${decnl}extern${def};${comment}${nl}"
+ decnl=
+ continue
+ ;;
+ \#define[\ \ ]* )
+ IFS=' '
+ set -- $line
+ # Ignore those with arguments
+ [ "$2" = "${2##*$openparen}" ] || continue
+ # and multiline definitions
+ [ "$line" = "${line%$backslash}" ] || continue
+ defines="${defines}#undef $2${nl}${line}${nl}"
+ continue
+ ;;
+ * ) continue;;
+ esac
+ # code for events
+ ev="${nl} /* from $src: */${nl} {${nl}"
+ while
+ read -r line
+ [ "$line" != "}" ]
+ do
+ # The C program indented by an extra 6 chars using
+ # tabs then spaces. I need to compare the output :-(
+ indent=6
+ while
+ l=${line# }
+ [ "$l" != "$line" ]
+ do
+ indent=$(($indent + 8))
+ line="$l"
+ done
+ while
+ l=${line# }
+ [ "$l" != "$line" ]
+ do
+ indent=$(($indent + 1))
+ line="$l"
+ done
+ [ -z "$line" -o "$line" != "${line###}" ] && indent=0
+ while
+ [ $indent -ge 8 ]
+ do
+ ev="$ev "
+ indent="$(($indent - 8))"
+ done
+ while
+ [ $indent -gt 0 ]
+ do
+ ev="$ev "
+ indent="$(($indent - 1))"
+ done
+ ev="${ev}${line}${nl}"
+ done
+ ev="${ev} }${nl}"
+ eval event_$event=\"\$event_$event\$ev\"
+ done
+done
+
+exec >init.c.tmp
+
+echo "/*"
+echo " * This file was generated by the mkinit program."
+echo " */"
+echo
+
+IFS=' '
+for f in $includes; do
+ echo "#include $f"
+done
+echo "#include \"shinstance.h\""
+echo "#include \"nodes.h\""
+
+echo
+echo
+echo
+echo "$defines"
+echo
+echo "$decles"
+echo
+echo
+echo "/*"
+echo " * Initialization code."
+echo " */"
+echo
+echo "void"
+echo "init(shinstance *psh) {"
+echo "${event_init%$nl}"
+echo "}"
+echo
+echo
+echo
+echo "/*"
+echo " * This routine is called when an error or an interrupt occurs in an"
+echo " * interactive shell and control is returned to the main command loop."
+echo " */"
+echo
+echo "void"
+echo "reset(shinstance *psh) {"
+echo "${event_reset%$nl}"
+echo "}"
+echo
+echo
+echo
+echo "/*"
+echo " * This routine is called to initialize the shell to run a shell procedure."
+echo " */"
+echo
+echo "void"
+echo "initshellproc(shinstance *psh) {"
+echo "${event_shellproc%$nl}"
+echo "}"
+
+exec >&-
+mv init.c.tmp init.c
diff --git a/src/kash/mknodes.sh b/src/kash/mknodes.sh
new file mode 100755
index 0000000..f11fc2e
--- /dev/null
+++ b/src/kash/mknodes.sh
@@ -0,0 +1,232 @@
+#! /bin/sh
+# $NetBSD: mknodes.sh,v 1.1 2004/01/16 23:24:38 dsl Exp $
+
+# Copyright (c) 2003 The NetBSD Foundation, Inc.
+# All rights reserved.
+#
+# This code is derived from software contributed to The NetBSD Foundation
+# by David Laight.
+#
+# 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 NetBSD Foundation 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+
+nodetypes=$1
+nodes_pat=$2
+objdir="$3"
+
+exec <$nodetypes
+exec >$objdir/nodes.h.tmp
+
+echo "/*"
+echo " * This file was generated by mknodes.sh"
+echo " */"
+echo
+
+tagno=0
+while IFS=; read -r line; do
+ line="${line%%#*}"
+ IFS=' '
+ set -- $line
+ IFS=
+ [ -z "$2" ] && continue
+ case "$line" in
+ [" "]* )
+ IFS=' '
+ [ $field = 0 ] && struct_list="$struct_list $struct"
+ eval field_${struct}_$field=\"\$*\"
+ eval numfld_$struct=\$field
+ field=$(($field + 1))
+ ;;
+ * )
+ define=$1
+ struct=$2
+ echo "#define $define $tagno"
+ tagno=$(($tagno + 1))
+ eval define_$struct=\"\$define_$struct \$define\"
+ struct_define="$struct_define $struct"
+ field=0
+ ;;
+ esac
+done
+
+echo
+
+## @todo inconsistent indentation here.
+IFS=' '
+for struct in $struct_list; do
+ echo
+ echo
+ echo "struct $struct {"
+ echo "#ifdef KASH_SEPARATE_PARSER_ALLOCATOR"
+ echo " struct pstack_block *pblock;"
+ echo "#endif"
+ field=0
+ while
+ eval line=\"\$field_${struct}_$field\"
+ field=$(($field + 1))
+ [ -n "$line" ]
+ do
+ IFS=' '
+ set -- $line
+ name=$1
+ case $2 in
+ nodeptr ) type="union node *";;
+ nodelist ) type="struct nodelist *";;
+ string ) type="char *";;
+ int ) type="int ";;
+ * ) name=; shift 2; type="$*";;
+ esac
+ echo " $type$name;"
+ done
+ echo "};"
+done
+
+echo
+echo
+echo "union node {"
+echo "#ifdef KASH_SEPARATE_PARSER_ALLOCATOR"
+echo "# ifdef __GNUC__"
+echo " __extension__"
+echo "# endif"
+echo " struct {"
+echo " struct pstack_block *pblock;"
+echo " int type;"
+echo " };"
+echo "#else"
+echo " int type;"
+echo "#endif"
+for struct in $struct_list; do
+ echo " struct $struct $struct;"
+done
+echo "};"
+echo
+echo
+echo "struct nodelist {"
+echo " struct nodelist *next;"
+echo " union node *n;"
+echo "};"
+echo
+echo
+echo "union node *copyfunc(struct shinstance *, union node *);"
+echo "void freefunc(struct shinstance *, union node *);"
+
+exec <$nodes_pat
+exec >$objdir/nodes.c.tmp
+mv -f $objdir/nodes.h.tmp $objdir/nodes.h || exit 1
+
+echo "/*"
+echo " * This file was generated by mknodes.sh"
+echo " */"
+echo
+
+while IFS=; read -r line; do
+ IFS=' '
+ set -- $line
+ IFS=
+ case "$1" in
+ '%SIZES' )
+ echo "static const short nodesize[$tagno] = {"
+ IFS=' '
+ for struct in $struct_define; do
+ echo " SHELL_ALIGN(sizeof (struct $struct)),"
+ done
+ echo "};"
+ ;;
+ '%CALCSIZE' )
+ echo " if (n == NULL)"
+ echo " return;"
+ echo " funcblocksize += nodesize[n->type];"
+ echo " switch (n->type) {"
+ IFS=' '
+ for struct in $struct_list; do
+ eval defines=\"\$define_$struct\"
+ for define in $defines; do
+ echo " case $define:"
+ done
+ eval field=\$numfld_$struct
+ while
+ [ $field != 0 ]
+ do
+ eval line=\"\$field_${struct}_$field\"
+ field=$(($field - 1))
+ IFS=' '
+ set -- $line
+ name=$1
+ cl=")"
+ case $2 in
+ nodeptr ) fn=calcsize;;
+ nodelist ) fn=sizenodelist;;
+ string ) fn="funcstringsize += strlen"
+ cl=") + 1";;
+ * ) continue;;
+ esac
+ echo " ${fn}(n->$struct.$name${cl};"
+ done
+ echo " break;"
+ done
+ echo " };"
+ ;;
+ '%COPY' )
+ echo " if (n == NULL)"
+ echo " return NULL;"
+ echo " new = funcblock;"
+ echo " funcblock = (char *) funcblock + nodesize[n->type];"
+ echo " switch (n->type) {"
+ IFS=' '
+ for struct in $struct_list; do
+ eval defines=\"\$define_$struct\"
+ for define in $defines; do
+ echo " case $define:"
+ done
+ eval field=\$numfld_$struct
+ while
+ [ $field != 0 ]
+ do
+ eval line=\"\$field_${struct}_$field\"
+ field=$(($field - 1))
+ IFS=' '
+ set -- $line
+ name=$1
+ case $2 in
+ nodeptr ) fn="copynode(";;
+ nodelist ) fn="copynodelist(";;
+ string ) fn="nodesavestr(";;
+ int ) fn=;;
+ temp ) echo "unexpected 'temp' node type" >&2; exit 2;;
+ * ) continue;;
+ esac
+ f="$struct.$name"
+ echo " new->$f = ${fn}n->$f${fn:+)};"
+ done
+ echo " break;"
+ done
+ echo " };"
+ echo " new->type = n->type;"
+ ;;
+ * ) echo "$line";;
+ esac
+done
+
+exec >/dev/null
+mv -f $objdir/nodes.c.tmp $objdir/nodes.c || exit 1
diff --git a/src/kash/mktokens b/src/kash/mktokens
new file mode 100755
index 0000000..74d2a95
--- /dev/null
+++ b/src/kash/mktokens
@@ -0,0 +1,98 @@
+#!/bin/sh -
+# $NetBSD: mktokens,v 1.10 2003/08/22 11:22:23 agc Exp $
+#
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# 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.
+#
+# @(#)mktokens 8.1 (Berkeley) 5/31/93
+
+# The following is a list of tokens. The second column is nonzero if the
+# token marks the end of a list. The third column is the name to print in
+# error messages.
+
+if [ -z "$TMPDIR" ]; then
+ TMPDIR="/tmp"
+ export TMPDIR
+fi
+F="$TMPDIR/ka$$"
+echo $F
+cat > $F <<\!
+TEOF 1 end of file
+TNL 0 newline
+TSEMI 0 ";"
+TBACKGND 0 "&"
+TAND 0 "&&"
+TOR 0 "||"
+TPIPE 0 "|"
+TLP 0 "("
+TRP 1 ")"
+TENDCASE 1 ";;"
+TENDBQUOTE 1 "`"
+TREDIR 0 redirection
+TWORD 0 word
+TIF 0 "if"
+TTHEN 1 "then"
+TELSE 1 "else"
+TELIF 1 "elif"
+TFI 1 "fi"
+TWHILE 0 "while"
+TUNTIL 0 "until"
+TFOR 0 "for"
+TDO 1 "do"
+TDONE 1 "done"
+TBEGIN 0 "{"
+TEND 1 "}"
+TCASE 0 "case"
+TESAC 1 "esac"
+TNOT 0 "!"
+!
+nl=`wc -l $F`
+exec > token.h
+awk '{print "#define " $1 " " NR-1}' $F
+echo '
+/* Array indicating which tokens mark the end of a list */
+const char tokendlist[] = {'
+awk '{print "\t" $2 ","}' $F
+echo '};
+
+const char *const tokname[] = {'
+sed -e 's/"/\\"/g' \
+ -e 's/[^ ]*[ ][ ]*[^ ]*[ ][ ]*\(.*\)/ "\1",/' \
+ $F
+echo '};
+'
+sed 's/"//g' $F | awk '
+/TIF/{print "#define KWDOFFSET " NR-1; print "";
+ print "const char *const parsekwd[] = {"}
+/TIF/,/neverfound/{print " \"" $3 "\","}'
+echo ' 0
+};'
+
+rm $F
diff --git a/src/kash/myhistedit.h b/src/kash/myhistedit.h
new file mode 100644
index 0000000..f1803b5
--- /dev/null
+++ b/src/kash/myhistedit.h
@@ -0,0 +1,49 @@
+/* $NetBSD: myhistedit.h,v 1.10 2003/08/07 09:05:35 agc Exp $ */
+
+/*-
+ * Copyright (c) 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.
+ *
+ * @(#)myhistedit.h 8.2 (Berkeley) 5/4/95
+ */
+
+#ifndef SMALL
+#include <histedit.h>
+
+/*extern History *hist;
+extern EditLine *el;*/
+#endif
+/*extern int displayhist;*/
+
+void histedit(struct shinstance *);
+void sethistsize(struct shinstance *, const char *);
+void setterm(struct shinstance *, const char *);
+int histcmd(struct shinstance *, int, char **);
+int inputrc(struct shinstance *, int, char **);
+int not_fcnumber(struct shinstance *, char *);
+int str_to_event(struct shinstance *, const char *, int);
+
diff --git a/src/kash/mystring.c b/src/kash/mystring.c
new file mode 100644
index 0000000..0888b89
--- /dev/null
+++ b/src/kash/mystring.c
@@ -0,0 +1,132 @@
+/* $NetBSD: mystring.c,v 1.16 2003/08/07 09:05:35 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)mystring.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: mystring.c,v 1.16 2003/08/07 09:05:35 agc Exp $");
+#endif /* not lint */
+#endif
+
+/*
+ * String functions.
+ *
+ * equal(s1, s2) Return true if strings are equal.
+ * scopy(from, to) Copy a string.
+ * scopyn(from, to, n) Like scopy, but checks for overflow.
+ * number(s) Convert a string of digits to an integer.
+ * is_number(s) Return true if s is a string of digits.
+ */
+
+#include <stdlib.h>
+#include "shell.h"
+#include "syntax.h"
+#include "error.h"
+#include "mystring.h"
+
+
+char nullstr[1]; /* zero length string */
+
+/*
+ * equal - #defined in mystring.h
+ */
+
+/*
+ * scopy - #defined in mystring.h
+ */
+
+
+/*
+ * scopyn - copy a string from "from" to "to", truncating the string
+ * if necessary. "To" is always nul terminated, even if
+ * truncation is performed. "Size" is the size of "to".
+ */
+
+void
+scopyn(const char *from, char *to, ssize_t size)
+{
+
+ while (--size > 0) {
+ if ((*to++ = *from++) == '\0')
+ return;
+ }
+ *to = '\0';
+}
+
+
+/*
+ * prefix -- see if pfx is a prefix of string.
+ */
+
+int
+prefix(const char *pfx, const char *string)
+{
+ while (*pfx) {
+ if (*pfx++ != *string++)
+ return 0;
+ }
+ return 1;
+}
+
+
+/*
+ * Convert a string of digits to an integer, printing an error message on
+ * failure.
+ */
+
+int
+number(shinstance *psh, const char *s)
+{
+
+ if (! is_number(s))
+ error(psh, "Illegal number: %s", s);
+ return atoi(s);
+}
+
+
+
+/*
+ * Check for a valid number. This should be elsewhere.
+ */
+
+int
+is_number(const char *p)
+{
+ do {
+ if (! is_digit(*p))
+ return 0;
+ } while (*++p != '\0');
+ return 1;
+}
diff --git a/src/kash/mystring.h b/src/kash/mystring.h
new file mode 100644
index 0000000..6819df4
--- /dev/null
+++ b/src/kash/mystring.h
@@ -0,0 +1,55 @@
+/* $NetBSD: mystring.h,v 1.11 2003/08/07 09:05:35 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)mystring.h 8.2 (Berkeley) 5/4/95
+ */
+
+#ifndef ___mystring_h
+#define ___mystring_h
+
+#include <string.h>
+#include "shtypes.h" /* ssize_t */
+
+void scopyn(const char *, char *, ssize_t);
+int prefix(const char *, const char *);
+int number(struct shinstance *, const char *);
+int is_number(const char *);
+#if !defined(RT_OS_FREEBSD) && !defined(RT_OS_NETBSD) && !defined(RT_OS_OPENBSD) && !defined(RT_OS_OS2) \
+ && /* newer darwin: */ !defined(strlcpy)
+size_t strlcpy(char *dst, const char *src, size_t siz);
+#endif
+
+#define equal(s1, s2) (strcmp(s1, s2) == 0)
+#define scopy(s1, s2) ((void)strcpy(s2, s1))
+
+#endif
diff --git a/src/kash/nodes.c.pat b/src/kash/nodes.c.pat
new file mode 100644
index 0000000..69d4240
--- /dev/null
+++ b/src/kash/nodes.c.pat
@@ -0,0 +1,185 @@
+/* $NetBSD: nodes.c.pat,v 1.12 2004/06/15 22:57:27 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)nodes.c.pat 8.2 (Berkeley) 5/4/95
+ */
+
+#include <stdlib.h>
+/*
+ * Routine for dealing with parsed shell commands.
+ */
+
+#include "shell.h"
+#include "nodes.h"
+#include "memalloc.h"
+#include "machdep.h"
+#include "mystring.h"
+#include "shinstance.h"
+
+#ifndef KASH_SEPARATE_PARSER_ALLOCATOR
+
+size_t funcblocksize; /* size of structures in function */
+size_t funcstringsize; /* size of strings in node */
+pointer funcblock; /* block to allocate function from */
+char *funcstring; /* block to allocate strings from */
+
+%SIZES
+
+
+STATIC void calcsize(union node *);
+STATIC void sizenodelist(struct nodelist *);
+STATIC union node *copynode(union node *);
+STATIC struct nodelist *copynodelist(struct nodelist *);
+STATIC char *nodesavestr(char *);
+
+#endif /* !KASH_SEPARATE_PARSER_ALLOCATOR */
+
+
+/*
+ * Make a copy of a parse tree.
+ */
+
+union node *
+copyfunc(psh, n)
+ struct shinstance *psh;
+ union node *n;
+{
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ if (n != NULL) {
+ unsigned refs = pstackretain(n->pblock);
+ TRACE2((psh, "copyfunc: %p - %u refs\n", n->pblock, refs)); K_NOREF(refs);
+ }
+ return n;
+#else
+ if (n == NULL)
+ return NULL;
+ funcblocksize = 0;
+ funcstringsize = 0;
+ calcsize(n);
+ funcblock = ckmalloc(psh, funcblocksize + funcstringsize);
+ funcstring = (char *) funcblock + funcblocksize;
+ return copynode(n);
+#endif
+}
+
+#ifndef KASH_SEPARATE_PARSER_ALLOCATOR
+
+STATIC void
+calcsize(n)
+ union node *n;
+{
+ %CALCSIZE
+}
+
+
+
+STATIC void
+sizenodelist(lp)
+ struct nodelist *lp;
+{
+ while (lp) {
+ funcblocksize += SHELL_ALIGN(sizeof(struct nodelist));
+ calcsize(lp->n);
+ lp = lp->next;
+ }
+}
+
+
+
+STATIC union node *
+copynode(n)
+ union node *n;
+{
+ union node *new;
+
+ %COPY
+ return new;
+}
+
+
+STATIC struct nodelist *
+copynodelist(lp)
+ struct nodelist *lp;
+{
+ struct nodelist *start;
+ struct nodelist **lpp;
+
+ lpp = &start;
+ while (lp) {
+ *lpp = funcblock;
+ funcblock = (char *) funcblock +
+ SHELL_ALIGN(sizeof(struct nodelist));
+ (*lpp)->n = copynode(lp->n);
+ lp = lp->next;
+ lpp = &(*lpp)->next;
+ }
+ *lpp = NULL;
+ return start;
+}
+
+
+
+STATIC char *
+nodesavestr(s)
+ char *s;
+{
+ register char *p = s;
+ register char *q = funcstring;
+ char *rtn = funcstring;
+
+ while ((*q++ = *p++) != 0)
+ continue;
+ funcstring = q;
+ return rtn;
+}
+
+#endif /* !KASH_SEPARATE_PARSER_ALLOCATOR */
+
+
+/*
+ * Free a parse tree.
+ */
+
+void
+freefunc(psh, n)
+ shinstance *psh;
+ union node *n;
+{
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ if (n)
+ pstackrelease(psh, n->pblock, "freefunc");
+#else
+ if (n)
+ ckfree(psh, n);
+#endif
+}
diff --git a/src/kash/nodetypes b/src/kash/nodetypes
new file mode 100644
index 0000000..d1fb2c3
--- /dev/null
+++ b/src/kash/nodetypes
@@ -0,0 +1,141 @@
+# $NetBSD: nodetypes,v 1.12 2003/08/22 11:22:23 agc Exp $
+# Copyright (c) 1991, 1993
+# The Regents of the University of California. All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# 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.
+#
+# @(#)nodetypes 8.2 (Berkeley) 5/4/95
+
+# This file describes the nodes used in parse trees. Unindented lines
+# contain a node type followed by a structure tag. Subsequent indented
+# lines specify the fields of the structure. Several node types can share
+# the same structure, in which case the fields of the structure should be
+# specified only once.
+#
+# A field of a structure is described by the name of the field followed
+# by a type. The currently implemented types are:
+# nodeptr - a pointer to a node
+# nodelist - a pointer to a list of nodes
+# string - a pointer to a nul terminated string
+# int - an integer
+# other - any type that can be copied by assignment
+# The last two types should be followed by the text of a C declaration for
+# the field.
+
+NSEMI nbinary # two commands separated by a semicolon
+ type int
+ ch1 nodeptr # the first child
+ ch2 nodeptr # the second child
+
+NCMD ncmd # a simple command
+ type int
+ backgnd int # set to run command in background
+ args nodeptr # the arguments
+ redirect nodeptr # list of file redirections
+
+NPIPE npipe # a pipeline
+ type int
+ backgnd int # set to run pipeline in background
+ cmdlist nodelist # the commands in the pipeline
+
+NREDIR nredir # redirection (of a complex command)
+ type int
+ n nodeptr # the command
+ redirect nodeptr # list of file redirections
+
+NBACKGND nredir # run command in background
+NSUBSHELL nredir # run command in a subshell
+
+NAND nbinary # the && operator
+NOR nbinary # the || operator
+
+NIF nif # the if statement. Elif clauses are handled
+ type int # using multiple if nodes.
+ test nodeptr # if test
+ ifpart nodeptr # then ifpart
+ elsepart nodeptr # else elsepart
+
+NWHILE nbinary # the while statement. First child is the test
+NUNTIL nbinary # the until statement
+
+NFOR nfor # the for statement
+ type int
+ args nodeptr # for var in args
+ body nodeptr # do body; done
+ var string # the for variable
+
+NCASE ncase # a case statement
+ type int
+ expr nodeptr # the word to switch on
+ cases nodeptr # the list of cases (NCLIST nodes)
+
+NCLIST nclist # a case
+ type int
+ next nodeptr # the next case in list
+ pattern nodeptr # list of patterns for this case
+ body nodeptr # code to execute for this case
+
+
+NDEFUN narg # define a function. The "next" field contains
+ # the body of the function.
+
+NARG narg # represents a word
+ type int
+ next nodeptr # next word in list
+ text string # the text of the word
+ backquote nodelist # list of commands in back quotes
+
+NTO nfile # fd> fname
+NCLOBBER nfile # fd>| fname
+NFROM nfile # fd< fname
+NFROMTO nfile # fd<> fname
+NAPPEND nfile # fd>> fname
+ type int
+ fd int # file descriptor being redirected (must match ndup)
+ next nodeptr # next redirection in list (must match ndup)
+ fname nodeptr # file name, in a NARG node
+
+NTOFD ndup # fd<&dupfd
+NFROMFD ndup # fd>&dupfd
+ type int
+ fd int # file descriptor being redirected (must match nfile)
+ next nodeptr # next redirection in list (must match nfile)
+ dupfd int # file descriptor to duplicate
+ vname nodeptr # file name if fd>&$var
+
+
+NHERE nhere # fd<<\!
+NXHERE nhere # fd<<!
+ type int
+ fd int # file descriptor being redirected (must match nfile)
+ next nodeptr # next redirection in list (must match nfile)
+ doc nodeptr # input to command (NARG node)
+
+NNOT nnot # ! command (actually pipeline)
+ type int
+ com nodeptr
diff --git a/src/kash/options.c b/src/kash/options.c
new file mode 100644
index 0000000..ed99af5
--- /dev/null
+++ b/src/kash/options.c
@@ -0,0 +1,635 @@
+/* $NetBSD: options.c,v 1.38 2005/03/20 21:38:17 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)options.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: options.c,v 1.38 2005/03/20 21:38:17 dsl Exp $");
+#endif /* not lint */
+#endif
+
+#include <stdlib.h>
+
+#include "shell.h"
+#define DEFINE_OPTIONS
+#include "options.h"
+#undef DEFINE_OPTIONS
+#include "nodes.h" /* for other header files */
+#include "eval.h"
+#include "jobs.h"
+#include "input.h"
+#include "output.h"
+#include "trap.h"
+#include "var.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#ifndef SMALL
+# include "myhistedit.h"
+#endif
+#include "show.h"
+#include "shinstance.h"
+
+//char *arg0; /* value of $0 */
+//struct shparam shellparam; /* current positional parameters */
+//char **argptr; /* argument list for builtin commands */
+//char *optionarg; /* set by nextopt (like getopt) */
+//char *optptr; /* used by nextopt */
+
+//char *minusc; /* argument to -c option */
+
+
+STATIC void options(shinstance *, int);
+STATIC void minus_o(shinstance *, char *, int);
+STATIC void setoption(shinstance *, int, int);
+STATIC int getopts(shinstance *, char *, char *, char **, char ***, char **);
+
+#ifndef SH_FORKED_MODE
+void
+subshellinitoptions(shinstance *psh, shinstance *inherit)
+{
+ unsigned i;
+ int left;
+ const char *arg;
+ memcpy(&psh->optlist[0], &inherit->optlist[0], sizeof(psh->optlist));
+
+ /** @todo opimize: skip this when executing builtins. */
+ /* Whether the subshell uses argptr/shellparam/arg0 or replaces them depends
+ on whether the shell will execute a builtin command or not.
+
+ orgargv is already set by the shinstance.c core code, scan the original
+ again and update arg0, shellparm, argptr and optptr. */
+
+ /* arg0 is either something from orgargv, or in the EXSHELLPROC case a
+ separate allocation that we need to dupe here. The (new) arg0malloc
+ flag indicates which. */
+ i = 0;
+ psh->arg0malloc = inherit->arg0malloc;
+ if (inherit->arg0malloc) {
+ psh->arg0 = savestr(psh, inherit->arg0);
+ } else {
+ while ((arg = inherit->orgargv[i]) != NULL) {
+ if (inherit->arg0 == arg) {
+ psh->arg0 = psh->orgargv[i];
+ break;
+ }
+ i++;
+ }
+ kHlpAssert(psh->arg0 != NULL);
+ }
+
+ /* eval.h's commandname is same as arg0 when set unless we're doing a dot-include. */
+ if (inherit->commandname) {
+ if (inherit->commandname == inherit->arg0) {
+ psh->commandname = psh->arg0;
+ } else {
+ psh->commandname = savestr(psh, inherit->commandname);
+ psh->commandnamemalloc = 1;
+ }
+ }
+
+ /* shellparam is either pointing right after arg0 in orgargv, though it may
+ also be a separately allocated thing (see setparam), or pointing to the
+ arguments of a shell function we're executing (see eval.c). All in all,
+ it's simpler if we just copy the whole darn thing, ASSUMING no
+ modifications will be made that are needed to be visible elsewhere.
+ */
+ psh->shellparam.malloc = 1;
+ psh->shellparam.reset = inherit->shellparam.reset;
+ psh->shellparam.nparam = left = inherit->shellparam.nparam;
+ kHlpAssert(left >= 0);
+ psh->shellparam.p = (char **)ckmalloc(psh, (left + 1) * sizeof(psh->shellparam.p[0]));
+ psh->shellparam.p[left] = NULL;
+ while (left-- > 0) {
+ arg = inherit->shellparam.p[left];
+ psh->shellparam.p[left] = savestr(psh, arg);
+ }
+
+ /* The shellparam.optnext member is either NULL or points to a 'p' entry. */
+ if (inherit->shellparam.optnext) {
+ size_t idx = (size_t)(inherit->shellparam.optnext - inherit->shellparam.p);
+ kHlpAssert(idx <= inherit->shellparam.nparam);
+ if (idx <= inherit->shellparam.nparam)
+ psh->shellparam.optnext = &psh->shellparam.p[idx];
+ }
+
+ /* The shellparam.optptr member is either NULL or points within argument
+ prior to shellparam.optnext. We can leave it as NULL if at the EOS. */
+ if (inherit->shellparam.optptr && *inherit->shellparam.optptr != '\0') {
+ intptr_t idx;
+ if (!inherit->shellparam.optnext || inherit->shellparam.optnext == inherit->shellparam.p)
+ idx = (intptr_t)(inherit->shellparam.nparam - 1);
+ else {
+ idx = (intptr_t)(inherit->shellparam.optnext - inherit->shellparam.p - 1);
+ if (idx > inherit->shellparam.nparam)
+ idx = inherit->shellparam.nparam - 1;
+ }
+ while (idx >= 0) {
+ size_t arglen, off;
+ arg = inherit->shellparam.p[idx];
+ arglen = strlen(arg);
+ off = (size_t)(inherit->shellparam.optptr - arg);
+ if (off < arglen) {
+ psh->shellparam.optptr = psh->shellparam.p[idx] + off;
+ break;
+ }
+ off--;
+ }
+ kHlpAssert(psh->shellparam.optptr != NULL);
+ }
+
+ /* minusc: only used in main.c, so not applicable to subshells. */
+ /* optionarg: only used by callers of nextopt, so not relevant when forking subhells. */
+}
+#endif /* SH_FORKED_MODE */
+
+
+/*
+ * Process the shell command line arguments.
+ */
+
+void
+procargs(shinstance *psh, int argc, char **argv)
+{
+ int i;
+
+ psh->argptr = argv;
+ if (argc > 0)
+ psh->argptr++;
+ for (i = 0; i < NOPTS; i++)
+ psh->optlist[i].val = 2;
+ options(psh, 1);
+ if (*psh->argptr == NULL && psh->minusc == NULL)
+ sflag(psh) = 1;
+ if (iflag(psh) == 2 && sflag(psh) == 1 && shfile_isatty(&psh->fdtab, 0) && shfile_isatty(&psh->fdtab, 1))
+ iflag(psh) = 1;
+ if (mflag(psh) == 2)
+ mflag(psh) = iflag(psh);
+ for (i = 0; i < NOPTS; i++)
+ if (psh->optlist[i].val == 2)
+ psh->optlist[i].val = 0;
+#if DEBUG == 2
+ debug(psh) = 1;
+#endif
+ psh->commandnamemalloc = 0;
+ psh->arg0malloc = 0;
+ psh->arg0 = argv[0];
+ if (sflag(psh) == 0 && psh->minusc == NULL) {
+ psh->commandname = argv[0];
+ psh->arg0 = *psh->argptr++;
+ setinputfile(psh, psh->arg0, 0);
+ psh->commandname = psh->arg0;
+ }
+ /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */
+ if (psh->minusc != NULL) {
+ if (psh->argptr == NULL || *psh->argptr == NULL)
+ error(psh, "Bad -c option");
+ psh->minusc = *psh->argptr++;
+ if (*psh->argptr != 0)
+ psh->arg0 = *psh->argptr++;
+ }
+
+ psh->shellparam.p = psh->argptr;
+ psh->shellparam.reset = 1;
+ /* kHlpAssert(shellparam.malloc == 0 && shellparam.nparam == 0); */
+ while (*psh->argptr) {
+ psh->shellparam.nparam++;
+ psh->argptr++;
+ }
+ optschanged(psh);
+}
+
+
+void
+optschanged(shinstance *psh)
+{
+ setinteractive(psh, iflag(psh));
+#ifndef SMALL
+ histedit(psh);
+#endif
+ setjobctl(psh, mflag(psh));
+}
+
+/*
+ * Process shell options. The global variable argptr contains a pointer
+ * to the argument list; we advance it past the options.
+ */
+
+STATIC void
+options(shinstance *psh, int cmdline)
+{
+ static char empty[] = "";
+ char *p;
+ int val;
+ int c;
+
+ if (cmdline)
+ psh->minusc = NULL;
+ while ((p = *psh->argptr) != NULL) {
+ psh->argptr++;
+ if ((c = *p++) == '-') {
+ val = 1;
+ if (p[0] == '\0' || (p[0] == '-' && p[1] == '\0')) {
+ if (!cmdline) {
+ /* "-" means turn off -x and -v */
+ if (p[0] == '\0')
+ xflag(psh) = vflag(psh) = 0;
+ /* "--" means reset params */
+ else if (*psh->argptr == NULL)
+ setparam(psh, psh->argptr);
+ }
+ break; /* "-" or "--" terminates options */
+ }
+ } else if (c == '+') {
+ val = 0;
+ } else {
+ psh->argptr--;
+ break;
+ }
+ while ((c = *p++) != '\0') {
+ if (c == 'c' && cmdline) {
+ /* command is after shell args*/
+ psh->minusc = empty;
+ } else if (c == 'o') {
+ minus_o(psh, *psh->argptr, val);
+ if (*psh->argptr)
+ psh->argptr++;
+ } else {
+ setoption(psh, c, val);
+ }
+ }
+ }
+}
+
+static void
+set_opt_val(shinstance *psh, int i, int val)
+{
+ int j;
+ int flag;
+
+ if (val && (flag = psh->optlist[i].opt_set)) {
+ /* some options (eg vi/emacs) are mutually exclusive */
+ for (j = 0; j < NOPTS; j++)
+ if (psh->optlist[j].opt_set == flag)
+ psh->optlist[j].val = 0;
+ }
+ psh->optlist[i].val = val;
+#ifdef DEBUG
+ if (&psh->optlist[i].val == &debug(psh))
+ opentrace(psh);
+#endif
+}
+
+STATIC void
+minus_o(shinstance *psh, char *name, int val)
+{
+ int i;
+
+ if (name == NULL) {
+ out1str(psh, "Current option settings\n");
+ for (i = 0; i < NOPTS; i++)
+ out1fmt(psh, "%-16s%s\n", psh->optlist[i].name,
+ psh->optlist[i].val ? "on" : "off");
+ } else {
+ for (i = 0; i < NOPTS; i++)
+ if (equal(name, psh->optlist[i].name)) {
+ set_opt_val(psh, i, val);
+ return;
+ }
+ error(psh, "Illegal option -o %s", name);
+ }
+}
+
+
+STATIC void
+setoption(shinstance *psh, int flag, int val)
+{
+ int i;
+
+ for (i = 0; i < NOPTS; i++)
+ if (psh->optlist[i].letter == flag) {
+ set_opt_val(psh, i, val);
+ return;
+ }
+ error(psh, "Illegal option -%c", flag);
+ /* NOTREACHED */
+}
+
+
+
+#ifdef mkinit
+INCLUDE "options.h"
+
+INIT {
+ memcpy(&psh->optlist[0], &ro_optlist[0], sizeof(psh->optlist));
+}
+
+SHELLPROC {
+ int i;
+
+ for (i = 0; psh->optlist[i].name; i++)
+ psh->optlist[i].val = 0;
+# if DEBUG == 2
+ debug(psh) = 1;
+# endif
+ optschanged(psh);
+}
+#endif
+
+
+/*
+ * Set the shell parameters.
+ */
+
+void
+setparam(shinstance *psh, char **argv)
+{
+ char **newparam;
+ char **ap;
+ int nparam;
+
+ for (nparam = 0 ; argv[nparam] ; nparam++)
+ continue;
+ ap = newparam = ckmalloc(psh, (nparam + 1) * sizeof *ap);
+ while (*argv) {
+ *ap++ = savestr(psh, *argv++);
+ }
+ *ap = NULL;
+ freeparam(psh, &psh->shellparam);
+ psh->shellparam.malloc = 1;
+ psh->shellparam.nparam = nparam;
+ psh->shellparam.p = newparam;
+ psh->shellparam.optnext = NULL;
+}
+
+
+/*
+ * Free the list of positional parameters.
+ */
+
+void
+freeparam(shinstance *psh, volatile struct shparam *param)
+{
+ char **ap;
+
+ if (param->malloc) {
+ for (ap = param->p ; *ap ; ap++)
+ ckfree(psh, *ap);
+ ckfree(psh, param->p);
+ }
+}
+
+
+
+/*
+ * The shift builtin command.
+ */
+
+int
+shiftcmd(shinstance *psh, int argc, char **argv)
+{
+ int n;
+ char **ap1, **ap2;
+
+ n = 1;
+ if (argc > 1)
+ n = number(psh, argv[1]);
+ if (n > psh->shellparam.nparam)
+ error(psh, "can't shift that many");
+ INTOFF;
+ psh->shellparam.nparam -= n;
+ for (ap1 = psh->shellparam.p ; --n >= 0 ; ap1++) {
+ if (psh->shellparam.malloc)
+ ckfree(psh, *ap1);
+ }
+ ap2 = psh->shellparam.p;
+ while ((*ap2++ = *ap1++) != NULL);
+ psh->shellparam.optnext = NULL;
+ INTON;
+ return 0;
+}
+
+
+
+/*
+ * The set command builtin.
+ */
+
+int
+setcmd(shinstance *psh, int argc, char **argv)
+{
+ if (argc == 1)
+ return showvars(psh, 0, 0, 1);
+ INTOFF;
+ options(psh, 0);
+ optschanged(psh);
+ if (*psh->argptr != NULL) {
+ setparam(psh, psh->argptr);
+ }
+ INTON;
+ return 0;
+}
+
+
+void
+getoptsreset(shinstance *psh, const char *value)
+{
+ if (number(psh, value) == 1) {
+ psh->shellparam.optnext = NULL;
+ psh->shellparam.reset = 1;
+ }
+}
+
+/*
+ * The getopts builtin. Shellparam.optnext points to the next argument
+ * to be processed. Shellparam.optptr points to the next character to
+ * be processed in the current argument. If shellparam.optnext is NULL,
+ * then it's the first time getopts has been called.
+ */
+
+int
+getoptscmd(shinstance *psh, int argc, char **argv)
+{
+ char **optbase;
+
+ if (argc < 3)
+ error(psh, "usage: getopts optstring var [arg]");
+ else if (argc == 3)
+ optbase = psh->shellparam.p;
+ else
+ optbase = &argv[3];
+
+ if (psh->shellparam.reset == 1) {
+ psh->shellparam.optnext = optbase;
+ psh->shellparam.optptr = NULL;
+ psh->shellparam.reset = 0;
+ }
+
+ return getopts(psh, argv[1], argv[2], optbase, &psh->shellparam.optnext,
+ &psh->shellparam.optptr);
+}
+
+STATIC int
+getopts(shinstance *psh, char *optstr, char *optvar, char **optfirst, char ***optnext, char **optpptr)
+{
+ char *p, *q;
+ char c = '?';
+ int done = 0;
+ int ind = 0;
+ int err = 0;
+ char s[12];
+
+ if ((p = *optpptr) == NULL || *p == '\0') {
+ /* Current word is done, advance */
+ if (*optnext == NULL)
+ return 1;
+ p = **optnext;
+ if (p == NULL || *p != '-' || *++p == '\0') {
+atend:
+ ind = (int)(*optnext - optfirst + 1);
+ *optnext = NULL;
+ p = NULL;
+ done = 1;
+ goto out;
+ }
+ (*optnext)++;
+ if (p[0] == '-' && p[1] == '\0') /* check for "--" */
+ goto atend;
+ }
+
+ c = *p++;
+ for (q = optstr; *q != c; ) {
+ if (*q == '\0') {
+ if (optstr[0] == ':') {
+ s[0] = c;
+ s[1] = '\0';
+ err |= setvarsafe(psh, "OPTARG", s, 0);
+ } else {
+ outfmt(&psh->errout, "Illegal option -%c\n", c);
+ (void) unsetvar(psh, "OPTARG", 0);
+ }
+ c = '?';
+ goto bad;
+ }
+ if (*++q == ':')
+ q++;
+ }
+
+ if (*++q == ':') {
+ if (*p == '\0' && (p = **optnext) == NULL) {
+ if (optstr[0] == ':') {
+ s[0] = c;
+ s[1] = '\0';
+ err |= setvarsafe(psh, "OPTARG", s, 0);
+ c = ':';
+ } else {
+ outfmt(&psh->errout, "No arg for -%c option\n", c);
+ (void) unsetvar(psh, "OPTARG", 0);
+ c = '?';
+ }
+ goto bad;
+ }
+
+ if (p == **optnext)
+ (*optnext)++;
+ err |= setvarsafe(psh, "OPTARG", p, 0);
+ p = NULL;
+ } else
+ err |= setvarsafe(psh, "OPTARG", "", 0);
+ ind = (int)(*optnext - optfirst + 1);
+ goto out;
+
+bad:
+ ind = 1;
+ *optnext = NULL;
+ p = NULL;
+out:
+ *optpptr = p;
+ fmtstr(s, sizeof(s), "%d", ind);
+ err |= setvarsafe(psh, "OPTIND", s, VNOFUNC);
+ s[0] = c;
+ s[1] = '\0';
+ err |= setvarsafe(psh, optvar, s, 0);
+ if (err) {
+ *optnext = NULL;
+ *optpptr = NULL;
+ output_flushall(psh);
+ exraise(psh, EXERROR);
+ }
+ return done;
+}
+
+/*
+ * XXX - should get rid of. have all builtins use getopt(3). the
+ * library getopt must have the BSD extension static variable "optreset"
+ * otherwise it can't be used within the shell safely.
+ *
+ * Standard option processing (a la getopt) for builtin routines. The
+ * only argument that is passed to nextopt is the option string; the
+ * other arguments are unnecessary. It return the character, or '\0' on
+ * end of input.
+ */
+
+int
+nextopt(shinstance *psh, const char *optstring)
+{
+ char *p;
+ const char *q;
+ char c;
+
+ if ((p = psh->optptr) == NULL || *p == '\0') {
+ p = *psh->argptr;
+ if (p == NULL || *p != '-' || *++p == '\0')
+ return '\0';
+ psh->argptr++;
+ if (p[0] == '-' && p[1] == '\0') /* check for "--" */
+ return '\0';
+ }
+ c = *p++;
+ for (q = optstring ; *q != c ; ) {
+ if (*q == '\0')
+ error(psh, "Illegal option -%c", c);
+ if (*++q == ':')
+ q++;
+ }
+ if (*++q == ':') {
+ if (*p == '\0' && (p = *psh->argptr++) == NULL)
+ error(psh, "No arg for -%c option", c);
+ psh->optionarg = p;
+ p = NULL;
+ }
+ psh->optptr = p;
+ return c;
+}
diff --git a/src/kash/options.h b/src/kash/options.h
new file mode 100644
index 0000000..dd8caaa
--- /dev/null
+++ b/src/kash/options.h
@@ -0,0 +1,145 @@
+/* $NetBSD: options.h,v 1.18 2005/05/07 19:52:17 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)options.h 8.2 (Berkeley) 5/4/95
+ */
+
+#ifndef ___options_h
+#define ___options_h
+
+struct shparam {
+ int nparam; /* # of positional parameters (without $0) */
+ unsigned char malloc; /* if parameter list dynamically allocated */
+ unsigned char reset; /* if getopts has been reset */
+ char **p; /* parameter list */
+ char **optnext; /* next parameter to be processed by getopts */
+ char *optptr; /* used by getopts */
+};
+
+
+struct optent {
+ const char *name; /* for set -o <name> */
+ const char letter; /* set [+/-]<letter> and $- */
+ const char opt_set; /* mutually exclusive option set */
+ char val; /* value of <letter>flag */
+};
+
+/* Those marked [U] are required by posix, but have no effect! */
+
+#ifdef DEBUG
+# define NOPTS 20
+#else
+# define NOPTS 19
+#endif
+
+#ifdef DEFINE_OPTIONS
+# define DEF_OPTS(name, letter, opt_set) {name, letter, opt_set, 0},
+const struct optent ro_optlist[NOPTS + 1] = {
+#else
+# define DEF_OPTS(name, letter, opt_set)
+#endif
+#define DEF_OPT(name,letter) DEF_OPTS(name, letter, 0)
+
+DEF_OPT( "errexit", 'e' ) /* exit on error */
+#define eflag(psh) (psh)->optlist[0].val
+DEF_OPT( "noglob", 'f' ) /* no pathname expansion */
+#define fflag(psh) (psh)->optlist[1].val
+DEF_OPT( "ignoreeof", 'I' ) /* do not exit on EOF */
+#define Iflag(psh) (psh)->optlist[2].val
+DEF_OPT( "interactive",'i' ) /* interactive shell */
+#define iflag(psh) (psh)->optlist[3].val
+DEF_OPT( "monitor", 'm' ) /* job control */
+#define mflag(psh) (psh)->optlist[4].val
+DEF_OPT( "noexec", 'n' ) /* [U] do not exec commands */
+#define nflag(psh) (psh)->optlist[5].val
+DEF_OPT( "stdin", 's' ) /* read from stdin */
+#define sflag(psh) (psh)->optlist[6].val
+DEF_OPT( "xtrace", 'x' ) /* trace after expansion */
+#define xflag(psh) (psh)->optlist[7].val
+DEF_OPT( "verbose", 'v' ) /* trace read input */
+#define vflag(psh) (psh)->optlist[8].val
+DEF_OPTS( "vi", 'V', 'V' ) /* vi style editing */
+#define Vflag(psh) (psh)->optlist[9].val
+DEF_OPTS( "emacs", 'E', 'V' ) /* emacs style editing */
+#define Eflag(psh) (psh)->optlist[10].val
+DEF_OPT( "noclobber", 'C' ) /* do not overwrite files with > */
+#define Cflag(psh) (psh)->optlist[11].val
+DEF_OPT( "allexport", 'a' ) /* export all variables */
+#define aflag(psh) (psh)->optlist[12].val
+DEF_OPT( "notify", 'b' ) /* [U] report completion of background jobs */
+#define bflag(psh) (psh)->optlist[13].val
+DEF_OPT( "nounset", 'u' ) /* error expansion of unset variables */
+#define uflag(psh) (psh)->optlist[14].val
+DEF_OPT( "quietprofile", 'q' )
+#define qflag(psh) (psh)->optlist[15].val
+DEF_OPT( "nolog", 0 ) /* [U] no functon defs in command history */
+#define nolog(psh) (psh)->optlist[16].val
+DEF_OPT( "cdprint", 0 ) /* always print result of cd */
+#define cdprint(psh) (psh)->optlist[17].val
+DEF_OPT( "tabcomplete", 0 ) /* <tab> causes filename expansion */
+#define tabcomplete(psh) (psh)->optlist[18].val
+#ifdef DEBUG
+DEF_OPT( "debug", 0 ) /* enable debug prints */
+#define debug(psh) (psh)->optlist[19].val
+#endif
+
+#ifdef DEFINE_OPTIONS
+ { 0, 0, 0, 0 },
+};
+#else
+extern const struct optent ro_optlist[];
+#endif
+#define sizeof_optlist (NOPTS * sizeof(struct optent))
+
+
+/*extern char *minusc;*/ /* argument to -c option */
+/*extern char *arg0;*/ /* $0 */
+/*extern struct shparam shellparam;*/ /* $@ */
+/*extern char **argptr;*/ /* argument list for builtin commands */
+/*extern char *optionarg;*/ /* set by nextopt */
+/*extern char *optptr;*/ /* used by nextopt */
+
+#ifndef SH_FORKED_MODE
+void subshellinitoptions(struct shinstance *, struct shinstance *);
+#endif
+void procargs(struct shinstance *, int, char **);
+void optschanged(struct shinstance *);
+void setparam(struct shinstance *, char **);
+void freeparam(struct shinstance *, volatile struct shparam *);
+int shiftcmd(struct shinstance *, int, char **);
+int setcmd(struct shinstance *, int, char **);
+int getoptscmd(struct shinstance *, int, char **);
+int nextopt(struct shinstance *, const char *);
+void getoptsreset(struct shinstance *, const char *);
+
+#endif
diff --git a/src/kash/output.c b/src/kash/output.c
new file mode 100644
index 0000000..142feb5
--- /dev/null
+++ b/src/kash/output.c
@@ -0,0 +1,502 @@
+/* $NetBSD: output.c,v 1.28 2003/08/07 09:05:36 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)output.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: output.c,v 1.28 2003/08/07 09:05:36 agc Exp $");
+#endif /* not lint */
+#endif
+
+/*
+ * Shell output routines. We use our own output routines because:
+ * When a builtin command is interrupted we have to discard
+ * any pending output.
+ * When a builtin command appears in back quotes, we want to
+ * save the output of the command in a region obtained
+ * via malloc, rather than doing a fork and reading the
+ * output of the command via a pipe.
+ * Our output routines may be smaller than the stdio routines.
+ */
+
+#include <sys/types.h>
+
+#include <stdio.h> /* defines BUFSIZ */
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "shell.h"
+#include "syntax.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "shinstance.h"
+
+//#define OUTBUFSIZ BUFSIZ
+#define BLOCK_OUT -2 /* output to a fixed block of memory */
+//#define MEM_OUT -3 /* output to dynamically allocated memory */
+#define OUTPUT_ERR 01 /* error occurred on output */
+
+
+//struct output output = {NULL, 0, NULL, OUTBUFSIZ, 1, 0};
+//struct output errout = {NULL, 0, NULL, 100, 2, 0};
+//struct output memout = {NULL, 0, NULL, 0, MEM_OUT, 0};
+//struct output *out1 = &output;
+//struct output *out2 = &errout;
+
+
+
+#ifdef mkinit
+
+INCLUDE "output.h"
+INCLUDE "memalloc.h"
+
+RESET {
+ psh->out1 = &psh->output;
+ psh->out2 = &psh->errout;
+ if (psh->memout.buf != NULL) {
+ ckfree(psh, psh->memout.buf);
+ psh->memout.buf = NULL;
+ psh->memout.nextc = NULL;
+ }
+}
+
+#endif
+
+
+#ifdef notdef /* no longer used */
+/*
+ * Set up an output file to write to memory rather than a file.
+ */
+
+void
+open_mem(char *block, int length, struct output *file)
+{
+ file->nextc = block;
+ file->nleft = --length;
+ file->fd = BLOCK_OUT;
+ file->flags = 0;
+ file->psh = psh;
+}
+#endif
+
+
+void
+out1str(shinstance *psh, const char *p)
+{
+ outstr(p, psh->out1);
+}
+
+
+void
+out2str(shinstance *psh, const char *p)
+{
+ outstr(p, psh->out2);
+}
+
+
+void
+outstr(const char *p, struct output *file)
+{
+ while (*p)
+ outc(*p++, file);
+ if (file->psh && file == file->psh->out2)
+ flushout(file);
+}
+
+
+char out_junk[16];
+
+
+void
+emptyoutbuf(struct output *dest)
+{
+ int offset;
+ shinstance *psh = dest->psh;
+
+ if (dest->fd == BLOCK_OUT) {
+ dest->nextc = out_junk;
+ dest->nleft = sizeof out_junk;
+ dest->flags |= OUTPUT_ERR;
+ } else if (dest->buf == NULL) {
+ INTOFF;
+ dest->buf = ckmalloc(psh, dest->bufsize);
+ dest->nextc = dest->buf;
+ dest->nleft = dest->bufsize;
+ INTON;
+ } else if (dest->fd == MEM_OUT) {
+ offset = dest->bufsize;
+ INTOFF;
+ dest->bufsize <<= 1;
+ dest->buf = ckrealloc(psh, dest->buf, dest->bufsize);
+ dest->nleft = dest->bufsize - offset;
+ dest->nextc = dest->buf + offset;
+ INTON;
+ } else {
+ flushout(dest);
+ }
+ dest->nleft--;
+}
+
+
+void
+output_flushall(shinstance *psh)
+{
+ flushout(&psh->output);
+ flushout(&psh->errout);
+}
+
+
+void
+flushout(struct output *dest)
+{
+
+ if (dest->buf == NULL || dest->nextc == dest->buf || dest->fd < 0)
+ return;
+ if (xwrite(dest->psh, dest->fd, dest->buf, dest->nextc - dest->buf) < 0)
+ dest->flags |= OUTPUT_ERR;
+ dest->nextc = dest->buf;
+ dest->nleft = dest->bufsize;
+}
+
+
+void
+freestdout(shinstance *psh)
+{
+ INTOFF;
+ if (psh->output.buf) {
+ ckfree(psh, psh->output.buf);
+ psh->output.buf = NULL;
+ psh->output.nextc = NULL;
+ psh->output.nleft = 0;
+ }
+ INTON;
+}
+
+
+void
+outfmt(struct output *file, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ doformat(file, fmt, ap);
+ va_end(ap);
+}
+
+
+void
+out1fmt(shinstance *psh, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ doformat(psh->out1, fmt, ap);
+ va_end(ap);
+}
+
+void
+dprintf(shinstance *psh, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ doformat(psh->out2, fmt, ap);
+ va_end(ap);
+ flushout(psh->out2);
+}
+
+void
+fmtstr(char *outbuf, size_t length, const char *fmt, ...)
+{
+ va_list ap;
+ struct output strout;
+
+ va_start(ap, fmt);
+ strout.nextc = outbuf;
+ strout.nleft = (int)length;
+ strout.fd = BLOCK_OUT;
+ strout.flags = 0;
+ strout.psh = NULL;
+ doformat(&strout, fmt, ap);
+ outc('\0', &strout);
+ if (strout.flags & OUTPUT_ERR)
+ outbuf[length - 1] = '\0';
+ va_end(ap);
+}
+
+/*
+ * Formatted output. This routine handles a subset of the printf formats:
+ * - Formats supported: d, u, o, p, X, s, and c.
+ * - The x format is also accepted but is treated like X.
+ * - The l, ll and q modifiers are accepted.
+ * - The - and # flags are accepted; # only works with the o format.
+ * - Width and precision may be specified with any format except c.
+ * - An * may be given for the width or precision.
+ * - The obsolete practice of preceding the width with a zero to get
+ * zero padding is not supported; use the precision field.
+ * - A % may be printed by writing %% in the format string.
+ */
+
+#define TEMPSIZE 32
+
+#ifdef BSD4_4
+#define HAVE_VASPRINTF 1
+#endif
+
+void
+doformat(struct output *dest, const char *f, va_list ap)
+{
+#ifdef HAVE_VASPRINTF
+ char *s;
+
+ vasprintf(&s, f, ap);
+ outstr(s, dest);
+ free(s);
+#else /* !HAVE_VASPRINTF */
+ static const char digit_lower[] = "0123456789abcdef";
+ static const char digit_upper[] = "0123456789ABCDEF";
+ const char *digit;
+ char c;
+ char temp[TEMPSIZE];
+ int flushleft;
+ int sharp;
+ int width;
+ int prec;
+ int islong;
+ int isquad;
+ char *p;
+ int sign;
+ int64_t l;
+ uint64_t num;
+ unsigned base;
+ int len;
+ int size;
+ int pad;
+
+ while ((c = *f++) != '\0') {
+ if (c != '%') {
+ outc(c, dest);
+ continue;
+ }
+ flushleft = 0;
+ sharp = 0;
+ width = 0;
+ prec = -1;
+ islong = 0;
+ isquad = 0;
+ for (;;) {
+ if (*f == '-')
+ flushleft++;
+ else if (*f == '#')
+ sharp++;
+ else
+ break;
+ f++;
+ }
+ if (*f == '*') {
+ width = va_arg(ap, int);
+ f++;
+ } else {
+ while (is_digit(*f)) {
+ width = 10 * width + digit_val(*f++);
+ }
+ }
+ if (*f == '.') {
+ if (*++f == '*') {
+ prec = va_arg(ap, int);
+ f++;
+ } else {
+ prec = 0;
+ while (is_digit(*f)) {
+ prec = 10 * prec + digit_val(*f++);
+ }
+ }
+ }
+ if (*f == 'l') {
+ f++;
+ if (*f == 'l') {
+ isquad++;
+ f++;
+ } else
+ islong++;
+ } else if (*f == 'q') {
+ isquad++;
+ f++;
+ }
+#ifdef _MSC_VER /* for SHPID_PRI / KI64_PRI */
+ else if (*f == 'I' && f[1] == '6' && f[2] == '4') {
+ isquad++;
+ f += 3;
+ }
+#endif
+ digit = digit_upper;
+ switch (*f) {
+ case 'd':
+ if (isquad)
+ l = va_arg(ap, int64_t);
+ else if (islong)
+ l = va_arg(ap, long);
+ else
+ l = va_arg(ap, int);
+ sign = 0;
+ num = l;
+ if (l < 0) {
+ num = -l;
+ sign = 1;
+ }
+ base = 10;
+ goto number;
+ case 'u':
+ base = 10;
+ goto uns_number;
+ case 'o':
+ base = 8;
+ goto uns_number;
+ case 'p':
+ outc('0', dest);
+ outc('x', dest);
+ /*FALLTHROUGH*/
+ case 'x':
+ /* we don't implement 'x'; treat like 'X' */
+ digit = digit_lower;
+ /*FALLTHROUGH*/
+ case 'X':
+ base = 16;
+uns_number: /* an unsigned number */
+ sign = 0;
+ if (isquad)
+ num = va_arg(ap, uint64_t);
+ else if (islong)
+ num = va_arg(ap, unsigned long);
+ else
+ num = va_arg(ap, unsigned int);
+number: /* process a number */
+ p = temp + TEMPSIZE - 1;
+ *p = '\0';
+ while (num) {
+ *--p = digit[num % base];
+ num /= base;
+ }
+ len = (int)((temp + TEMPSIZE - 1) - p);
+ if (prec < 0)
+ prec = 1;
+ if (sharp && *f == 'o' && prec <= len)
+ prec = len + 1;
+ pad = 0;
+ if (width) {
+ size = len;
+ if (size < prec)
+ size = prec;
+ size += sign;
+ pad = width - size;
+ if (flushleft == 0) {
+ while (--pad >= 0)
+ outc(' ', dest);
+ }
+ }
+ if (sign)
+ outc('-', dest);
+ prec -= len;
+ while (--prec >= 0)
+ outc('0', dest);
+ while (*p)
+ outc(*p++, dest);
+ while (--pad >= 0)
+ outc(' ', dest);
+ break;
+ case 's':
+ p = va_arg(ap, char *);
+ pad = 0;
+ if (width) {
+ len = (int)strlen(p);
+ if (prec >= 0 && len > prec)
+ len = prec;
+ pad = width - len;
+ if (flushleft == 0) {
+ while (--pad >= 0)
+ outc(' ', dest);
+ }
+ }
+ prec++;
+ while (--prec != 0 && *p)
+ outc(*p++, dest);
+ while (--pad >= 0)
+ outc(' ', dest);
+ break;
+ case 'c':
+ c = va_arg(ap, int);
+ outc(c, dest);
+ break;
+ default:
+ outc(*f, dest);
+ break;
+ }
+ f++;
+ }
+#endif /* !HAVE_VASPRINTF */
+}
+
+
+
+/*
+ * Version of write which resumes after a signal is caught.
+ */
+
+int
+xwrite(shinstance *psh, int fd, char *buf, size_t nbytes)
+{
+ int ntry;
+ long i;
+ size_t n;
+
+ n = nbytes;
+ ntry = 0;
+ for (;;) {
+ i = shfile_write(&psh->fdtab, fd, buf, n);
+ if (i > 0) {
+ if ((n -= i) <= 0)
+ return (int)nbytes;
+ buf += i;
+ ntry = 0;
+ } else if (i == 0) {
+ if (++ntry > 10)
+ return (int)(nbytes - n);
+ } else if (errno != EINTR) {
+ return -1;
+ }
+ }
+}
diff --git a/src/kash/output.h b/src/kash/output.h
new file mode 100644
index 0000000..96b60d5
--- /dev/null
+++ b/src/kash/output.h
@@ -0,0 +1,92 @@
+/* $NetBSD: output.h,v 1.17 2003/08/07 09:05:36 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)output.h 8.2 (Berkeley) 5/4/95
+ */
+
+#ifndef OUTPUT_INCL
+
+#include <stdarg.h>
+
+/* The stupid, stupid, unix specification guys added dprintf to stdio.h!
+ Wonder what kind of weed they were smoking when doing that... */
+#include <stdio.h>
+#undef dprintf
+#define dprintf mydprintf
+
+
+struct output {
+ char *nextc;
+ int nleft;
+ char *buf;
+ int bufsize;
+ short fd;
+ short flags;
+ struct shinstance *psh;
+};
+
+/*extern struct output output;
+extern struct output errout;
+extern struct output memout;
+extern struct output *out1;
+extern struct output *out2;*/
+#if !defined(__GNUC__) && !defined(__attribute__)
+# define __attribute__(a)
+#endif
+
+void open_mem(char *, int, struct output *);
+void out1str(struct shinstance *, const char *);
+void out2str(struct shinstance *, const char *);
+void outstr(const char *, struct output *);
+void emptyoutbuf(struct output *);
+void output_flushall(struct shinstance *);
+void flushout(struct output *);
+void freestdout(struct shinstance *);
+void outfmt(struct output *, const char *, ...)
+ __attribute__((__format__(__printf__,2,3)));
+void out1fmt(struct shinstance *, const char *, ...)
+ __attribute__((__format__(__printf__,2,3)));
+void dprintf(struct shinstance *, const char *, ...)
+ __attribute__((__format__(__printf__,2,3)));
+void fmtstr(char *, size_t, const char *, ...)
+ __attribute__((__format__(__printf__,3,4)));
+void doformat(struct output *, const char *, va_list);
+int xwrite(struct shinstance *, int, char *, size_t);
+int xioctl(struct shinstance *, int, unsigned long, char *);
+
+#define outc(c, file) (--(file)->nleft < 0? (emptyoutbuf(file), *(file)->nextc++ = (c)) : (*(file)->nextc++ = (c)))
+#define out1c(psh, c) outc(c, (psh)->out1);
+#define out2c(psh, c) outc(c, (psh)->out2);
+
+#define OUTPUT_INCL
+#endif
diff --git a/src/kash/parser.c b/src/kash/parser.c
new file mode 100644
index 0000000..f6073ea
--- /dev/null
+++ b/src/kash/parser.c
@@ -0,0 +1,1966 @@
+/* $NetBSD: parser.c,v 1.59 2005/03/21 20:10:29 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95";
+#else
+__RCSID("$NetBSD: parser.c,v 1.59 2005/03/21 20:10:29 dsl Exp $");
+#endif /* not lint */
+#endif
+
+#define SH_MEMALLOC_NO_STACK
+#include <stdlib.h>
+
+#include "shell.h"
+#include "parser.h"
+#include "nodes.h"
+#include "expand.h" /* defines rmescapes() */
+#include "eval.h" /* defines commandname */
+#include "redir.h" /* defines copyfd() */
+#include "syntax.h"
+#include "options.h"
+#include "input.h"
+#include "output.h"
+#include "var.h"
+#include "error.h"
+#include "memalloc.h"
+#include "mystring.h"
+#include "alias.h"
+#include "show.h"
+#ifndef SMALL
+# include "myhistedit.h"
+#endif
+#include "cd.h"
+#include "shinstance.h"
+
+/*
+ * Shell command parser.
+ */
+
+#define EOFMARKLEN 79
+
+/* values returned by readtoken */
+#include "token.h"
+
+#define OPENBRACE '{'
+#define CLOSEBRACE '}'
+
+
+struct heredoc {
+ struct heredoc *next; /* next here document in list */
+ union node *here; /* redirection node */
+ char *eofmark; /* string indicating end of input */
+ int striptabs; /* if set, strip leading tabs */
+};
+
+
+
+//static int noalias = 0; /* when set, don't handle aliases */
+//struct heredoc *heredoclist; /* list of here documents to read */
+//int parsebackquote; /* nonzero if we are inside backquotes */
+//int doprompt; /* if set, prompt the user */
+//int needprompt; /* true if interactive and at start of line */
+//int lasttoken; /* last token read */
+//MKINIT int tokpushback; /* last token pushed back */
+//char *wordtext; /* text of last word returned by readtoken */
+//MKINIT int checkkwd; /* 1 == check for kwds, 2 == also eat newlines */
+//struct nodelist *backquotelist;
+//union node *redirnode;
+//struct heredoc *heredoc;
+//int quoteflag; /* set if (part of) last token was quoted */
+//int startlinno; /* line # where last token started */
+
+
+STATIC union node *list(shinstance *, int);
+STATIC union node *andor(shinstance *);
+STATIC union node *pipeline(shinstance *);
+STATIC union node *command(shinstance *);
+STATIC union node *simplecmd(shinstance *, union node **, union node *);
+STATIC union node *makename(shinstance *);
+STATIC void parsefname(shinstance *);
+STATIC void parseheredoc(shinstance *);
+STATIC int peektoken(shinstance *);
+STATIC int readtoken(shinstance *);
+STATIC int xxreadtoken(shinstance *);
+STATIC int readtoken1(shinstance *, int, char const *, char *, int);
+STATIC int noexpand(shinstance *, char *);
+SH_NORETURN_1 STATIC void synexpect(shinstance *, int) SH_NORETURN_2;
+SH_NORETURN_1 STATIC void synerror(shinstance *, const char *) SH_NORETURN_2;
+STATIC void setprompt(shinstance *, int);
+
+
+/*
+ * Read and parse a command. Returns NEOF on end of file. (NULL is a
+ * valid parse tree indicating a blank line.)
+ */
+
+union node *
+parsecmd(shinstance *psh, int interact)
+{
+ union node *ret;
+ int t;
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ pstack_block *pst = pstackallocpush(psh);
+#endif
+ TRACE2((psh, "parsecmd(%d)\n", interact));
+
+ psh->tokpushback = 0;
+ psh->doprompt = interact;
+ if (psh->doprompt)
+ setprompt(psh, 1);
+ else
+ setprompt(psh, 0);
+ psh->needprompt = 0;
+ t = readtoken(psh);
+ if (t == TEOF)
+ return NEOF;
+ if (t == TNL)
+ return NULL;
+ psh->tokpushback++;
+ ret = list(psh, 1);
+#if 0 /*def DEBUG*/
+ TRACE2((psh, "parsecmd(%d) returns:\n", interact));
+ showtree(psh, ret);
+#endif
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ pstackmarkdone(pst);
+#endif
+ return ret;
+}
+
+
+STATIC union node *
+list(shinstance *psh, int nlflag)
+{
+ union node *n1, *n2, *n3;
+ int tok;
+
+ psh->checkkwd = 2;
+ if (nlflag == 0 && tokendlist[peektoken(psh)])
+ return NULL;
+ n1 = NULL;
+ for (;;) {
+ n2 = andor(psh);
+ tok = readtoken(psh);
+ if (tok == TBACKGND) {
+ if (n2->type == NCMD || n2->type == NPIPE) {
+ n2->ncmd.backgnd = 1;
+ } else if (n2->type == NREDIR) {
+ n2->type = NBACKGND;
+ } else {
+ n3 = pstallocnode(psh, sizeof (struct nredir));
+ n3->type = NBACKGND;
+ n3->nredir.n = n2;
+ n3->nredir.redirect = NULL;
+ n2 = n3;
+ }
+ }
+ if (n1 == NULL) {
+ n1 = n2;
+ }
+ else {
+ n3 = pstallocnode(psh, sizeof (struct nbinary));
+ n3->type = NSEMI;
+ n3->nbinary.ch1 = n1;
+ n3->nbinary.ch2 = n2;
+ n1 = n3;
+ }
+ switch (tok) {
+ case TBACKGND:
+ case TSEMI:
+ tok = readtoken(psh);
+ /* fall through */
+ case TNL:
+ if (tok == TNL) {
+ parseheredoc(psh);
+ if (nlflag)
+ return n1;
+ } else {
+ psh->tokpushback++;
+ }
+ psh->checkkwd = 2;
+ if (tokendlist[peektoken(psh)])
+ return n1;
+ break;
+ case TEOF:
+ if (psh->heredoclist)
+ parseheredoc(psh);
+ else
+ pungetc(psh); /* push back EOF on input */
+ return n1;
+ default:
+ if (nlflag)
+ synexpect(psh, -1);
+ psh->tokpushback++;
+ return n1;
+ }
+ }
+}
+
+
+
+STATIC union node *
+andor(shinstance *psh)
+{
+ union node *n1, *n2, *n3;
+ int t;
+
+ n1 = pipeline(psh);
+ for (;;) {
+ if ((t = readtoken(psh)) == TAND) {
+ t = NAND;
+ } else if (t == TOR) {
+ t = NOR;
+ } else {
+ psh->tokpushback++;
+ return n1;
+ }
+ n2 = pipeline(psh);
+ n3 = pstallocnode(psh, sizeof (struct nbinary));
+ n3->type = t;
+ n3->nbinary.ch1 = n1;
+ n3->nbinary.ch2 = n2;
+ n1 = n3;
+ }
+}
+
+
+
+STATIC union node *
+pipeline(shinstance *psh)
+{
+ union node *n1, *n2, *pipenode;
+ struct nodelist *lp, *prev;
+ int negate;
+
+ negate = 0;
+ TRACE((psh, "pipeline: entered\n"));
+ while (readtoken(psh) == TNOT)
+ negate = !negate;
+ psh->tokpushback++;
+ n1 = command(psh);
+ if (readtoken(psh) == TPIPE) {
+ pipenode = pstallocnode(psh, sizeof (struct npipe));
+ pipenode->type = NPIPE;
+ pipenode->npipe.backgnd = 0;
+ lp = pstalloclist(psh);
+ pipenode->npipe.cmdlist = lp;
+ lp->n = n1;
+ do {
+ prev = lp;
+ lp = pstalloclist(psh);
+ lp->n = command(psh);
+ prev->next = lp;
+ } while (readtoken(psh) == TPIPE);
+ lp->next = NULL;
+ n1 = pipenode;
+ }
+ psh->tokpushback++;
+ if (negate) {
+ n2 = pstallocnode(psh, sizeof (struct nnot));
+ n2->type = NNOT;
+ n2->nnot.com = n1;
+ return n2;
+ } else
+ return n1;
+}
+
+
+
+STATIC union node *
+command(shinstance *psh)
+{
+ union node *n1, *n2;
+ union node *ap, **app;
+ union node *cp, **cpp;
+ union node *redir, **rpp;
+ int t, negate = 0;
+
+ psh->checkkwd = 2;
+ redir = NULL;
+ n1 = NULL;
+ rpp = &redir;
+
+ /* Check for redirection which may precede command */
+ while (readtoken(psh) == TREDIR) {
+ *rpp = n2 = psh->redirnode;
+ rpp = &n2->nfile.next;
+ parsefname(psh);
+ }
+ psh->tokpushback++;
+
+ while (readtoken(psh) == TNOT) {
+ TRACE((psh, "command: TNOT recognized\n"));
+ negate = !negate;
+ }
+ psh->tokpushback++;
+
+ switch (readtoken(psh)) {
+ case TIF:
+ n1 = pstallocnode(psh, sizeof (struct nif));
+ n1->type = NIF;
+ n1->nif.test = list(psh, 0);
+ if (readtoken(psh) != TTHEN)
+ synexpect(psh, TTHEN);
+ n1->nif.ifpart = list(psh, 0);
+ n2 = n1;
+ while (readtoken(psh) == TELIF) {
+ n2->nif.elsepart = pstallocnode(psh, sizeof (struct nif));
+ n2 = n2->nif.elsepart;
+ n2->type = NIF;
+ n2->nif.test = list(psh, 0);
+ if (readtoken(psh) != TTHEN)
+ synexpect(psh, TTHEN);
+ n2->nif.ifpart = list(psh, 0);
+ }
+ if (psh->lasttoken == TELSE)
+ n2->nif.elsepart = list(psh, 0);
+ else {
+ n2->nif.elsepart = NULL;
+ psh->tokpushback++;
+ }
+ if (readtoken(psh) != TFI)
+ synexpect(psh, TFI);
+ psh->checkkwd = 1;
+ break;
+ case TWHILE:
+ case TUNTIL: {
+ int got;
+ n1 = pstallocnode(psh, sizeof (struct nbinary));
+ n1->type = (psh->lasttoken == TWHILE)? NWHILE : NUNTIL;
+ n1->nbinary.ch1 = list(psh, 0);
+ if ((got=readtoken(psh)) != TDO) {
+TRACE((psh, "expecting DO got %s %s\n", tokname[got], got == TWORD ? psh->wordtext : ""));
+ synexpect(psh, TDO);
+ }
+ n1->nbinary.ch2 = list(psh, 0);
+ if (readtoken(psh) != TDONE)
+ synexpect(psh, TDONE);
+ psh->checkkwd = 1;
+ break;
+ }
+ case TFOR:
+ if (readtoken(psh) != TWORD || psh->quoteflag || ! goodname(psh->wordtext))
+ synerror(psh, "Bad for loop variable");
+ n1 = pstallocnode(psh, sizeof (struct nfor));
+ n1->type = NFOR;
+ n1->nfor.var = psh->wordtext;
+ if (readtoken(psh) == TWORD && ! psh->quoteflag && equal(psh->wordtext, "in")) {
+ app = &ap;
+ while (readtoken(psh) == TWORD) {
+ n2 = pstallocnode(psh, sizeof (struct narg));
+ n2->type = NARG;
+ n2->narg.text = psh->wordtext;
+ n2->narg.backquote = psh->backquotelist;
+ *app = n2;
+ app = &n2->narg.next;
+ }
+ *app = NULL;
+ n1->nfor.args = ap;
+ if (psh->lasttoken != TNL && psh->lasttoken != TSEMI)
+ synexpect(psh, -1);
+ } else {
+ static char argvars[5] = {CTLVAR, (char)(unsigned char)(VSNORMAL|VSQUOTE),
+ '@', '=', '\0'};
+ n2 = pstallocnode(psh, sizeof (struct narg));
+ n2->type = NARG;
+ n2->narg.text = argvars;
+ n2->narg.backquote = NULL;
+ n2->narg.next = NULL;
+ n1->nfor.args = n2;
+ /*
+ * Newline or semicolon here is optional (but note
+ * that the original Bourne shell only allowed NL).
+ */
+ if (psh->lasttoken != TNL && psh->lasttoken != TSEMI)
+ psh->tokpushback++;
+ }
+ psh->checkkwd = 2;
+ if ((t = readtoken(psh)) == TDO)
+ t = TDONE;
+ else if (t == TBEGIN)
+ t = TEND;
+ else
+ synexpect(psh, -1);
+ n1->nfor.body = list(psh, 0);
+ if (readtoken(psh) != t)
+ synexpect(psh, t);
+ psh->checkkwd = 1;
+ break;
+ case TCASE:
+ n1 = pstallocnode(psh, sizeof (struct ncase));
+ n1->type = NCASE;
+ if (readtoken(psh) != TWORD)
+ synexpect(psh, TWORD);
+ n1->ncase.expr = n2 = pstallocnode(psh, sizeof (struct narg));
+ n2->type = NARG;
+ n2->narg.text = psh->wordtext;
+ n2->narg.backquote = psh->backquotelist;
+ n2->narg.next = NULL;
+ while (readtoken(psh) == TNL);
+ if (psh->lasttoken != TWORD || ! equal(psh->wordtext, "in"))
+ synerror(psh, "expecting \"in\"");
+ cpp = &n1->ncase.cases;
+ psh->noalias = 1;
+ psh->checkkwd = 2, readtoken(psh);
+ do {
+ *cpp = cp = pstallocnode(psh, sizeof (struct nclist));
+ cp->type = NCLIST;
+ app = &cp->nclist.pattern;
+ for (;;) {
+ *app = ap = pstallocnode(psh, sizeof (struct narg));
+ ap->type = NARG;
+ ap->narg.text = psh->wordtext;
+ ap->narg.backquote = psh->backquotelist;
+ if (psh->checkkwd = 2, readtoken(psh) != TPIPE)
+ break;
+ app = &ap->narg.next;
+ readtoken(psh);
+ }
+ ap->narg.next = NULL;
+ psh->noalias = 0;
+ if (psh->lasttoken != TRP) {
+ synexpect(psh, TRP);
+ }
+ cp->nclist.body = list(psh, 0);
+
+ psh->checkkwd = 2;
+ if ((t = readtoken(psh)) != TESAC) {
+ if (t != TENDCASE) {
+ psh->noalias = 0;
+ synexpect(psh, TENDCASE);
+ } else {
+ psh->noalias = 1;
+ psh->checkkwd = 2;
+ readtoken(psh);
+ }
+ }
+ cpp = &cp->nclist.next;
+ } while(psh->lasttoken != TESAC);
+ psh->noalias = 0;
+ *cpp = NULL;
+ psh->checkkwd = 1;
+ break;
+ case TLP:
+ n1 = pstallocnode(psh, sizeof (struct nredir));
+ n1->type = NSUBSHELL;
+ n1->nredir.n = list(psh, 0);
+ n1->nredir.redirect = NULL;
+ if (readtoken(psh) != TRP)
+ synexpect(psh, TRP);
+ psh->checkkwd = 1;
+ break;
+ case TBEGIN:
+ n1 = list(psh, 0);
+ if (readtoken(psh) != TEND)
+ synexpect(psh, TEND);
+ psh->checkkwd = 1;
+ break;
+ /* Handle an empty command like other simple commands. */
+ case TSEMI:
+ /*
+ * An empty command before a ; doesn't make much sense, and
+ * should certainly be disallowed in the case of `if ;'.
+ */
+ if (!redir)
+ synexpect(psh, -1);
+ /* FALLTHROUGH */
+ case TAND:
+ case TOR:
+ case TNL:
+ case TEOF:
+ case TWORD:
+ case TRP:
+ psh->tokpushback++;
+ n1 = simplecmd(psh, rpp, redir);
+ goto checkneg;
+ default:
+ synexpect(psh, -1);
+ /* NOTREACHED */
+ }
+
+ /* Now check for redirection which may follow command */
+ while (readtoken(psh) == TREDIR) {
+ *rpp = n2 = psh->redirnode;
+ rpp = &n2->nfile.next;
+ parsefname(psh);
+ }
+ psh->tokpushback++;
+ *rpp = NULL;
+ if (redir) {
+ if (n1->type != NSUBSHELL) {
+ n2 = pstallocnode(psh, sizeof (struct nredir));
+ n2->type = NREDIR;
+ n2->nredir.n = n1;
+ n1 = n2;
+ }
+ n1->nredir.redirect = redir;
+ }
+
+checkneg:
+ if (negate) {
+ n2 = pstallocnode(psh, sizeof (struct nnot));
+ n2->type = NNOT;
+ n2->nnot.com = n1;
+ return n2;
+ }
+ else
+ return n1;
+}
+
+
+STATIC union node *
+simplecmd(shinstance *psh, union node **rpp, union node *redir)
+{
+ union node *args, **app;
+ union node **orig_rpp = rpp;
+ union node *n = NULL, *n2;
+ int negate = 0;
+
+ /* If we don't have any redirections already, then we must reset */
+ /* rpp to be the address of the local redir variable. */
+ if (redir == 0)
+ rpp = &redir;
+
+ args = NULL;
+ app = &args;
+ /*
+ * We save the incoming value, because we need this for shell
+ * functions. There can not be a redirect or an argument between
+ * the function name and the open parenthesis.
+ */
+ orig_rpp = rpp;
+
+ while (readtoken(psh) == TNOT) {
+ TRACE((psh, "command: TNOT recognized\n"));
+ negate = !negate;
+ }
+ psh->tokpushback++;
+
+ for (;;) {
+ if (readtoken(psh) == TWORD) {
+ n = pstallocnode(psh, sizeof (struct narg));
+ n->type = NARG;
+ n->narg.text = psh->wordtext;
+ n->narg.backquote = psh->backquotelist;
+ *app = n;
+ app = &n->narg.next;
+ } else if (psh->lasttoken == TREDIR) {
+ *rpp = n = psh->redirnode;
+ rpp = &n->nfile.next;
+ parsefname(psh); /* read name of redirection file */
+ } else if (psh->lasttoken == TLP && app == &args->narg.next
+ && rpp == orig_rpp) {
+ /* We have a function */
+ if (readtoken(psh) != TRP)
+ synexpect(psh, TRP);
+#ifdef notdef
+ if (! goodname(n->narg.text))
+ synerror(psh, "Bad function name");
+#endif
+ n->type = NDEFUN;
+ n->narg.next = command(psh);
+ goto checkneg;
+ } else {
+ psh->tokpushback++;
+ break;
+ }
+ }
+ *app = NULL;
+ *rpp = NULL;
+ n = pstallocnode(psh, sizeof (struct ncmd));
+ n->type = NCMD;
+ n->ncmd.backgnd = 0;
+ n->ncmd.args = args;
+ n->ncmd.redirect = redir;
+
+checkneg:
+ if (negate) {
+ n2 = pstallocnode(psh, sizeof (struct nnot));
+ n2->type = NNOT;
+ n2->nnot.com = n;
+ return n2;
+ }
+ else
+ return n;
+}
+
+STATIC union node *
+makename(shinstance *psh)
+{
+ union node *n;
+
+ n = pstallocnode(psh, sizeof (struct narg));
+ n->type = NARG;
+ n->narg.next = NULL;
+ n->narg.text = psh->wordtext;
+ n->narg.backquote = psh->backquotelist;
+ return n;
+}
+
+void fixredir(shinstance *psh, union node *n, const char *text, int err)
+{
+ TRACE((psh, "Fix redir %s %d\n", text, err));
+ if (!err)
+ n->ndup.vname = NULL;
+
+ if (is_digit(text[0]) && text[1] == '\0')
+ n->ndup.dupfd = digit_val(text[0]);
+ else if (text[0] == '-' && text[1] == '\0')
+ n->ndup.dupfd = -1;
+ else {
+
+ if (err)
+ synerror(psh, "Bad fd number");
+ else
+ n->ndup.vname = makename(psh);
+ }
+}
+
+
+STATIC void
+parsefname(shinstance *psh)
+{
+ union node *n = psh->redirnode;
+
+ if (readtoken(psh) != TWORD)
+ synexpect(psh, -1);
+ if (n->type == NHERE) {
+ struct heredoc *here = psh->heredoc;
+ struct heredoc *p;
+ size_t i;
+
+ if (psh->quoteflag == 0)
+ n->type = NXHERE;
+ TRACE((psh, "Here document %d\n", n->type));
+ if (here->striptabs) {
+ while (*psh->wordtext == '\t')
+ psh->wordtext++;
+ }
+ if (! noexpand(psh, psh->wordtext) || (i = strlen(psh->wordtext)) == 0 || i > EOFMARKLEN)
+ synerror(psh, "Illegal eof marker for << redirection");
+ rmescapes(psh, psh->wordtext);
+ here->eofmark = psh->wordtext;
+ here->next = NULL;
+ if (psh->heredoclist == NULL)
+ psh->heredoclist = here;
+ else {
+ for (p = psh->heredoclist ; p->next ; p = p->next);
+ p->next = here;
+ }
+ } else if (n->type == NTOFD || n->type == NFROMFD) {
+ fixredir(psh, n, psh->wordtext, 0);
+ } else {
+ n->nfile.fname = makename(psh);
+ }
+}
+
+
+/*
+ * Input any here documents.
+ */
+
+STATIC void
+parseheredoc(shinstance *psh)
+{
+ struct heredoc *here;
+ union node *n;
+
+ while (psh->heredoclist) {
+ here = psh->heredoclist;
+ psh->heredoclist = here->next;
+ if (psh->needprompt) {
+ setprompt(psh, 2);
+ psh->needprompt = 0;
+ }
+ readtoken1(psh, pgetc(psh), here->here->type == NHERE? SQSYNTAX : DQSYNTAX,
+ here->eofmark, here->striptabs);
+ n = pstallocnode(psh, sizeof (struct narg));
+ n->narg.type = NARG;
+ n->narg.next = NULL;
+ n->narg.text = psh->wordtext;
+ n->narg.backquote = psh->backquotelist;
+ here->here->nhere.doc = n;
+ }
+}
+
+STATIC int
+peektoken(shinstance *psh)
+{
+ int t;
+
+ t = readtoken(psh);
+ psh->tokpushback++;
+ return (t);
+}
+
+STATIC int
+readtoken(shinstance *psh)
+{
+ int t;
+ int savecheckkwd = psh->checkkwd;
+#ifdef DEBUG
+ int alreadyseen = psh->tokpushback;
+#endif
+ struct alias *ap;
+
+ top:
+ t = xxreadtoken(psh);
+
+ if (psh->checkkwd) {
+ /*
+ * eat newlines
+ */
+ if (psh->checkkwd == 2) {
+ psh->checkkwd = 0;
+ while (t == TNL) {
+ parseheredoc(psh);
+ t = xxreadtoken(psh);
+ }
+ } else
+ psh->checkkwd = 0;
+ /*
+ * check for keywords and aliases
+ */
+ if (t == TWORD && !psh->quoteflag)
+ {
+ const char *const *pp;
+
+ for (pp = parsekwd; *pp; pp++) {
+ if (**pp == *psh->wordtext && equal(*pp, psh->wordtext))
+ {
+ psh->lasttoken = t = (int)(pp -
+ parsekwd + KWDOFFSET);
+ TRACE((psh, "keyword %s recognized\n", tokname[t]));
+ goto out;
+ }
+ }
+ if(!psh->noalias &&
+ (ap = lookupalias(psh, psh->wordtext, 1)) != NULL) {
+ pushstring(psh, ap->val, strlen(ap->val), ap);
+ psh->checkkwd = savecheckkwd;
+ goto top;
+ }
+ }
+out:
+ psh->checkkwd = (t == TNOT) ? savecheckkwd : 0;
+ }
+#ifdef DEBUG
+ if (!alreadyseen)
+ TRACE((psh, "token %s %s\n", tokname[t], t == TWORD ? psh->wordtext : ""));
+ else
+ TRACE((psh, "reread token %s \"%s\"\n", tokname[t], t == TWORD ? psh->wordtext : ""));
+#endif
+ return (t);
+}
+
+
+/*
+ * Read the next input token.
+ * If the token is a word, we set psh->backquotelist to the list of cmds in
+ * backquotes. We set psh->quoteflag to true if any part of the word was
+ * quoted.
+ * If the token is TREDIR, then we set psh->redirnode to a structure containing
+ * the redirection.
+ * In all cases, the variable psh->startlinno is set to the number of the line
+ * on which the token starts.
+ *
+ * [Change comment: here documents and internal procedures]
+ * [Readtoken shouldn't have any arguments. Perhaps we should make the
+ * word parsing code into a separate routine. In this case, readtoken
+ * doesn't need to have any internal procedures, but parseword does.
+ * We could also make parseoperator in essence the main routine, and
+ * have parseword (readtoken1?) handle both words and redirection.]
+ */
+
+#define RETURN(token) return psh->lasttoken = token
+
+STATIC int
+xxreadtoken(shinstance *psh)
+{
+ int c;
+
+ if (psh->tokpushback) {
+ psh->tokpushback = 0;
+ return psh->lasttoken;
+ }
+ if (psh->needprompt) {
+ setprompt(psh, 2);
+ psh->needprompt = 0;
+ }
+ psh->startlinno = psh->plinno;
+ for (;;) { /* until token or start of word found */
+ c = pgetc_macro(psh);
+ if (c == ' ' || c == '\t')
+ continue; /* quick check for white space first */
+ switch (c) {
+ case ' ': case '\t':
+ continue;
+ case '#':
+ while ((c = pgetc(psh)) != '\n' && c != PEOF);
+ pungetc(psh);
+ continue;
+ case '\\':
+ if (pgetc(psh) == '\n') {
+ psh->startlinno = ++psh->plinno;
+ if (psh->doprompt)
+ setprompt(psh, 2);
+ else
+ setprompt(psh, 0);
+ continue;
+ }
+ pungetc(psh);
+ goto breakloop;
+ case '\n':
+ psh->plinno++;
+ psh->needprompt = psh->doprompt;
+ RETURN(TNL);
+ case PEOF:
+ RETURN(TEOF);
+ case '&':
+ if (pgetc(psh) == '&')
+ RETURN(TAND);
+ pungetc(psh);
+ RETURN(TBACKGND);
+ case '|':
+ if (pgetc(psh) == '|')
+ RETURN(TOR);
+ pungetc(psh);
+ RETURN(TPIPE);
+ case ';':
+ if (pgetc(psh) == ';')
+ RETURN(TENDCASE);
+ pungetc(psh);
+ RETURN(TSEMI);
+ case '(':
+ RETURN(TLP);
+ case ')':
+ RETURN(TRP);
+ default:
+ goto breakloop;
+ }
+ }
+breakloop:
+ return readtoken1(psh, c, BASESYNTAX, (char *)NULL, 0);
+#undef RETURN
+}
+
+
+
+/*
+ * If eofmark is NULL, read a word or a redirection symbol. If eofmark
+ * is not NULL, read a here document. In the latter case, eofmark is the
+ * word which marks the end of the document and striptabs is true if
+ * leading tabs should be stripped from the document. The argument firstc
+ * is the first character of the input token or document.
+ *
+ * Because C does not have internal subroutines, I have simulated them
+ * using goto's to implement the subroutine linkage. The following macros
+ * will run code that appears at the end of readtoken1.
+ */
+
+#define CHECKEND() {goto checkend; checkend_return:;}
+#define PARSEREDIR() {goto parseredir; parseredir_return:;}
+#define PARSESUB() {goto parsesub; parsesub_return:;}
+#define PARSEBACKQOLD() {oldstyle = 1; goto parsebackq; parsebackq_oldreturn:;}
+#define PARSEBACKQNEW() {oldstyle = 0; goto parsebackq; parsebackq_newreturn:;}
+#define PARSEARITH() {goto parsearith; parsearith_return:;}
+
+/*
+ * Keep track of nested doublequotes in dblquote and doublequotep.
+ * We use dblquote for the first 32 levels, and we expand to a malloc'ed
+ * region for levels above that. Usually we never need to malloc.
+ * This code assumes that an int is 32 bits. We don't use uint32_t,
+ * because the rest of the code does not.
+ */
+#define ISDBLQUOTE() ((varnest < 32) ? (dblquote & (1 << varnest)) : \
+ (dblquotep[(varnest / 32) - 1] & (1 << (varnest % 32))))
+
+#define SETDBLQUOTE() \
+ if (varnest < 32) \
+ dblquote |= (1 << varnest); \
+ else \
+ dblquotep[(varnest / 32) - 1] |= (1 << (varnest % 32))
+
+#define CLRDBLQUOTE() \
+ if (varnest < 32) \
+ dblquote &= ~(1 << varnest); \
+ else \
+ dblquotep[(varnest / 32) - 1] &= ~(1 << (varnest % 32))
+
+STATIC int
+readtoken1(shinstance *psh, int firstc, char const *syntax, char *eofmark, int striptabs)
+{
+ int c = firstc;
+ char *out;
+ char line[EOFMARKLEN + 1];
+ struct nodelist *bqlist;
+ int quotef = 0;
+ int *dblquotep = NULL;
+ size_t maxnest = 32;
+ int dblquote;
+ int varnest; /* levels of variables expansion */
+ int arinest; /* levels of arithmetic expansion */
+ int parenlevel; /* levels of parens in arithmetic */
+ int oldstyle;
+ char const *prevsyntax; /* syntax before arithmetic */
+
+ psh->startlinno = psh->plinno;
+ dblquote = 0;
+ varnest = 0;
+ if (syntax == DQSYNTAX) {
+ SETDBLQUOTE();
+ }
+ quotef = 0;
+ bqlist = NULL;
+ arinest = 0;
+ parenlevel = 0;
+
+#if __GNUC__
+ /* Try avoid longjmp clobbering */
+ (void) &maxnest;
+ (void) &dblquotep;
+ (void) &out;
+ (void) &quotef;
+ (void) &dblquote;
+ (void) &varnest;
+ (void) &arinest;
+ (void) &parenlevel;
+ (void) &oldstyle;
+ (void) &prevsyntax;
+ (void) &syntax;
+#endif
+
+ PSTARTSTACKSTR(psh, out);
+ loop: { /* for each line, until end of word */
+#if ATTY
+ if (c == '\034' && psh->doprompt
+ && attyset() && ! equal(termval(), "emacs")) {
+ attyline();
+ if (syntax == BASESYNTAX)
+ return readtoken(psh);
+ c = pgetc(psh);
+ goto loop;
+ }
+#endif
+ CHECKEND(); /* set c to PEOF if at end of here document */
+ for (;;) { /* until end of line or end of word */
+ PSTCHECKSTRSPACE(psh, 4+1, out); /* permit 4 calls to PSTUPUTC, pluss terminator */
+ switch(syntax[c]) {
+ case CNL: /* '\n' */
+ if (syntax == BASESYNTAX)
+ goto endword; /* exit outer loop */
+ PSTUPUTC(psh, c, out);
+ psh->plinno++;
+ if (psh->doprompt)
+ setprompt(psh, 2);
+ else
+ setprompt(psh, 0);
+ c = pgetc(psh);
+ goto loop; /* continue outer loop */
+ case CWORD:
+ PSTUPUTC(psh, c, out);
+ break;
+ case CCTL:
+ if (eofmark == NULL || ISDBLQUOTE())
+ PSTUPUTC(psh, CTLESC, out);
+ PSTUPUTC(psh, c, out);
+ break;
+ case CBACK: /* backslash */
+ c = pgetc(psh);
+ if (c == PEOF) {
+ PSTUPUTC(psh, '\\', out);
+ pungetc(psh);
+ break;
+ }
+ if (c == '\n') {
+ if (psh->doprompt)
+ setprompt(psh, 2);
+ else
+ setprompt(psh, 0);
+ break;
+ }
+ quotef = 1;
+ if (ISDBLQUOTE() && c != '\\' &&
+ c != '`' && c != '$' &&
+ (c != '"' || eofmark != NULL))
+ PSTUPUTC(psh, '\\', out);
+ if (SQSYNTAX[c] == CCTL)
+ PSTUPUTC(psh, CTLESC, out);
+ else if (eofmark == NULL) {
+ PSTUPUTC(psh, CTLQUOTEMARK, out);
+ PSTUPUTC(psh, c, out);
+ if (varnest != 0)
+ PSTUPUTC(psh, CTLQUOTEEND, out);
+ break;
+ }
+ PSTUPUTC(psh, c, out);
+ break;
+ case CSQUOTE:
+ if (syntax != SQSYNTAX) {
+ if (eofmark == NULL)
+ PSTUPUTC(psh, CTLQUOTEMARK, out);
+ quotef = 1;
+ syntax = SQSYNTAX;
+ break;
+ }
+ if (eofmark != NULL && arinest == 0 &&
+ varnest == 0) {
+ /* Ignore inside quoted here document */
+ PSTUPUTC(psh, c, out);
+ break;
+ }
+ /* End of single quotes... */
+ if (arinest)
+ syntax = ARISYNTAX;
+ else {
+ syntax = BASESYNTAX;
+ if (varnest != 0)
+ PSTUPUTC(psh, CTLQUOTEEND, out);
+ }
+ break;
+ case CDQUOTE:
+ if (eofmark != NULL && arinest == 0 &&
+ varnest == 0) {
+ /* Ignore inside here document */
+ PSTUPUTC(psh, c, out);
+ break;
+ }
+ quotef = 1;
+ if (arinest) {
+ if (ISDBLQUOTE()) {
+ syntax = ARISYNTAX;
+ CLRDBLQUOTE();
+ } else {
+ syntax = DQSYNTAX;
+ SETDBLQUOTE();
+ PSTUPUTC(psh, CTLQUOTEMARK, out);
+ }
+ break;
+ }
+ if (eofmark != NULL)
+ break;
+ if (ISDBLQUOTE()) {
+ if (varnest != 0)
+ PSTUPUTC(psh, CTLQUOTEEND, out);
+ syntax = BASESYNTAX;
+ CLRDBLQUOTE();
+ } else {
+ syntax = DQSYNTAX;
+ SETDBLQUOTE();
+ PSTUPUTC(psh, CTLQUOTEMARK, out);
+ }
+ break;
+ case CVAR: /* '$' */
+ PARSESUB(); /* parse substitution */
+ break;
+ case CENDVAR: /* CLOSEBRACE */
+ if (varnest > 0 && !ISDBLQUOTE()) {
+ varnest--;
+ PSTUPUTC(psh, CTLENDVAR, out);
+ } else {
+ PSTUPUTC(psh, c, out);
+ }
+ break;
+ case CLP: /* '(' in arithmetic */
+ parenlevel++;
+ PSTUPUTC(psh, c, out);
+ break;
+ case CRP: /* ')' in arithmetic */
+ if (parenlevel > 0) {
+ PSTUPUTC(psh, c, out);
+ --parenlevel;
+ } else {
+ if (pgetc(psh) == ')') {
+ if (--arinest == 0) {
+ PSTUPUTC(psh, CTLENDARI, out);
+ syntax = prevsyntax;
+ if (syntax == DQSYNTAX)
+ SETDBLQUOTE();
+ else
+ CLRDBLQUOTE();
+ } else
+ PSTUPUTC(psh, ')', out);
+ } else {
+ /*
+ * unbalanced parens
+ * (don't 2nd guess - no error)
+ */
+ pungetc(psh);
+ PSTUPUTC(psh, ')', out);
+ }
+ }
+ break;
+ case CBQUOTE: /* '`' */
+ PARSEBACKQOLD();
+ break;
+ case CSHEOF:
+ goto endword; /* exit outer loop */
+ default:
+ if (varnest == 0)
+ goto endword; /* exit outer loop */
+ PSTUPUTC(psh, c, out);
+ }
+ c = pgetc_macro(psh);
+ }
+ }
+endword:
+ if (syntax == ARISYNTAX)
+ synerror(psh, "Missing '))'");
+ if (syntax != BASESYNTAX && ! psh->parsebackquote && eofmark == NULL)
+ synerror(psh, "Unterminated quoted string");
+ if (varnest != 0) {
+ psh->startlinno = psh->plinno;
+ /* { */
+ synerror(psh, "Missing '}'");
+ }
+ PSTUPUTC(psh, '\0', out);
+ if (eofmark == NULL) {
+ size_t len = (size_t)(out - PSTBLOCK(psh));
+ char *start = PSTBLOCK(psh);
+ if ((c == '>' || c == '<')
+ && quotef == 0
+ && len <= 2
+ && (*start == '\0' || is_digit(*start))) {
+ out = start;
+ PARSEREDIR();
+ return psh->lasttoken = TREDIR;
+ } else {
+ pungetc(psh);
+ }
+ }
+ psh->quoteflag = quotef;
+ psh->backquotelist = bqlist;
+ psh->wordtext = pstgrabstr(psh, out);
+ if (dblquotep != NULL)
+ ckfree(psh, dblquotep);
+ return psh->lasttoken = TWORD;
+/* end of readtoken routine */
+
+
+
+/*
+ * Check to see whether we are at the end of the here document. When this
+ * is called, c is set to the first character of the next input line. If
+ * we are at the end of the here document, this routine sets the c to PEOF.
+ */
+
+checkend: {
+ if (eofmark) {
+ if (striptabs) {
+ while (c == '\t')
+ c = pgetc(psh);
+ }
+ if (c == *eofmark) {
+ if (pfgets(psh, line, sizeof line) != NULL) {
+ char *p, *q;
+
+ p = line;
+ for (q = eofmark + 1 ; *q && *p == *q ; p++, q++);
+ if (*p == '\n' && *q == '\0') {
+ c = PEOF;
+ psh->plinno++;
+ psh->needprompt = psh->doprompt;
+ } else {
+ pushstring(psh, line, strlen(line), NULL);
+ }
+ }
+ }
+ }
+ goto checkend_return;
+}
+
+
+/*
+ * Parse a redirection operator. The variable "out" points to a string
+ * specifying the fd to be redirected. The variable "c" contains the
+ * first character of the redirection operator.
+ */
+
+parseredir: {
+ union node *np;
+ char fd = *out;
+ char dummy[ sizeof(struct ndup) >= sizeof(struct nfile)
+ && sizeof(struct ndup) >= sizeof(struct nhere) ? 1 : 0];
+ (void)dummy;
+
+ np = pstallocnode(psh, sizeof (struct ndup));
+ if (c == '>') {
+ np->nfile.fd = 1;
+ c = pgetc(psh);
+ if (c == '>')
+ np->type = NAPPEND;
+ else if (c == '|')
+ np->type = NCLOBBER;
+ else if (c == '&')
+ np->type = NTOFD;
+ else {
+ np->type = NTO;
+ pungetc(psh);
+ }
+ } else { /* c == '<' */
+ np->nfile.fd = 0;
+ switch (c = pgetc(psh)) {
+ case '<':
+ np->type = NHERE;
+ psh->heredoc = (struct heredoc *)pstalloc(psh, sizeof (struct heredoc));
+ psh->heredoc->here = np;
+ if ((c = pgetc(psh)) == '-') {
+ psh->heredoc->striptabs = 1;
+ } else {
+ psh->heredoc->striptabs = 0;
+ pungetc(psh);
+ }
+ break;
+
+ case '&':
+ np->type = NFROMFD;
+ break;
+
+ case '>':
+ np->type = NFROMTO;
+ break;
+
+ default:
+ np->type = NFROM;
+ pungetc(psh);
+ break;
+ }
+ }
+ if (fd != '\0')
+ np->nfile.fd = digit_val(fd);
+ psh->redirnode = np;
+ goto parseredir_return;
+}
+
+
+/*
+ * Parse a substitution. At this point, we have read the dollar sign
+ * and nothing else.
+ */
+
+parsesub: {
+ int subtype;
+ int typeloc;
+ int flags;
+ char *p;
+ static const char types[] = "}-+?=";
+
+ c = pgetc(psh);
+ if (c != '(' && c != OPENBRACE && !is_name(c) && !is_special(c)) {
+ PSTUPUTC(psh, '$', out);
+ pungetc(psh);
+ } else if (c == '(') { /* $(command) or $((arith)) */
+ if (pgetc(psh) == '(') {
+ PARSEARITH();
+ } else {
+ pungetc(psh);
+ PARSEBACKQNEW();
+ }
+ } else {
+ PSTUPUTC(psh, CTLVAR, out);
+ typeloc = (int)(out - PSTBLOCK(psh));
+ PSTUPUTC(psh, VSNORMAL, out);
+ subtype = VSNORMAL;
+ if (c == OPENBRACE) {
+ c = pgetc(psh);
+ if (c == '#') {
+ if ((c = pgetc(psh)) == CLOSEBRACE)
+ c = '#';
+ else
+ subtype = VSLENGTH;
+ }
+ else
+ subtype = 0;
+ }
+ if (is_name(c)) {
+ do {
+ PSTPUTC(psh, c, out);
+ c = pgetc(psh);
+ } while (is_in_name(c));
+ } else if (is_digit(c)) {
+ do {
+ PSTUPUTC(psh, c, out);
+ c = pgetc(psh);
+ } while (is_digit(c));
+ }
+ else if (is_special(c)) {
+ PSTUPUTC(psh, c, out);
+ c = pgetc(psh);
+ }
+ else {
+badsub:
+ synerror(psh, "Bad substitution");
+ }
+
+ PSTPUTC(psh, '=', out);
+ flags = 0;
+ if (subtype == 0) {
+ switch (c) {
+ case ':':
+ flags = VSNUL;
+ c = pgetc(psh);
+ /*FALLTHROUGH*/
+ default:
+ p = strchr(types, c);
+ if (p == NULL)
+ goto badsub;
+ subtype = (int)(p - types + VSNORMAL);
+ break;
+ case '%':
+ case '#':
+ {
+ int cc = c;
+ subtype = c == '#' ? VSTRIMLEFT :
+ VSTRIMRIGHT;
+ c = pgetc(psh);
+ if (c == cc)
+ subtype++;
+ else
+ pungetc(psh);
+ break;
+ }
+ }
+ } else {
+ pungetc(psh);
+ }
+ if (ISDBLQUOTE() || arinest)
+ flags |= VSQUOTE;
+ *(PSTBLOCK(psh) + typeloc) = subtype | flags;
+ if (subtype != VSNORMAL) {
+ varnest++;
+ if (varnest >= (int)maxnest) {
+ dblquotep = ckrealloc(psh, dblquotep, maxnest / 8);
+ dblquotep[(maxnest / 32) - 1] = 0;
+ maxnest += 32;
+ }
+ }
+ }
+ goto parsesub_return;
+}
+
+
+/*
+ * Called to parse command substitutions. Newstyle is set if the command
+ * is enclosed inside $(...); nlpp is a pointer to the head of the linked
+ * list of commands (passed by reference), and savelen is the number of
+ * characters on the top of the stack which must be preserved.
+ */
+
+parsebackq: {
+ struct nodelist **nlpp;
+ int savepbq;
+ union node *n;
+ char *volatile str;
+ struct jmploc jmploc;
+ struct jmploc *volatile savehandler;
+ int savelen;
+ int saveprompt;
+#ifdef __GNUC__
+ (void) &saveprompt;
+#endif
+
+ savepbq = psh->parsebackquote;
+ if (setjmp(jmploc.loc)) {
+ if (str)
+ ckfree(psh, str);
+ psh->parsebackquote = 0;
+ psh->handler = savehandler;
+ longjmp(psh->handler->loc, 1);
+ }
+ INTOFF;
+ str = NULL;
+ savelen = (int)(out - PSTBLOCK(psh));
+ if (savelen > 0) {
+ str = ckmalloc(psh, savelen);
+ memcpy(str, PSTBLOCK(psh), savelen);
+ }
+ savehandler = psh->handler;
+ psh->handler = &jmploc;
+ INTON;
+ if (oldstyle) {
+ /* We must read until the closing backquote, giving special
+ treatment to some slashes, and then push the string and
+ reread it as input, interpreting it normally. */
+ char *pout;
+ int pc;
+ int psavelen;
+ char *pstr;
+
+
+ PSTARTSTACKSTR(psh, pout);
+ for (;;) {
+ if (psh->needprompt) {
+ setprompt(psh, 2);
+ psh->needprompt = 0;
+ }
+ switch (pc = pgetc(psh)) {
+ case '`':
+ goto done;
+
+ case '\\':
+ if ((pc = pgetc(psh)) == '\n') {
+ psh->plinno++;
+ if (psh->doprompt)
+ setprompt(psh, 2);
+ else
+ setprompt(psh, 0);
+ /*
+ * If eating a newline, avoid putting
+ * the newline into the new character
+ * stream (via the PSTPUTC after the
+ * switch).
+ */
+ continue;
+ }
+ if (pc != '\\' && pc != '`' && pc != '$' && (!ISDBLQUOTE() || pc != '"'))
+ PSTPUTC(psh, '\\', pout);
+ break;
+
+ case '\n':
+ psh->plinno++;
+ psh->needprompt = psh->doprompt;
+ break;
+
+ case PEOF:
+ psh->startlinno = psh->plinno;
+ synerror(psh, "EOF in backquote substitution");
+ break;
+
+ default:
+ break;
+ }
+ PSTPUTC(psh, pc, pout);
+ }
+done:
+ PSTPUTC(psh, '\0', pout);
+ psavelen = (int)(pout - PSTBLOCK(psh));
+ if (psavelen > 0) { /** @todo nonsensical test? */
+ pstr = pstgrabstr(psh, pout);
+ setinputstring(psh, pstr, 1 /*push*/);
+ }
+ }
+ nlpp = &bqlist;
+ while (*nlpp)
+ nlpp = &(*nlpp)->next;
+ *nlpp = pstalloclist(psh);
+ (*nlpp)->next = NULL;
+ psh->parsebackquote = oldstyle;
+
+ if (oldstyle) {
+ saveprompt = psh->doprompt;
+ psh->doprompt = 0;
+ }
+
+ n = list(psh, 0);
+
+ if (oldstyle)
+ psh->doprompt = saveprompt;
+ else {
+ if (readtoken(psh) != TRP)
+ synexpect(psh, TRP);
+ }
+
+ (*nlpp)->n = n;
+ if (oldstyle) {
+ /*
+ * Start reading from old file again, ignoring any pushed back
+ * tokens left from the backquote parsing
+ */
+ popfile(psh);
+ psh->tokpushback = 0;
+ }
+ PSTARTSTACKSTR(psh, out);
+ if (str) {
+ PSTPUTSTRN(psh, str, savelen, out);
+ INTOFF;
+ ckfree(psh, str);
+ str = NULL;
+ INTON;
+ }
+ psh->parsebackquote = savepbq;
+ psh->handler = savehandler;
+ if (arinest || ISDBLQUOTE())
+ PSTUPUTC(psh, CTLBACKQ | CTLQUOTE, out);
+ else
+ PSTUPUTC(psh, CTLBACKQ, out);
+ if (oldstyle)
+ goto parsebackq_oldreturn;
+ else
+ goto parsebackq_newreturn;
+}
+
+/*
+ * Parse an arithmetic expansion (indicate start of one and set state)
+ */
+parsearith: {
+
+ if (++arinest == 1) {
+ prevsyntax = syntax;
+ syntax = ARISYNTAX;
+ PSTUPUTC(psh, CTLARI, out);
+ if (ISDBLQUOTE())
+ PSTUPUTC(psh, '"',out);
+ else
+ PSTUPUTC(psh, ' ',out);
+ } else {
+ /*
+ * we collapse embedded arithmetic expansion to
+ * parenthesis, which should be equivalent
+ */
+ PSTUPUTC(psh, '(', out);
+ }
+ goto parsearith_return;
+}
+
+} /* end of readtoken */
+
+
+
+#ifdef mkinit
+RESET {
+ psh->tokpushback = 0;
+ psh->checkkwd = 0;
+}
+#endif
+
+/*
+ * Returns true if the text contains nothing to expand (no dollar signs
+ * or backquotes).
+ */
+
+STATIC int
+noexpand(shinstance *psh, char *text)
+{
+ char *p;
+ char c;
+
+ p = text;
+ while ((c = *p++) != '\0') {
+ if (c == CTLQUOTEMARK)
+ continue;
+ if (c == CTLESC)
+ p++;
+ else if (BASESYNTAX[(int)c] == CCTL)
+ return 0;
+ }
+ return 1;
+}
+
+
+/*
+ * Return true if the argument is a legal variable name (a letter or
+ * underscore followed by zero or more letters, underscores, and digits).
+ */
+
+int
+goodname(const char *name)
+{
+ const char *p;
+
+ p = name;
+ if (! is_name(*p))
+ return 0;
+ while (*++p) {
+ if (! is_in_name(*p))
+ return 0;
+ }
+ return 1;
+}
+
+
+/*
+ * Called when an unexpected token is read during the parse. The argument
+ * is the token that is expected, or -1 if more than one type of token can
+ * occur at this point.
+ */
+
+SH_NORETURN_1 STATIC void
+synexpect(shinstance *psh, int token)
+{
+ char msg[64];
+
+ if (token >= 0) {
+ fmtstr(msg, 64, "%s unexpected (expecting %s)",
+ tokname[psh->lasttoken], tokname[token]);
+ } else {
+ fmtstr(msg, 64, "%s unexpected", tokname[psh->lasttoken]);
+ }
+ synerror(psh, msg);
+ /* NOTREACHED */
+}
+
+
+SH_NORETURN_1 STATIC void
+synerror(shinstance *psh, const char *msg)
+{
+ if (psh->commandname) {
+ TRACE((psh, "synerror: %s: %d: Syntax error: %s", psh->commandname, psh->startlinno, msg));
+ outfmt(&psh->errout, "%s: %d: ", psh->commandname, psh->startlinno);
+ } else {
+ TRACE((psh, "synerror: Syntax error: %s\n", msg));
+ }
+ outfmt(&psh->errout, "Syntax error: %s\n", msg);
+ error(psh, (char *)NULL);
+ /* NOTREACHED */
+}
+
+STATIC const char *
+my_basename(const char *argv0, unsigned *lenp)
+{
+ const char *tmp;
+
+ /* skip the path */
+ for (tmp = strpbrk(argv0, "\\/:"); tmp; tmp = strpbrk(argv0, "\\/:"))
+ argv0 = tmp + 1;
+
+ if (lenp) {
+ /* find the end, ignoring extenions */
+ tmp = strrchr(argv0, '.');
+ if (!tmp)
+ tmp = strchr(argv0, '\0');
+ *lenp = (unsigned)(tmp - argv0);
+ }
+ return argv0;
+}
+
+
+STATIC void
+setprompt(shinstance *psh, int which)
+{
+ psh->whichprompt = which;
+
+#ifndef SMALL
+ if (!el)
+#endif
+ {
+ /* deal with bash prompts */
+ const char *prompt = getprompt(psh, NULL);
+ if (!strchr(prompt, '\\')) {
+ out2str(psh, prompt);
+ } else {
+ while (*prompt) {
+ if (*prompt != '\\') {
+ out2c(psh, *prompt++);
+ } else {
+ prompt++;
+ switch (*prompt++)
+ {
+ /* simple */
+ case '$': out2c(psh, sh_geteuid(psh) ? '$' : '#'); break;
+ case '\\': out2c(psh, '\\'); break;
+ case 'a': out2c(psh, '\a'); break;
+ case 'e': out2c(psh, 033); break;
+ case 'n': out2c(psh, '\n'); break;
+ case 'r': out2c(psh, '\r'); break;
+
+ /* complicated */
+ case 's': {
+ unsigned len;
+ const char *arg0 = my_basename(psh->arg0, &len);
+ outfmt(psh->out2, "%.*s", len, arg0);
+ break;
+ }
+ case 'v':
+ outfmt(psh->out2, "%d.%d", KBUILD_VERSION_MAJOR,
+ KBUILD_VERSION_MINOR);
+ break;
+ case 'V':
+ outfmt(psh->out2, "%d.%d.%d", KBUILD_VERSION_MAJOR,
+ KBUILD_VERSION_MINOR, KBUILD_VERSION_PATCH);
+ break;
+ out2str(psh, getpwd(psh, 1) ? getpwd(psh, 1) : "?");
+ break;
+ case 'w':
+ case 'W': {
+ const char *cwd = getpwd(psh, 1);
+ const char *home = bltinlookup(psh, "HOME", 1);
+ size_t home_len = home ? strlen(home) : 0;
+ if (!cwd) cwd = "?";
+ if (!strncmp(cwd, home, home_len)
+ && ( cwd[home_len] == '\0'
+ || (cwd[home_len] == '/' && prompt[-1] == 'w'))) {
+ out2c(psh, '~');
+ if (prompt[-1] == 'w' && cwd[home_len]) {
+ out2str(psh, cwd + home_len);
+ }
+ } else if (prompt[-1] == 'w') {
+ out2str(psh, cwd);
+ } else {
+ out2str(psh, my_basename(cwd, NULL));
+ }
+ break;
+ }
+ case '0':
+ case '1':
+ case '2':
+ case '3': {
+ unsigned int ch = prompt[-1] - '0';
+ if (isdigit(*prompt)) {
+ ch *= 8;
+ ch += *prompt++ - '0';
+ }
+ if (isdigit(*prompt)) {
+ ch *= 8;
+ ch += *prompt++ - '0';
+ }
+ out2c(psh, ch);
+ break;
+ }
+
+ /* ignore */
+ break;
+ case '!':
+ case '#':
+ case '@':
+ case 'A':
+ case 'h':
+ case 'H':
+ case 'j':
+ case 'l':
+ case 't':
+ case 'T':
+ case 'u':
+ case '[':
+ if (strchr(prompt, ']')) {
+ prompt = strchr(prompt, ']') + 1;
+ }
+ break;
+ case 'D':
+ if (*prompt == '{' && strchr(prompt, '}')) {
+ prompt = strchr(prompt, '}') + 1;
+ }
+ break;
+ }
+
+ }
+ }
+ }
+ }
+}
+
+/*
+ * called by editline -- any expansions to the prompt
+ * should be added here.
+ */
+const char *
+getprompt(shinstance *psh, void *unused)
+{
+ switch (psh->whichprompt) {
+ case 0:
+ return "";
+ case 1:
+ return ps1val(psh);
+ case 2:
+ return ps2val(psh);
+ default:
+ return "<internal prompt error>";
+ }
+}
+
+#ifndef KASH_SEPARATE_PARSER_ALLOCATOR
+
+static union node *copyparsetreeint(shinstance *psh, union node *src);
+
+/*
+ * Helper to copyparsetreeint.
+ */
+static struct nodelist *
+copynodelist(shinstance *psh, struct nodelist *src)
+{
+ struct nodelist *ret = NULL;
+ if (src) {
+ struct nodelist **ppnext = &ret;
+ while (src) {
+ struct nodelist *dst = pstalloclist(psh);
+ dst->next = NULL;
+ *ppnext = dst;
+ ppnext = &dst->next;
+ dst->n = copyparsetreeint(psh, src->n);
+ src = src->next;
+ }
+ }
+ return ret;
+}
+
+/*
+ * Duplicates a node tree.
+ *
+ * Note! This could probably be generated from nodelist.
+ */
+static union node *
+copyparsetreeint(shinstance *psh, union node *src)
+{
+ /** @todo Try avoid recursion for one of the sub-nodes, esp. when there
+ * is a list like 'next' one. */
+ union node *ret;
+ if (src) {
+ int const type = src->type;
+ switch (type) {
+ case NSEMI:
+ case NAND:
+ case NOR:
+ case NWHILE:
+ case NUNTIL:
+ ret = pstallocnode(psh, sizeof(src->nbinary));
+ ret->nbinary.type = type;
+ ret->nbinary.ch1 = copyparsetreeint(psh, src->nbinary.ch1);
+ ret->nbinary.ch2 = copyparsetreeint(psh, src->nbinary.ch2);
+ break;
+
+ case NCMD:
+ ret = pstallocnode(psh, sizeof(src->ncmd));
+ ret->ncmd.type = NCMD;
+ ret->ncmd.backgnd = src->ncmd.backgnd;
+ ret->ncmd.args = copyparsetreeint(psh, src->ncmd.args);
+ ret->ncmd.redirect = copyparsetreeint(psh, src->ncmd.redirect);
+ break;
+
+ case NPIPE:
+ ret = pstallocnode(psh, sizeof(src->npipe));
+ ret->npipe.type = NPIPE;
+ ret->npipe.backgnd = src->ncmd.backgnd;
+ ret->npipe.cmdlist = copynodelist(psh, src->npipe.cmdlist);
+ break;
+
+ case NREDIR:
+ case NBACKGND:
+ case NSUBSHELL:
+ ret = pstallocnode(psh, sizeof(src->nredir));
+ ret->nredir.type = type;
+ ret->nredir.n = copyparsetreeint(psh, src->nredir.n);
+ ret->nredir.redirect = copyparsetreeint(psh, src->nredir.redirect);
+ break;
+
+ case NIF:
+ ret = pstallocnode(psh, sizeof(src->nif));
+ ret->nif.type = NIF;
+ ret->nif.test = copyparsetreeint(psh, src->nif.test);
+ ret->nif.ifpart = copyparsetreeint(psh, src->nif.ifpart);
+ ret->nif.elsepart = copyparsetreeint(psh, src->nif.elsepart);
+ break;
+
+ case NFOR:
+ ret = pstallocnode(psh, sizeof(src->nfor));
+ ret->nfor.type = NFOR;
+ ret->nfor.args = copyparsetreeint(psh, src->nfor.args);
+ ret->nfor.body = copyparsetreeint(psh, src->nfor.body);
+ ret->nfor.var = pstsavestr(psh, src->nfor.var);
+ break;
+
+ case NCASE:
+ ret = pstallocnode(psh, sizeof(src->ncase));
+ ret->ncase.type = NCASE;
+ ret->ncase.expr = copyparsetreeint(psh, src->ncase.expr);
+ ret->ncase.cases = copyparsetreeint(psh, src->ncase.cases);
+ break;
+
+ case NCLIST:
+ ret = pstallocnode(psh, sizeof(src->nclist));
+ ret->nclist.type = NCLIST;
+ ret->nclist.next = copyparsetreeint(psh, src->nclist.next);
+ ret->nclist.pattern = copyparsetreeint(psh, src->nclist.pattern);
+ ret->nclist.body = copyparsetreeint(psh, src->nclist.body);
+ break;
+
+ case NDEFUN:
+ case NARG:
+ ret = pstallocnode(psh, sizeof(src->narg));
+ ret->narg.type = type;
+ ret->narg.next = copyparsetreeint(psh, src->narg.next);
+ ret->narg.text = pstsavestr(psh, src->narg.text);
+ ret->narg.backquote = copynodelist(psh, src->narg.backquote);
+ break;
+
+ case NTO:
+ case NCLOBBER:
+ case NFROM:
+ case NFROMTO:
+ case NAPPEND:
+ ret = pstallocnode(psh, sizeof(src->nfile));
+ ret->nfile.type = type;
+ ret->nfile.fd = src->nfile.fd;
+ ret->nfile.next = copyparsetreeint(psh, src->nfile.next);
+ ret->nfile.fname = copyparsetreeint(psh, src->nfile.fname);
+ break;
+
+ case NTOFD:
+ case NFROMFD:
+ ret = pstallocnode(psh, sizeof(src->ndup));
+ ret->ndup.type = type;
+ ret->ndup.fd = src->ndup.fd;
+ ret->ndup.next = copyparsetreeint(psh, src->ndup.next);
+ ret->ndup.dupfd = src->ndup.dupfd;
+ ret->ndup.vname = copyparsetreeint(psh, src->ndup.vname);
+ break;
+
+ case NHERE:
+ case NXHERE:
+ ret = pstallocnode(psh, sizeof(src->nhere));
+ ret->nhere.type = type;
+ ret->nhere.fd = src->nhere.fd;
+ ret->nhere.next = copyparsetreeint(psh, src->nhere.next);
+ ret->nhere.doc = copyparsetreeint(psh, src->nhere.doc);
+ break;
+
+ case NNOT:
+ ret = pstallocnode(psh, sizeof(src->nnot));
+ ret->nnot.type = NNOT;
+ ret->nnot.com = copyparsetreeint(psh, src->nnot.com);
+ break;
+
+ default:
+ error(psh, "Unknown node type: %d (node=%p)", src->type, src);
+ return NULL;
+ }
+ } else {
+ ret = NULL;
+ }
+ return ret;
+}
+
+#endif
+
+union node *copyparsetree(shinstance *psh, union node *src)
+{
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ K_NOREF(psh);
+ pstackretainpush(psh, src->pblock);
+ return src;
+#else
+ return copyparsetreeint(psh, src);
+#endif
+}
+
diff --git a/src/kash/parser.h b/src/kash/parser.h
new file mode 100644
index 0000000..58408be
--- /dev/null
+++ b/src/kash/parser.h
@@ -0,0 +1,88 @@
+/* $NetBSD: parser.h,v 1.17 2004/06/26 22:09:49 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)parser.h 8.3 (Berkeley) 5/4/95
+ */
+
+#ifndef ___parse_h
+#define ___parse_h
+
+/* control characters in argument strings */
+#define CTL_FIRST '\201' /* first 'special' character */
+#define CTLESC '\201' /* escape next character */
+#define CTLVAR '\202' /* variable defn */
+#define CTLENDVAR '\203'
+#define CTLBACKQ '\204'
+#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */
+/* CTLBACKQ | CTLQUOTE == '\205' */
+#define CTLARI '\206' /* arithmetic expression */
+#define CTLENDARI '\207'
+#define CTLQUOTEMARK '\210'
+#define CTLQUOTEEND '\211' /* only inside ${...} */
+#define CTL_LAST '\211' /* last 'special' character */
+
+/* variable substitution byte (follows CTLVAR) */
+#define VSTYPE 0x0f /* type of variable substitution */
+#define VSNUL 0x10 /* colon--treat the empty string as unset */
+#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */
+
+/* values of VSTYPE field */
+#define VSNORMAL 0x1 /* normal variable: $var or ${var} */
+#define VSMINUS 0x2 /* ${var-text} */
+#define VSPLUS 0x3 /* ${var+text} */
+#define VSQUESTION 0x4 /* ${var?message} */
+#define VSASSIGN 0x5 /* ${var=text} */
+#define VSTRIMLEFT 0x6 /* ${var#pattern} */
+#define VSTRIMLEFTMAX 0x7 /* ${var##pattern} */
+#define VSTRIMRIGHT 0x8 /* ${var%pattern} */
+#define VSTRIMRIGHTMAX 0x9 /* ${var%%pattern} */
+#define VSLENGTH 0xa /* ${#var} */
+
+
+/*
+ * NEOF is returned by parsecmd when it encounters an end of file. It
+ * must be distinct from NULL, so we use the address of a variable that
+ * happens to be handy.
+ */
+/*extern int tokpushback;*/
+#define NEOF ((union node *)&psh->tokpushback)
+/*extern int whichprompt;*/ /* 1 == PS1, 2 == PS2 */
+
+
+union node *parsecmd(struct shinstance *, int);
+void fixredir(struct shinstance *, union node *, const char *, int);
+int goodname(const char *);
+const char *getprompt(struct shinstance *, void *);
+union node *copyparsetree(shinstance *, union node *);
+
+#endif
diff --git a/src/kash/redir.c b/src/kash/redir.c
new file mode 100644
index 0000000..e9c3092
--- /dev/null
+++ b/src/kash/redir.c
@@ -0,0 +1,484 @@
+/* $NetBSD: redir.c,v 1.29 2004/07/08 03:57:33 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)redir.c 8.2 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: redir.c,v 1.29 2004/07/08 03:57:33 christos Exp $");
+#endif /* not lint */
+#endif
+
+#include <sys/types.h>
+#include <limits.h> /* PIPE_BUF */
+#include <string.h>
+#include <errno.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+/*
+ * Code for dealing with input/output redirection.
+ */
+
+#include "main.h"
+#include "shell.h"
+#include "nodes.h"
+#include "jobs.h"
+#include "options.h"
+#include "expand.h"
+#include "redir.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "shinstance.h"
+
+
+#define EMPTY -2 /* marks an unused slot in redirtab */
+#define PIPESIZE SHFILE_PIPE_SIZE
+
+
+//MKINIT struct redirtab *redirlist;
+
+/*
+ * We keep track of whether or not fd0 has been redirected. This is for
+ * background commands, where we want to redirect fd0 to /dev/null only
+ * if it hasn't already been redirected.
+*/
+//int fd0_redirected = 0;
+
+STATIC void openredirect(shinstance *, union node *, char[10], int, const char *);
+STATIC int openhere(shinstance *, union node *);
+
+
+#ifndef SH_FORKED_MODE
+void
+subshellinitredir(shinstance *psh, shinstance *inherit)
+{
+ /* We can have a redirlist here if we're handling backtick while expanding
+ arguments, just copy it even if the subshell probably doesn't need it. */
+ struct redirtab *src = inherit->redirlist;
+ if (src)
+ {
+ struct redirtab **dstp = &psh->redirlist;
+ do
+ {
+ struct redirtab *dst = ckmalloc(psh, sizeof(*dst));
+ memcpy(dst->renamed, src->renamed, sizeof(dst->renamed));
+ *dstp = dst;
+ dstp = &dst->next;
+ src = src->next;
+ } while (src);
+ *dstp = NULL;
+
+ psh->fd0_redirected = inherit->fd0_redirected;
+ }
+
+ /* Copy the expanded redirection filenames (stack), but only the last entry
+ as the subshell does not have the ability to unwind stack in the parent
+ and therefore cannot get to the earlier redirection stuff: */
+ if (inherit->expfnames)
+ {
+ redirexpfnames * const expfnamesrc = inherit->expfnames;
+ unsigned i = expfnamesrc->count;
+ redirexpfnames *dst = stalloc(psh, offsetof(redirexpfnames, names) + sizeof(dst->names[0]) * i);
+ dst->count = i;
+ dst->depth = 1;
+ dst->prev = NULL;
+ while (i-- > 0)
+ dst->names[i] = stsavestr(psh, expfnamesrc->names[i]);
+ psh->expfnames = dst;
+ }
+}
+#endif /* !SH_FORKED_MODE */
+
+
+/*
+ * Process a list of redirection commands. If the REDIR_PUSH flag is set,
+ * old file descriptors are stashed away so that the redirection can be
+ * undone by calling popredir. If the REDIR_BACKQ flag is set, then the
+ * standard output, and the standard error if it becomes a duplicate of
+ * stdout, is saved in memory.
+ */
+
+void
+redirect(shinstance *psh, union node *redir, int flags)
+{
+ union node *n;
+ struct redirtab *sv = NULL;
+ int i;
+ int fd;
+ int try;
+ char memory[10]; /* file descriptors to write to memory */
+ unsigned idxexpfname;
+
+ for (i = 10 ; --i >= 0 ; )
+ memory[i] = 0;
+ memory[1] = flags & REDIR_BACKQ;
+ if (flags & REDIR_PUSH) {
+ sv = ckmalloc(psh, sizeof (struct redirtab));
+ for (i = 0 ; i < 10 ; i++)
+ sv->renamed[i] = EMPTY;
+ sv->next = psh->redirlist;
+ psh->redirlist = sv;
+ }
+ idxexpfname = 0;
+ for (n = redir, idxexpfname = 0 ; n ; n = n->nfile.next, idxexpfname++) {
+ kHlpAssert(idxexpfname < psh->expfnames->count);
+ fd = n->nfile.fd;
+ try = 0;
+ if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) &&
+ n->ndup.dupfd == fd)
+ continue; /* redirect from/to same file descriptor */
+
+ if ((flags & REDIR_PUSH) && sv->renamed[fd] == EMPTY) {
+ INTOFF;
+again:
+ if ((i = shfile_fcntl(&psh->fdtab, fd, F_DUPFD, 10)) == -1) {
+ switch (errno) {
+ case EBADF:
+ if (!try) {
+ openredirect(psh, n, memory, flags, psh->expfnames->names[idxexpfname]);
+ try++;
+ goto again;
+ }
+ /* FALLTHROUGH*/
+ default:
+ INTON;
+ error(psh, "%d: %s", fd, sh_strerror(psh, errno));
+ /* NOTREACHED */
+ }
+ }
+ if (!try) {
+ sv->renamed[fd] = i;
+ shfile_close(&psh->fdtab, fd);
+ }
+ INTON;
+ } else {
+ shfile_close(&psh->fdtab, fd);
+ }
+ if (fd == 0)
+ psh->fd0_redirected++;
+ if (!try)
+ openredirect(psh, n, memory, flags, psh->expfnames->names[idxexpfname]);
+ }
+ kHlpAssert(!redir || idxexpfname == psh->expfnames->count);
+ if (memory[1])
+ psh->out1 = &psh->memout;
+ if (memory[2])
+ psh->out2 = &psh->memout;
+}
+
+
+STATIC void
+openredirect(shinstance *psh, union node *redir, char memory[10], int flags, const char *fname)
+{
+ int fd = redir->nfile.fd;
+ int f;
+ int oflags = O_WRONLY|O_CREAT|O_TRUNC;
+
+ /*
+ * We suppress interrupts so that we won't leave open file
+ * descriptors around. This may not be such a good idea because
+ * an open of a device or a fifo can block indefinitely.
+ */
+ INTOFF;
+ memory[fd] = 0;
+ switch (redir->nfile.type) {
+ case NFROM:
+ if ((f = shfile_open(&psh->fdtab, fname, O_RDONLY, 0)) < 0)
+ goto eopen;
+ break;
+ case NFROMTO:
+ if ((f = shfile_open(&psh->fdtab, fname, O_RDWR|O_CREAT|O_TRUNC, 0666)) < 0)
+ goto ecreate;
+ break;
+ case NTO:
+ if (Cflag(psh))
+ oflags |= O_EXCL;
+ /* FALLTHROUGH */
+ case NCLOBBER:
+ if ((f = shfile_open(&psh->fdtab, fname, oflags, 0666)) < 0)
+ goto ecreate;
+ break;
+ case NAPPEND:
+ if ((f = shfile_open(&psh->fdtab, fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0)
+ goto ecreate;
+ break;
+ case NTOFD:
+ case NFROMFD:
+ if (redir->ndup.dupfd >= 0) { /* if not ">&-" */
+ if (memory[redir->ndup.dupfd])
+ memory[fd] = 1;
+ else
+ copyfd(psh, redir->ndup.dupfd, fd);
+ }
+ INTON;
+ return;
+ case NHERE:
+ case NXHERE:
+ f = openhere(psh, redir);
+ break;
+ default:
+ sh_abort(psh);
+ }
+
+ if (f != fd) {
+ movefd(psh, f, fd);
+ }
+ INTON;
+ return;
+ecreate:
+ error(psh, "cannot create %s: %s", fname, errmsg(psh, errno, E_CREAT));
+eopen:
+ error(psh, "cannot open %s: %s", fname, errmsg(psh, errno, E_OPEN));
+}
+
+#ifdef KASH_USE_FORKSHELL2
+struct openherechild
+{
+ int pip[2];
+ size_t len;
+};
+static int openhere_child(shinstance *psh, union node *n, void *argp)
+{
+ struct openherechild args = *(struct openherechild *)argp;
+
+ shfile_close(&psh->fdtab, args.pip[0]);
+ sh_signal(psh, SIGINT, SH_SIG_IGN);
+ sh_signal(psh, SIGQUIT, SH_SIG_IGN);
+ sh_signal(psh, SIGHUP, SH_SIG_IGN);
+# ifdef SIGTSTP
+ sh_signal(psh, SIGTSTP, SH_SIG_IGN);
+# endif
+ sh_signal(psh, SIGPIPE, SH_SIG_DFL);
+ if (n->type == NHERE)
+ xwrite(psh, args.pip[1], n->nhere.doc->narg.text, args.len);
+ else
+ expandhere(psh, n->nhere.doc, args.pip[1]);
+ return 0;
+}
+
+#endif /* KASH_USE_FORKSHELL2*/
+
+/*
+ * Handle here documents. Normally we fork off a process to write the
+ * data to a pipe. If the document is short, we can stuff the data in
+ * the pipe without forking.
+ */
+
+STATIC int
+openhere(shinstance *psh, union node *redir)
+{
+ int pip[2];
+ size_t len = 0;
+
+ if (shfile_pipe(&psh->fdtab, pip) < 0)
+ error(psh, "Pipe call failed");
+ if (redir->type == NHERE) {
+ len = strlen(redir->nhere.doc->narg.text);
+ if (len <= PIPESIZE) {
+ xwrite(psh, pip[1], redir->nhere.doc->narg.text, len);
+ goto out;
+ }
+ }
+#ifdef KASH_USE_FORKSHELL2
+ {
+ struct openherechild args;
+ args.pip[0] = pip[0];
+ args.pip[1] = pip[1];
+ args.len = len;
+ forkshell2(psh, (struct job *)NULL, redir,
+ FORK_NOJOB | FORK_JUST_IO,
+ openhere_child, redir, &args, sizeof(args), NULL);
+ }
+#else
+ if (forkshell(psh, (struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) {
+ shfile_close(&psh->fdtab, pip[0]);
+ sh_signal(psh, SIGINT, SH_SIG_IGN);
+ sh_signal(psh, SIGQUIT, SH_SIG_IGN);
+ sh_signal(psh, SIGHUP, SH_SIG_IGN);
+#ifdef SIGTSTP
+ sh_signal(psh, SIGTSTP, SH_SIG_IGN);
+#endif
+ sh_signal(psh, SIGPIPE, SH_SIG_DFL);
+ if (redir->type == NHERE)
+ xwrite(psh, pip[1], redir->nhere.doc->narg.text, len);
+ else
+ expandhere(psh, redir->nhere.doc, pip[1]);
+ sh__exit(psh, 0);
+ }
+#endif
+out:
+ shfile_close(&psh->fdtab, pip[1]);
+ return pip[0];
+}
+
+
+
+/*
+ * Undo the effects of the last redirection.
+ */
+
+void
+popredir(shinstance *psh)
+{
+ struct redirtab *rp = psh->redirlist;
+ int i;
+
+ for (i = 0 ; i < 10 ; i++) {
+ if (rp->renamed[i] != EMPTY) {
+ if (i == 0)
+ psh->fd0_redirected--;
+ if (rp->renamed[i] >= 0) {
+ movefd(psh, rp->renamed[i], i);
+ } else {
+ shfile_close(&psh->fdtab, i);
+ }
+ }
+ }
+ INTOFF;
+ psh->redirlist = rp->next;
+ ckfree(psh, rp);
+ INTON;
+}
+
+/*
+ * Undo all redirections. Called on error or interrupt.
+ */
+
+#ifdef mkinit
+
+INCLUDE "redir.h"
+
+RESET {
+ while (psh->redirlist)
+ popredir(psh);
+}
+
+SHELLPROC {
+ clearredir(psh);
+}
+
+#endif
+
+/* Return true if fd 0 has already been redirected at least once. */
+int
+fd0_redirected_p(shinstance *psh) {
+ return psh->fd0_redirected != 0;
+}
+
+/*
+ * Discard all saved file descriptors.
+ */
+
+void
+clearredir(shinstance *psh)
+{
+ struct redirtab *rp;
+ int i;
+
+ for (rp = psh->redirlist ; rp ; rp = rp->next) {
+ for (i = 0 ; i < 10 ; i++) {
+ if (rp->renamed[i] >= 0) {
+ shfile_close(&psh->fdtab, rp->renamed[i]);
+ }
+ rp->renamed[i] = EMPTY;
+ }
+ }
+}
+
+
+
+/*
+ * Copy a file descriptor to be >= to. Returns -1
+ * if the source file descriptor is closed, EMPTY if there are no unused
+ * file descriptors left.
+ */
+
+int
+copyfd(shinstance *psh, int from, int to)
+{
+ int newfd;
+
+ newfd = shfile_fcntl(&psh->fdtab, from, F_DUPFD, to);
+ if (newfd < 0) {
+ if (errno == EMFILE)
+ return EMPTY;
+ error(psh, "%d: %s", from, sh_strerror(psh, errno));
+ }
+ return newfd;
+}
+
+
+/*
+ * Move a file descriptor to be == to. Returns -1
+ * if the source file descriptor is closed, EMPTY if there are no unused
+ * file descriptors left.
+ */
+
+int
+movefd(shinstance *psh, int from, int to)
+{
+ int newfd;
+
+ newfd = shfile_movefd(&psh->fdtab, from, to);
+ if (newfd < 0) {
+ if (errno == EMFILE)
+ return EMPTY;
+ error(psh, "%d: %s", from, sh_strerror(psh, errno));
+ }
+ return newfd;
+}
+
+
+/*
+ * Move a file descriptor to be >= to. Returns -1
+ * if the source file descriptor is closed, EMPTY if there are no unused
+ * file descriptors left.
+ */
+
+int
+movefd_above(shinstance *psh, int from, int to)
+{
+ int newfd;
+
+ newfd = shfile_movefd_above(&psh->fdtab, from, to);
+ if (newfd < 0) {
+ if (errno == EMFILE)
+ return EMPTY;
+ error(psh, "%d: %s", from, sh_strerror(psh, errno));
+ }
+ return newfd;
+}
+
diff --git a/src/kash/redir.h b/src/kash/redir.h
new file mode 100644
index 0000000..7e8e16e
--- /dev/null
+++ b/src/kash/redir.h
@@ -0,0 +1,54 @@
+/* $NetBSD: redir.h,v 1.15 2003/08/07 09:05:37 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)redir.h 8.2 (Berkeley) 5/4/95
+ */
+
+/* flags passed to redirect */
+#define REDIR_PUSH 01 /* save previous values of file descriptors */
+#define REDIR_BACKQ 02 /* save the command output in memory */
+
+union node;
+#ifndef SH_FORKED_MODE
+void subshellinitredir(shinstance *, shinstance *);
+#endif
+void redirect(struct shinstance *, union node *, int);
+void popredir(struct shinstance *);
+int fd0_redirected_p(struct shinstance *);
+struct redirexpfnames;
+void expredircleanup(shinstance *, unsigned);
+void clearredir(struct shinstance *);
+int copyfd(struct shinstance *, int, int);
+int movefd(struct shinstance *, int, int);
+int movefd_above(struct shinstance *, int, int);
+
diff --git a/src/kash/setmode.c b/src/kash/setmode.c
new file mode 100644
index 0000000..02fa1f9
--- /dev/null
+++ b/src/kash/setmode.c
@@ -0,0 +1,472 @@
+/* $NetBSD: setmode.c,v 1.30 2003/08/07 16:42:56 agc Exp $ */
+
+/*
+ * Copyright (c) 1989, 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Dave Borman at Cray Research, Inc.
+ *
+ * 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.
+ */
+
+#if 0
+#if defined(LIBC_SCCS) && !defined(lint)
+static char sccsid[] = "@(#)setmode.c 8.2 (Berkeley) 3/25/94";
+#else
+__RCSID("$NetBSD: setmode.c,v 1.30 2003/08/07 16:42:56 agc Exp $");
+#endif /* LIBC_SCCS and not lint */
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "shinstance.h" /* for unistd.h types/defines */
+
+#ifdef SETMODE_DEBUG
+#include <stdio.h>
+#endif
+
+#define SET_LEN 6 /* initial # of bitcmd struct to malloc */
+#define SET_LEN_INCR 4 /* # of bitcmd structs to add as needed */
+
+typedef struct bitcmd {
+ char cmd;
+ char cmd2;
+ mode_t bits;
+} BITCMD;
+
+#define CMD2_CLR 0x01
+#define CMD2_SET 0x02
+#define CMD2_GBITS 0x04
+#define CMD2_OBITS 0x08
+#define CMD2_UBITS 0x10
+
+static BITCMD *addcmd(BITCMD *, int, int, int, unsigned int);
+static void compress_mode(BITCMD *);
+#ifdef SETMODE_DEBUG
+static void dumpmode(BITCMD *);
+#endif
+
+#ifndef _DIAGASSERT
+# define _DIAGASSERT kHlpAssert
+#endif
+
+#ifndef S_ISTXT
+# ifdef S_ISVTX
+# define S_ISTXT S_ISVTX
+# else
+# define S_ISTXT 0
+# endif
+#endif /* !S_ISTXT */
+
+/*
+ * Given the old mode and an array of bitcmd structures, apply the operations
+ * described in the bitcmd structures to the old mode, and return the new mode.
+ * Note that there is no '=' command; a strict assignment is just a '-' (clear
+ * bits) followed by a '+' (set bits).
+ */
+mode_t
+kash_getmode(const void *bbox, mode_t omode)
+{
+ const BITCMD *set;
+ mode_t clrval, newmode, value;
+
+ _DIAGASSERT(bbox != NULL);
+
+ set = (const BITCMD *)bbox;
+ newmode = omode;
+ for (value = 0;; set++)
+ switch(set->cmd) {
+ /*
+ * When copying the user, group or other bits around, we "know"
+ * where the bits are in the mode so that we can do shifts to
+ * copy them around. If we don't use shifts, it gets real
+ * grundgy with lots of single bit checks and bit sets.
+ */
+ case 'u':
+ value = (newmode & S_IRWXU) >> 6;
+ goto common;
+
+ case 'g':
+ value = (newmode & S_IRWXG) >> 3;
+ goto common;
+
+ case 'o':
+ value = newmode & S_IRWXO;
+common: if (set->cmd2 & CMD2_CLR) {
+ clrval =
+ (set->cmd2 & CMD2_SET) ? S_IRWXO : value;
+ if (set->cmd2 & CMD2_UBITS)
+ newmode &= ~((clrval<<6) & set->bits);
+ if (set->cmd2 & CMD2_GBITS)
+ newmode &= ~((clrval<<3) & set->bits);
+ if (set->cmd2 & CMD2_OBITS)
+ newmode &= ~(clrval & set->bits);
+ }
+ if (set->cmd2 & CMD2_SET) {
+ if (set->cmd2 & CMD2_UBITS)
+ newmode |= (value<<6) & set->bits;
+ if (set->cmd2 & CMD2_GBITS)
+ newmode |= (value<<3) & set->bits;
+ if (set->cmd2 & CMD2_OBITS)
+ newmode |= value & set->bits;
+ }
+ break;
+
+ case '+':
+ newmode |= set->bits;
+ break;
+
+ case '-':
+ newmode &= ~set->bits;
+ break;
+
+ case 'X':
+ if (omode & (S_IFDIR|S_IXUSR|S_IXGRP|S_IXOTH))
+ newmode |= set->bits;
+ break;
+
+ case '\0':
+ default:
+#ifdef SETMODE_DEBUG
+ (void)printf("getmode:%04o -> %04o\n", omode, newmode);
+#endif
+ return (newmode);
+ }
+}
+
+#define ADDCMD(a, b, c, d) do { \
+ if (set >= endset) { \
+ BITCMD *newset; \
+ setlen += SET_LEN_INCR; \
+ newset = sh_realloc(NULL, saveset, sizeof(BITCMD) * setlen); \
+ if (newset == NULL) { \
+ sh_free(NULL, saveset); \
+ return (NULL); \
+ } \
+ set = newset + (set - saveset); \
+ saveset = newset; \
+ endset = newset + (setlen - 2); \
+ } \
+ set = addcmd(set, (a), (b), (c), (d)); \
+} while (/*CONSTCOND*/0)
+
+#define STANDARD_BITS (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
+
+void *
+kash_setmode(shinstance *psh, const char *p)
+{
+ int perm, who;
+ char op, *ep;
+ BITCMD *set, *saveset, *endset;
+ mode_t mask;
+ int equalopdone = 0; /* pacify gcc */
+ int permXbits, setlen;
+
+ if (!*p)
+ return (NULL);
+
+ /*
+ * Get a copy of the mask for the permissions that are mask relative.
+ * Flip the bits, we want what's not set.
+ */
+ mask = shfile_get_umask(&psh->fdtab);
+
+ setlen = SET_LEN + 2;
+
+ if ((set = sh_malloc(NULL, sizeof(BITCMD) * setlen)) == NULL)
+ return (NULL);
+ saveset = set;
+ endset = set + (setlen - 2);
+
+ /*
+ * If an absolute number, get it and return; disallow non-octal digits
+ * or illegal bits.
+ */
+ if (isdigit((unsigned char)*p)) {
+ perm = (mode_t)strtol(p, &ep, 8);
+ if (*ep || perm & ~(STANDARD_BITS|S_ISTXT)) {
+ sh_free(NULL, saveset);
+ return (NULL);
+ }
+ ADDCMD('=', (STANDARD_BITS|S_ISTXT), perm, mask);
+ set->cmd = 0;
+ return (saveset);
+ }
+
+ /*
+ * Build list of structures to set/clear/copy bits as described by
+ * each clause of the symbolic mode.
+ */
+ for (;;) {
+ /* First, find out which bits might be modified. */
+ for (who = 0;; ++p) {
+ switch (*p) {
+ case 'a':
+ who |= STANDARD_BITS;
+ break;
+ case 'u':
+ who |= S_ISUID|S_IRWXU;
+ break;
+ case 'g':
+ who |= S_ISGID|S_IRWXG;
+ break;
+ case 'o':
+ who |= S_IRWXO;
+ break;
+ default:
+ goto getop;
+ }
+ }
+
+getop: if ((op = *p++) != '+' && op != '-' && op != '=') {
+ sh_free(NULL, saveset);
+ return (NULL);
+ }
+ if (op == '=')
+ equalopdone = 0;
+
+ who &= ~S_ISTXT;
+ for (perm = 0, permXbits = 0;; ++p) {
+ switch (*p) {
+ case 'r':
+ perm |= S_IRUSR|S_IRGRP|S_IROTH;
+ break;
+ case 's':
+ /*
+ * If specific bits where requested and
+ * only "other" bits ignore set-id.
+ */
+ if (who == 0 || (who & ~S_IRWXO))
+ perm |= S_ISUID|S_ISGID;
+ break;
+ case 't':
+ /*
+ * If specific bits where requested and
+ * only "other" bits ignore set-id.
+ */
+ if (who == 0 || (who & ~S_IRWXO)) {
+ who |= S_ISTXT;
+ perm |= S_ISTXT;
+ }
+ break;
+ case 'w':
+ perm |= S_IWUSR|S_IWGRP|S_IWOTH;
+ break;
+ case 'X':
+ permXbits = S_IXUSR|S_IXGRP|S_IXOTH;
+ break;
+ case 'x':
+ perm |= S_IXUSR|S_IXGRP|S_IXOTH;
+ break;
+ case 'u':
+ case 'g':
+ case 'o':
+ /*
+ * When ever we hit 'u', 'g', or 'o', we have
+ * to flush out any partial mode that we have,
+ * and then do the copying of the mode bits.
+ */
+ if (perm) {
+ ADDCMD(op, who, perm, mask);
+ perm = 0;
+ }
+ if (op == '=')
+ equalopdone = 1;
+ if (op == '+' && permXbits) {
+ ADDCMD('X', who, permXbits, mask);
+ permXbits = 0;
+ }
+ ADDCMD(*p, who, op, mask);
+ break;
+
+ default:
+ /*
+ * Add any permissions that we haven't already
+ * done.
+ */
+ if (perm || (op == '=' && !equalopdone)) {
+ if (op == '=')
+ equalopdone = 1;
+ ADDCMD(op, who, perm, mask);
+ perm = 0;
+ }
+ if (permXbits) {
+ ADDCMD('X', who, permXbits, mask);
+ permXbits = 0;
+ }
+ goto apply;
+ }
+ }
+
+apply: if (!*p)
+ break;
+ if (*p != ',')
+ goto getop;
+ ++p;
+ }
+ set->cmd = 0;
+#ifdef SETMODE_DEBUG
+ (void)printf("Before compress_mode()\n");
+ dumpmode(saveset);
+#endif
+ compress_mode(saveset);
+#ifdef SETMODE_DEBUG
+ (void)printf("After compress_mode()\n");
+ dumpmode(saveset);
+#endif
+ return (saveset);
+}
+
+static BITCMD *
+addcmd(set, op, who, oparg, mask)
+ BITCMD *set;
+ int oparg, who;
+ int op;
+ unsigned int mask;
+{
+
+ _DIAGASSERT(set != NULL);
+
+ switch (op) {
+ case '=':
+ set->cmd = '-';
+ set->bits = who ? who : STANDARD_BITS;
+ set++;
+
+ op = '+';
+ /* FALLTHROUGH */
+ case '+':
+ case '-':
+ case 'X':
+ set->cmd = op;
+ set->bits = (who ? (unsigned int)who : mask) & (unsigned int)oparg;
+ break;
+
+ case 'u':
+ case 'g':
+ case 'o':
+ set->cmd = op;
+ if (who) {
+ set->cmd2 = ((who & S_IRUSR) ? CMD2_UBITS : 0) |
+ ((who & S_IRGRP) ? CMD2_GBITS : 0) |
+ ((who & S_IROTH) ? CMD2_OBITS : 0);
+ set->bits = (mode_t)~0;
+ } else {
+ set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS;
+ set->bits = mask;
+ }
+
+ if (oparg == '+')
+ set->cmd2 |= CMD2_SET;
+ else if (oparg == '-')
+ set->cmd2 |= CMD2_CLR;
+ else if (oparg == '=')
+ set->cmd2 |= CMD2_SET|CMD2_CLR;
+ break;
+ }
+ return (set + 1);
+}
+
+#ifdef SETMODE_DEBUG
+static void
+dumpmode(set)
+ BITCMD *set;
+{
+
+ _DIAGASSERT(set != NULL);
+
+ for (; set->cmd; ++set)
+ (void)printf("cmd: '%c' bits %04o%s%s%s%s%s%s\n",
+ set->cmd, set->bits, set->cmd2 ? " cmd2:" : "",
+ set->cmd2 & CMD2_CLR ? " CLR" : "",
+ set->cmd2 & CMD2_SET ? " SET" : "",
+ set->cmd2 & CMD2_UBITS ? " UBITS" : "",
+ set->cmd2 & CMD2_GBITS ? " GBITS" : "",
+ set->cmd2 & CMD2_OBITS ? " OBITS" : "");
+}
+#endif
+
+/*
+ * Given an array of bitcmd structures, compress by compacting consecutive
+ * '+', '-' and 'X' commands into at most 3 commands, one of each. The 'u',
+ * 'g' and 'o' commands continue to be separate. They could probably be
+ * compacted, but it's not worth the effort.
+ */
+static void
+compress_mode(set)
+ BITCMD *set;
+{
+ BITCMD *nset;
+ int setbits, clrbits, Xbits, op;
+
+ _DIAGASSERT(set != NULL);
+
+ for (nset = set;;) {
+ /* Copy over any 'u', 'g' and 'o' commands. */
+ while ((op = nset->cmd) != '+' && op != '-' && op != 'X') {
+ *set++ = *nset++;
+ if (!op)
+ return;
+ }
+
+ for (setbits = clrbits = Xbits = 0;; nset++) {
+ if ((op = nset->cmd) == '-') {
+ clrbits |= nset->bits;
+ setbits &= ~nset->bits;
+ Xbits &= ~nset->bits;
+ } else if (op == '+') {
+ setbits |= nset->bits;
+ clrbits &= ~nset->bits;
+ Xbits &= ~nset->bits;
+ } else if (op == 'X')
+ Xbits |= nset->bits & ~setbits;
+ else
+ break;
+ }
+ if (clrbits) {
+ set->cmd = '-';
+ set->cmd2 = 0;
+ set->bits = clrbits;
+ set++;
+ }
+ if (setbits) {
+ set->cmd = '+';
+ set->cmd2 = 0;
+ set->bits = setbits;
+ set++;
+ }
+ if (Xbits) {
+ set->cmd = 'X';
+ set->cmd2 = 0;
+ set->bits = Xbits;
+ set++;
+ }
+ }
+}
diff --git a/src/kash/sh.1 b/src/kash/sh.1
new file mode 100644
index 0000000..a116652
--- /dev/null
+++ b/src/kash/sh.1
@@ -0,0 +1,1949 @@
+.\" $NetBSD: sh.1,v 1.80 2005/05/24 00:03:52 wiz Exp $
+.\" Copyright (c) 1991, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Kenneth Almquist.
+.\"
+.\" 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.
+.\"
+.\" @(#)sh.1 8.6 (Berkeley) 5/4/95
+.\"
+.Dd May 7, 2005
+.Os
+.Dt SH 1
+.Sh NAME
+.Nm sh
+.Nd command interpreter (shell)
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl aCefnuvxIimqVEb
+.Op Cm +aCefnuvxIimqVEb
+.Ek
+.Bk -words
+.Op Fl o Ar option_name
+.Op Cm +o Ar option_name
+.Ek
+.Bk -words
+.Op Ar command_file Oo Ar argument ... Oc
+.Ek
+.Nm
+.Fl c
+.Bk -words
+.Op Fl aCefnuvxIimqVEb
+.Op Cm +aCefnuvxIimqVEb
+.Ek
+.Bk -words
+.Op Fl o Ar option_name
+.Op Cm +o Ar option_name
+.Ek
+.Bk -words
+.Ar command_string
+.Op Ar command_name Oo Ar argument ... Oc
+.Ek
+.Nm
+.Fl s
+.Bk -words
+.Op Fl aCefnuvxIimqVEb
+.Op Cm +aCefnuvxIimqVEb
+.Ek
+.Bk -words
+.Op Fl o Ar option_name
+.Op Cm +o Ar option_name
+.Ek
+.Bk -words
+.Op Ar argument ...
+.Ek
+.Sh DESCRIPTION
+.Nm
+is the standard command interpreter for the system.
+The current version of
+.Nm
+is in the process of being changed to conform with the
+.Tn POSIX
+1003.2 and 1003.2a specifications for the shell.
+This version has many
+features which make it appear similar in some respects to the Korn shell,
+but it is not a Korn shell clone (see
+.Xr ksh 1 ) .
+Only features designated by
+.Tn POSIX ,
+plus a few Berkeley extensions, are being incorporated into this shell.
+.\" We expect
+.\" .Tn POSIX
+.\" conformance by the time 4.4 BSD is released.
+This man page is not intended
+to be a tutorial or a complete specification of the shell.
+.Ss Overview
+The shell is a command that reads lines from either a file or the
+terminal, interprets them, and generally executes other commands.
+It is the program that is running when a user logs into the system
+(although a user can select a different shell with the
+.Xr chsh 1
+command).
+The shell implements a language that has flow control
+constructs, a macro facility that provides a variety of features in
+addition to data storage, along with built in history and line editing
+capabilities.
+It incorporates many features to aid interactive use and
+has the advantage that the interpretative language is common to both
+interactive and non-interactive use (shell scripts).
+That is, commands
+can be typed directly to the running shell or can be put into a file and
+the file can be executed directly by the shell.
+.Ss Invocation
+If no args are present and if the standard input of the shell
+is connected to a terminal (or if the
+.Fl i
+flag is set),
+and the
+.Fl c
+option is not present, the shell is considered an interactive shell.
+An interactive shell generally prompts before each command and handles
+programming and command errors differently (as described below).
+When first starting,
+the shell inspects argument 0, and if it begins with a dash
+.Sq - ,
+the shell is also considered
+a login shell.
+This is normally done automatically by the system
+when the user first logs in.
+A login shell first reads commands
+from the files
+.Pa /etc/profile
+and
+.Pa .profile
+if they exist.
+If the environment variable
+.Ev ENV
+is set on entry to a shell, or is set in the
+.Pa .profile
+of a login shell, the shell next reads
+commands from the file named in
+.Ev ENV .
+Therefore, a user should place commands that are to be executed only at
+login time in the
+.Pa .profile
+file, and commands that are executed for every shell inside the
+.Ev ENV
+file.
+To set the
+.Ev ENV
+variable to some file, place the following line in your
+.Pa .profile
+of your home directory
+.Pp
+.Dl ENV=$HOME/.shinit; export ENV
+.Pp
+substituting for
+.Dq .shinit
+any filename you wish.
+Since the
+.Ev ENV
+file is read for every invocation of the shell, including shell scripts
+and non-interactive shells, the following paradigm is useful for
+restricting commands in the
+.Ev ENV
+file to interactive invocations.
+Place commands within the
+.Dq case
+and
+.Dq esac
+below (these commands are described later):
+.Pp
+.Bl -item -compact -offset indent
+.It
+.Li case $- in *i*)
+.Bl -item -compact -offset indent
+.It
+.Li # commands for interactive use only
+.It
+.Li ...
+.El
+.It
+.Li esac
+.El
+.Pp
+If command line arguments besides the options have been specified, then
+the shell treats the first argument as the name of a file from which to
+read commands (a shell script), and the remaining arguments are set as the
+positional parameters of the shell ($1, $2, etc).
+Otherwise, the shell
+reads commands from its standard input.
+.Ss Argument List Processing
+All of the single letter options have a corresponding name that can be
+used as an argument to the
+.Fl o
+option.
+The set
+.Fl o
+name is provided next to the single letter option in
+the description below.
+Specifying a dash
+.Dq -
+turns the option on, while using a plus
+.Dq +
+disables the option.
+The following options can be set from the command line or
+with the
+.Ic set
+builtin (described later).
+.Bl -tag -width aaaallexportfoo -offset indent
+.It Fl a Em allexport
+Export all variables assigned to.
+.It Fl c
+Read commands from the
+.Ar command_string
+operand instead of from the standard input.
+Special parameter 0 will be set from the
+.Ar command_name
+operand and the positional parameters ($1, $2, etc.)
+set from the remaining argument operands.
+.It Fl C Em noclobber
+Don't overwrite existing files with
+.Dq \*[Gt] .
+.It Fl e Em errexit
+If not interactive, exit immediately if any untested command fails.
+The exit status of a command is considered to be
+explicitly tested if the command is used to control an
+.Ic if ,
+.Ic elif ,
+.Ic while ,
+or
+.Ic until ;
+or if the command is the left hand operand of an
+.Dq \*[Am]\*[Am]
+or
+.Dq ||
+operator.
+.It Fl f Em noglob
+Disable pathname expansion.
+.It Fl n Em noexec
+If not interactive, read commands but do not execute them.
+This is useful for checking the syntax of shell scripts.
+.It Fl u Em nounset
+Write a message to standard error when attempting to expand a variable
+that is not set, and if the shell is not interactive, exit immediately.
+.It Fl v Em verbose
+The shell writes its input to standard error as it is read.
+Useful for debugging.
+.It Fl x Em xtrace
+Write each command to standard error (preceded by a
+.Sq +\ )
+before it is executed.
+Useful for debugging.
+.It Fl q Em quietprofile
+If the
+.Fl v
+or
+.Fl x
+options have been set, do not apply them when reading
+initialization files, these being
+.Pa /etc/profile ,
+.Pa .profile ,
+and the file specified by the
+.Ev ENV
+environment variable.
+.It Fl I Em ignoreeof
+Ignore EOF's from input when interactive.
+.It Fl i Em interactive
+Force the shell to behave interactively.
+.It Fl m Em monitor
+Turn on job control (set automatically when interactive).
+.It Fl s Em stdin
+Read commands from standard input (set automatically if no file arguments
+are present).
+This option has no effect when set after the shell has
+already started running (i.e. with
+.Ic set ) .
+.It Fl V Em vi
+Enable the built-in
+.Xr vi 1
+command line editor (disables
+.Fl E
+if it has been set).
+(See the
+.Sx Command Line Editing
+section below.)
+.It Fl E Em emacs
+Enable the built-in emacs style
+command line editor (disables
+.Fl V
+if it has been set).
+(See the
+.Sx Command Line Editing
+section below.)
+.It Fl b Em notify
+Enable asynchronous notification of background job completion.
+(UNIMPLEMENTED for 4.4alpha)
+.It "\ \ " Em cdprint
+Make an interactive shell always print the new directory name when
+changed by the
+.Ic cd
+command.
+.It "\ \ " Em tabcomplete
+Enables filename completion in the command line editor.
+Typing a tab character will extend the current input word to match a
+filename.
+If more than one filename matches it is only extended to be the common prefix.
+Typing a second tab character will list all the matching names.
+.El
+.Ss Lexical Structure
+The shell reads input in terms of lines from a file and breaks it up into
+words at whitespace (blanks and tabs), and at certain sequences of
+characters that are special to the shell called
+.Dq operators .
+There are two types of operators: control operators and redirection
+operators (their meaning is discussed later).
+Following is a list of operators:
+.Bl -ohang -offset indent
+.It "Control operators:"
+.Dl \*[Am] \*[Am]\*[Am] \&( \&) \&; ;; | || \*[Lt]newline\*[Gt]
+.It "Redirection operators:"
+.Dl \*[Lt] \*[Gt] \*[Gt]| \*[Lt]\*[Lt] \*[Gt]\*[Gt] \*[Lt]\*[Am] \*[Gt]\*[Am] \*[Lt]\*[Lt]- \*[Lt]\*[Gt]
+.El
+.Ss Quoting
+Quoting is used to remove the special meaning of certain characters or
+words to the shell, such as operators, whitespace, or keywords.
+There are three types of quoting: matched single quotes,
+matched double quotes, and backslash.
+.Ss Backslash
+A backslash preserves the literal meaning of the following
+character, with the exception of
+.Aq newline .
+A backslash preceding a
+.Aq newline
+is treated as a line continuation.
+.Ss Single Quotes
+Enclosing characters in single quotes preserves the literal meaning of all
+the characters (except single quotes, making it impossible to put
+single-quotes in a single-quoted string).
+.Ss Double Quotes
+Enclosing characters within double quotes preserves the literal
+meaning of all characters except dollarsign
+.Pq $ ,
+backquote
+.Pq ` ,
+and backslash
+.Pq \e .
+The backslash inside double quotes is historically weird, and serves to
+quote only the following characters:
+.Dl $ ` \*q \e \*[Lt]newline\*[Gt] .
+Otherwise it remains literal.
+.Ss Reserved Words
+Reserved words are words that have special meaning to the
+shell and are recognized at the beginning of a line and
+after a control operator.
+The following are reserved words:
+.Bl -column while while while while while -offset indent
+.It ! Ta elif Ta fi Ta while Ta case
+.It else Ta for Ta then Ta { Ta }
+.It do Ta done Ta until Ta if Ta esac
+.El
+.Pp
+Their meaning is discussed later.
+.Ss Aliases
+An alias is a name and corresponding value set using the
+.Ic alias
+builtin command.
+Whenever a reserved word may occur (see above),
+and after checking for reserved words, the shell
+checks the word to see if it matches an alias.
+If it does, it replaces it in the input stream with its value.
+For example, if there is an alias called
+.Dq lf
+with the value
+.Dq "ls -F" ,
+then the input:
+.Pp
+.Dl lf foobar Aq return
+.Pp
+would become
+.Pp
+.Dl ls -F foobar Aq return
+.Pp
+Aliases provide a convenient way for naive users to create shorthands for
+commands without having to learn how to create functions with arguments.
+They can also be used to create lexically obscure code.
+This use is discouraged.
+.Ss Commands
+The shell interprets the words it reads according to a language, the
+specification of which is outside the scope of this man page (refer to the
+BNF in the
+.Tn POSIX
+1003.2 document).
+Essentially though, a line is read and if the first
+word of the line (or after a control operator) is not a reserved word,
+then the shell has recognized a simple command.
+Otherwise, a complex
+command or some other special construct may have been recognized.
+.Ss Simple Commands
+If a simple command has been recognized, the shell performs
+the following actions:
+.Bl -enum -offset indent
+.It
+Leading words of the form
+.Dq name=value
+are stripped off and assigned to the environment of the simple command.
+Redirection operators and their arguments (as described below) are
+stripped off and saved for processing.
+.It
+The remaining words are expanded as described in
+the section called
+.Dq Expansions ,
+and the first remaining word is considered the command name and the
+command is located.
+The remaining words are considered the arguments of the command.
+If no command name resulted, then the
+.Dq name=value
+variable assignments recognized in item 1 affect the current shell.
+.It
+Redirections are performed as described in the next section.
+.El
+.Ss Redirections
+Redirections are used to change where a command reads its input or sends
+its output.
+In general, redirections open, close, or duplicate an
+existing reference to a file.
+The overall format used for redirection is:
+.Pp
+.Dl [n] Va redir-op Ar file
+.Pp
+where
+.Va redir-op
+is one of the redirection operators mentioned previously.
+Following is a list of the possible redirections.
+The
+.Bq n
+is an optional number, as in
+.Sq 3
+(not
+.Sq Bq 3 ) ,
+that refers to a file descriptor.
+.Bl -tag -width aaabsfiles -offset indent
+.It [n] Ns \*[Gt] file
+Redirect standard output (or n) to file.
+.It [n] Ns \*[Gt]| file
+Same, but override the
+.Fl C
+option.
+.It [n] Ns \*[Gt]\*[Gt] file
+Append standard output (or n) to file.
+.It [n] Ns \*[Lt] file
+Redirect standard input (or n) from file.
+.It [n1] Ns \*[Lt]\*[Am] Ns n2
+Duplicate standard input (or n1) from file descriptor n2.
+.It [n] Ns \*[Lt]\*[Am]-
+Close standard input (or n).
+.It [n1] Ns \*[Gt]\*[Am] Ns n2
+Duplicate standard output (or n1) to n2.
+.It [n] Ns \*[Gt]\*[Am]-
+Close standard output (or n).
+.It [n] Ns \*[Lt]\*[Gt] file
+Open file for reading and writing on standard input (or n).
+.El
+.Pp
+The following redirection is often called a
+.Dq here-document .
+.Bl -item -offset indent
+.It
+.Li [n]\*[Lt]\*[Lt] delimiter
+.Dl here-doc-text ...
+.Li delimiter
+.El
+.Pp
+All the text on successive lines up to the delimiter is saved away and
+made available to the command on standard input, or file descriptor n if
+it is specified.
+If the delimiter as specified on the initial line is
+quoted, then the here-doc-text is treated literally, otherwise the text is
+subjected to parameter expansion, command substitution, and arithmetic
+expansion (as described in the section on
+.Dq Expansions ) .
+If the operator is
+.Dq \*[Lt]\*[Lt]-
+instead of
+.Dq \*[Lt]\*[Lt] ,
+then leading tabs in the here-doc-text are stripped.
+.Ss Search and Execution
+There are three types of commands: shell functions, builtin commands, and
+normal programs -- and the command is searched for (by name) in that order.
+They each are executed in a different way.
+.Pp
+When a shell function is executed, all of the shell positional parameters
+(except $0, which remains unchanged) are set to the arguments of the shell
+function.
+The variables which are explicitly placed in the environment of
+the command (by placing assignments to them before the function name) are
+made local to the function and are set to the values given.
+Then the command given in the function definition is executed.
+The positional parameters are restored to their original values
+when the command completes.
+This all occurs within the current shell.
+.Pp
+Shell builtins are executed internally to the shell, without spawning a
+new process.
+.Pp
+Otherwise, if the command name doesn't match a function or builtin, the
+command is searched for as a normal program in the file system (as
+described in the next section).
+When a normal program is executed, the shell runs the program,
+passing the arguments and the environment to the program.
+If the program is not a normal executable file (i.e., if it does
+not begin with the "magic number" whose
+.Tn ASCII
+representation is "#!", so
+.Xr execve 2
+returns
+.Er ENOEXEC
+then) the shell will interpret the program in a subshell.
+The child shell will reinitialize itself in this case,
+so that the effect will be as if a
+new shell had been invoked to handle the ad-hoc shell script, except that
+the location of hashed commands located in the parent shell will be
+remembered by the child.
+.Pp
+Note that previous versions of this document and the source code itself
+misleadingly and sporadically refer to a shell script without a magic
+number as a "shell procedure".
+.Ss Path Search
+When locating a command, the shell first looks to see if it has a shell
+function by that name.
+Then it looks for a builtin command by that name.
+If a builtin command is not found, one of two things happen:
+.Bl -enum
+.It
+Command names containing a slash are simply executed without performing
+any searches.
+.It
+The shell searches each entry in
+.Ev PATH
+in turn for the command.
+The value of the
+.Ev PATH
+variable should be a series of entries separated by colons.
+Each entry consists of a directory name.
+The current directory may be indicated
+implicitly by an empty directory name, or explicitly by a single period.
+.El
+.Ss Command Exit Status
+Each command has an exit status that can influence the behavior
+of other shell commands.
+The paradigm is that a command exits
+with zero for normal or success, and non-zero for failure,
+error, or a false indication.
+The man page for each command
+should indicate the various exit codes and what they mean.
+Additionally, the builtin commands return exit codes, as does
+an executed shell function.
+.Pp
+If a command consists entirely of variable assignments then the
+exit status of the command is that of the last command substitution
+if any, otherwise 0.
+.Ss Complex Commands
+Complex commands are combinations of simple commands with control
+operators or reserved words, together creating a larger complex command.
+More generally, a command is one of the following:
+.Bl -bullet
+.It
+simple command
+.It
+pipeline
+.It
+list or compound-list
+.It
+compound command
+.It
+function definition
+.El
+.Pp
+Unless otherwise stated, the exit status of a command is that of the last
+simple command executed by the command.
+.Ss Pipelines
+A pipeline is a sequence of one or more commands separated
+by the control operator |.
+The standard output of all but
+the last command is connected to the standard input
+of the next command.
+The standard output of the last
+command is inherited from the shell, as usual.
+.Pp
+The format for a pipeline is:
+.Pp
+.Dl [!] command1 [ | command2 ...]
+.Pp
+The standard output of command1 is connected to the standard input of
+command2.
+The standard input, standard output, or both of a command is
+considered to be assigned by the pipeline before any redirection specified
+by redirection operators that are part of the command.
+.Pp
+If the pipeline is not in the background (discussed later), the shell
+waits for all commands to complete.
+.Pp
+If the reserved word ! does not precede the pipeline, the exit status is
+the exit status of the last command specified in the pipeline.
+Otherwise, the exit status is the logical NOT of the exit status of the
+last command.
+That is, if the last command returns zero, the exit status
+is 1; if the last command returns greater than zero, the exit status is
+zero.
+.Pp
+Because pipeline assignment of standard input or standard output or both
+takes place before redirection, it can be modified by redirection.
+For example:
+.Pp
+.Dl $ command1 2\*[Gt]\*[Am]1 | command2
+.Pp
+sends both the standard output and standard error of command1
+to the standard input of command2.
+.Pp
+A ; or
+.Aq newline
+terminator causes the preceding AND-OR-list (described
+next) to be executed sequentially; a \*[Am] causes asynchronous execution of
+the preceding AND-OR-list.
+.Pp
+Note that unlike some other shells, each process in the pipeline is a
+child of the invoking shell (unless it is a shell builtin, in which case
+it executes in the current shell -- but any effect it has on the
+environment is wiped).
+.Ss Background Commands -- \*[Am]
+If a command is terminated by the control operator ampersand (\*[Am]), the
+shell executes the command asynchronously -- that is, the shell does not
+wait for the command to finish before executing the next command.
+.Pp
+The format for running a command in background is:
+.Pp
+.Dl command1 \*[Am] [command2 \*[Am] ...]
+.Pp
+If the shell is not interactive, the standard input of an asynchronous
+command is set to
+.Pa /dev/null .
+.Ss Lists -- Generally Speaking
+A list is a sequence of zero or more commands separated by newlines,
+semicolons, or ampersands, and optionally terminated by one of these three
+characters.
+The commands in a list are executed in the order they are written.
+If command is followed by an ampersand, the shell starts the
+command and immediately proceed onto the next command; otherwise it waits
+for the command to terminate before proceeding to the next one.
+.Ss Short-Circuit List Operators
+.Dq \*[Am]\*[Am]
+and
+.Dq ||
+are AND-OR list operators.
+.Dq \*[Am]\*[Am]
+executes the first command, and then executes the second command if and only
+if the exit status of the first command is zero.
+.Dq ||
+is similar, but executes the second command if and only if the exit status
+of the first command is nonzero.
+.Dq \*[Am]\*[Am]
+and
+.Dq ||
+both have the same priority.
+Note that these operators are left-associative, so
+.Dq true || echo bar && echo baz
+writes
+.Dq baz
+and nothing else.
+This is not the way it works in C.
+.Ss Flow-Control Constructs -- if, while, for, case
+The syntax of the if command is
+.Bd -literal -offset indent
+if list
+then list
+[ elif list
+then list ] ...
+[ else list ]
+fi
+.Ed
+.Pp
+The syntax of the while command is
+.Bd -literal -offset indent
+while list
+do list
+done
+.Ed
+.Pp
+The two lists are executed repeatedly while the exit status of the
+first list is zero.
+The until command is similar, but has the word
+until in place of while, which causes it to
+repeat until the exit status of the first list is zero.
+.Pp
+The syntax of the for command is
+.Bd -literal -offset indent
+for variable in word ...
+do list
+done
+.Ed
+.Pp
+The words are expanded, and then the list is executed repeatedly with the
+variable set to each word in turn.
+do and done may be replaced with
+.Dq {
+and
+.Dq } .
+.Pp
+The syntax of the break and continue command is
+.Bd -literal -offset indent
+break [ num ]
+continue [ num ]
+.Ed
+.Pp
+Break terminates the num innermost for or while loops.
+Continue continues with the next iteration of the innermost loop.
+These are implemented as builtin commands.
+.Pp
+The syntax of the case command is
+.Bd -literal -offset indent
+case word in
+pattern) list ;;
+\&...
+esac
+.Ed
+.Pp
+The pattern can actually be one or more patterns (see
+.Sx Shell Patterns
+described later), separated by
+.Dq \*(Ba
+characters.
+.Ss Grouping Commands Together
+Commands may be grouped by writing either
+.Pp
+.Dl (list)
+.Pp
+or
+.Pp
+.Dl { list; }
+.Pp
+The first of these executes the commands in a subshell.
+Builtin commands grouped into a (list) will not affect the current shell.
+The second form does not fork another shell so is slightly more efficient.
+Grouping commands together this way allows you to redirect
+their output as though they were one program:
+.Pp
+.Bd -literal -offset indent
+{ echo -n \*q hello \*q ; echo \*q world" ; } \*[Gt] greeting
+.Ed
+.Pp
+Note that
+.Dq }
+must follow a control operator (here,
+.Dq \&; )
+so that it is recognized as a reserved word and not as another command argument.
+.Ss Functions
+The syntax of a function definition is
+.Pp
+.Dl name ( ) command
+.Pp
+A function definition is an executable statement; when executed it
+installs a function named name and returns an exit status of zero.
+The command is normally a list enclosed between
+.Dq {
+and
+.Dq } .
+.Pp
+Variables may be declared to be local to a function by using a local
+command.
+This should appear as the first statement of a function, and the syntax is
+.Pp
+.Dl local [ variable | - ] ...
+.Pp
+Local is implemented as a builtin command.
+.Pp
+When a variable is made local, it inherits the initial value and exported
+and readonly flags from the variable with the same name in the surrounding
+scope, if there is one.
+Otherwise, the variable is initially unset.
+The shell uses dynamic scoping, so that if you make the variable x local to
+function f, which then calls function g, references to the variable x made
+inside g will refer to the variable x declared inside f, not to the global
+variable named x.
+.Pp
+The only special parameter that can be made local is
+.Dq - .
+Making
+.Dq -
+local any shell options that are changed via the set command inside the
+function to be restored to their original values when the function
+returns.
+.Pp
+The syntax of the return command is
+.Pp
+.Dl return [ exitstatus ]
+.Pp
+It terminates the currently executing function.
+Return is implemented as a builtin command.
+.Ss Variables and Parameters
+The shell maintains a set of parameters.
+A parameter denoted by a name is called a variable.
+When starting up, the shell turns all the environment
+variables into shell variables.
+New variables can be set using the form
+.Pp
+.Dl name=value
+.Pp
+Variables set by the user must have a name consisting solely of
+alphabetics, numerics, and underscores - the first of which must not be
+numeric.
+A parameter can also be denoted by a number or a special
+character as explained below.
+.Ss Positional Parameters
+A positional parameter is a parameter denoted by a number (n \*[Gt] 0).
+The shell sets these initially to the values of its command line arguments
+that follow the name of the shell script.
+The
+.Ic set
+builtin can also be used to set or reset them.
+.Ss Special Parameters
+A special parameter is a parameter denoted by one of the following special
+characters.
+The value of the parameter is listed next to its character.
+.Bl -tag -width thinhyphena
+.It *
+Expands to the positional parameters, starting from one.
+When the
+expansion occurs within a double-quoted string it expands to a single
+field with the value of each parameter separated by the first character of
+the
+.Ev IFS
+variable, or by a
+.Aq space
+if
+.Ev IFS
+is unset.
+.It @
+Expands to the positional parameters, starting from one.
+When the expansion occurs within double-quotes, each positional
+parameter expands as a separate argument.
+If there are no positional parameters, the
+expansion of @ generates zero arguments, even when @ is
+double-quoted.
+What this basically means, for example, is
+if $1 is
+.Dq abc
+and $2 is
+.Dq def ghi ,
+then
+.Qq $@
+expands to
+the two arguments:
+.Pp
+.Sm off
+.Dl \*q abc \*q \ \*q def\ ghi \*q
+.Sm on
+.It #
+Expands to the number of positional parameters.
+.It \&?
+Expands to the exit status of the most recent pipeline.
+.It - (Hyphen.)
+Expands to the current option flags (the single-letter
+option names concatenated into a string) as specified on
+invocation, by the set builtin command, or implicitly
+by the shell.
+.It $
+Expands to the process ID of the invoked shell.
+A subshell retains the same value of $ as its parent.
+.It \&!
+Expands to the process ID of the most recent background
+command executed from the current shell.
+For a pipeline, the process ID is that of the last command in the pipeline.
+.It 0 (Zero.)
+Expands to the name of the shell or shell script.
+.El
+.Ss Word Expansions
+This clause describes the various expansions that are performed on words.
+Not all expansions are performed on every word, as explained later.
+.Pp
+Tilde expansions, parameter expansions, command substitutions, arithmetic
+expansions, and quote removals that occur within a single word expand to a
+single field.
+It is only field splitting or pathname expansion that can
+create multiple fields from a single word.
+The single exception to this
+rule is the expansion of the special parameter @ within double-quotes, as
+was described above.
+.Pp
+The order of word expansion is:
+.Bl -enum
+.It
+Tilde Expansion, Parameter Expansion, Command Substitution,
+Arithmetic Expansion (these all occur at the same time).
+.It
+Field Splitting is performed on fields
+generated by step (1) unless the
+.Ev IFS
+variable is null.
+.It
+Pathname Expansion (unless set
+.Fl f
+is in effect).
+.It
+Quote Removal.
+.El
+.Pp
+The $ character is used to introduce parameter expansion, command
+substitution, or arithmetic evaluation.
+.Ss Tilde Expansion (substituting a user's home directory)
+A word beginning with an unquoted tilde character (~) is
+subjected to tilde expansion.
+All the characters up to
+a slash (/) or the end of the word are treated as a username
+and are replaced with the user's home directory.
+If the username is missing (as in
+.Pa ~/foobar ) ,
+the tilde is replaced with the value of the
+.Va HOME
+variable (the current user's home directory).
+.Ss Parameter Expansion
+The format for parameter expansion is as follows:
+.Pp
+.Dl ${expression}
+.Pp
+where expression consists of all characters until the matching
+.Dq } .
+Any
+.Dq }
+escaped by a backslash or within a quoted string, and characters in
+embedded arithmetic expansions, command substitutions, and variable
+expansions, are not examined in determining the matching
+.Dq } .
+.Pp
+The simplest form for parameter expansion is:
+.Pp
+.Dl ${parameter}
+.Pp
+The value, if any, of parameter is substituted.
+.Pp
+The parameter name or symbol can be enclosed in braces, which are
+optional except for positional parameters with more than one digit or
+when parameter is followed by a character that could be interpreted as
+part of the name.
+If a parameter expansion occurs inside double-quotes:
+.Bl -enum
+.It
+Pathname expansion is not performed on the results of the expansion.
+.It
+Field splitting is not performed on the results of the
+expansion, with the exception of the special rules for @.
+.El
+.Pp
+In addition, a parameter expansion can be modified by using one of the
+following formats.
+.Bl -tag -width aaparameterwordaaaaa
+.It ${parameter:-word}
+Use Default Values.
+If parameter is unset or null, the expansion of word
+is substituted; otherwise, the value of parameter is substituted.
+.It ${parameter:=word}
+Assign Default Values.
+If parameter is unset or null, the expansion of
+word is assigned to parameter.
+In all cases, the final value of parameter is substituted.
+Only variables, not positional parameters or special
+parameters, can be assigned in this way.
+.It ${parameter:?[word]}
+Indicate Error if Null or Unset.
+If parameter is unset or null, the
+expansion of word (or a message indicating it is unset if word is omitted)
+is written to standard error and the shell exits with a nonzero exit status.
+Otherwise, the value of parameter is substituted.
+An interactive shell need not exit.
+.It ${parameter:+word}
+Use Alternative Value.
+If parameter is unset or null, null is
+substituted; otherwise, the expansion of word is substituted.
+.El
+.Pp
+In the parameter expansions shown previously, use of the colon in the
+format results in a test for a parameter that is unset or null; omission
+of the colon results in a test for a parameter that is only unset.
+.Bl -tag -width aaparameterwordaaaaa
+.It ${#parameter}
+String Length.
+The length in characters of the value of parameter.
+.El
+.Pp
+The following four varieties of parameter expansion provide for substring
+processing.
+In each case, pattern matching notation (see
+.Sx Shell Patterns ) ,
+rather than regular expression notation, is used to evaluate the patterns.
+If parameter is * or @, the result of the expansion is unspecified.
+Enclosing the full parameter expansion string in double-quotes does not
+cause the following four varieties of pattern characters to be quoted,
+whereas quoting characters within the braces has this effect.
+.Bl -tag -width aaparameterwordaaaaa
+.It ${parameter%word}
+Remove Smallest Suffix Pattern.
+The word is expanded to produce a pattern.
+The parameter expansion then results in parameter, with the
+smallest portion of the suffix matched by the pattern deleted.
+.It ${parameter%%word}
+Remove Largest Suffix Pattern.
+The word is expanded to produce a pattern.
+The parameter expansion then results in parameter, with the largest
+portion of the suffix matched by the pattern deleted.
+.It ${parameter#word}
+Remove Smallest Prefix Pattern.
+The word is expanded to produce a pattern.
+The parameter expansion then results in parameter, with the
+smallest portion of the prefix matched by the pattern deleted.
+.It ${parameter##word}
+Remove Largest Prefix Pattern.
+The word is expanded to produce a pattern.
+The parameter expansion then results in parameter, with the largest
+portion of the prefix matched by the pattern deleted.
+.El
+.Ss Command Substitution
+Command substitution allows the output of a command to be substituted in
+place of the command name itself.
+Command substitution occurs when the command is enclosed as follows:
+.Pp
+.Dl $(command)
+.Pp
+or
+.Po
+.Dq backquoted
+version
+.Pc :
+.Pp
+.Dl `command`
+.Pp
+The shell expands the command substitution by executing command in a
+subshell environment and replacing the command substitution with the
+standard output of the command, removing sequences of one or more
+.Ao newline Ac Ns s
+at the end of the substitution.
+(Embedded
+.Ao newline Ac Ns s
+before
+the end of the output are not removed; however, during field splitting,
+they may be translated into
+.Ao space Ac Ns s ,
+depending on the value of
+.Ev IFS
+and quoting that is in effect.)
+.Ss Arithmetic Expansion
+Arithmetic expansion provides a mechanism for evaluating an arithmetic
+expression and substituting its value.
+The format for arithmetic expansion is as follows:
+.Pp
+.Dl $((expression))
+.Pp
+The expression is treated as if it were in double-quotes, except
+that a double-quote inside the expression is not treated specially.
+The shell expands all tokens in the expression for parameter expansion,
+command substitution, and quote removal.
+.Pp
+Next, the shell treats this as an arithmetic expression and
+substitutes the value of the expression.
+.Ss White Space Splitting (Field Splitting)
+After parameter expansion, command substitution, and
+arithmetic expansion the shell scans the results of
+expansions and substitutions that did not occur in double-quotes for
+field splitting and multiple fields can result.
+.Pp
+The shell treats each character of the
+.Ev IFS
+as a delimiter and use the delimiters to split the results of parameter
+expansion and command substitution into fields.
+.Pp
+Non-whitespace characters in
+.Ev IFS
+are treated strictly as parameter terminators.
+So adjacent non-whitespace
+.Ev IFS
+characters will produce empty parameters.
+.Pp
+If
+.Ev IFS
+is unset it is assumed to contain space, tab, and newline.
+.Ss Pathname Expansion (File Name Generation)
+Unless the
+.Fl f
+flag is set, file name generation is performed after word splitting is
+complete.
+Each word is viewed as a series of patterns, separated by slashes.
+The process of expansion replaces the word with the names of all
+existing files whose names can be formed by replacing each pattern with a
+string that matches the specified pattern.
+There are two restrictions on
+this: first, a pattern cannot match a string containing a slash, and
+second, a pattern cannot match a string starting with a period unless the
+first character of the pattern is a period.
+The next section describes the
+patterns used for both Pathname Expansion and the
+.Ic case
+command.
+.Ss Shell Patterns
+A pattern consists of normal characters, which match themselves,
+and meta-characters.
+The meta-characters are
+.Dq \&! ,
+.Dq * ,
+.Dq \&? ,
+and
+.Dq \&[ .
+These characters lose their special meanings if they are quoted.
+When command or variable substitution is performed
+and the dollar sign or back quotes are not double quoted,
+the value of the variable or the output of
+the command is scanned for these characters and they are turned into
+meta-characters.
+.Pp
+An asterisk
+.Pq Dq *
+matches any string of characters.
+A question mark matches any single character.
+A left bracket
+.Pq Dq \&[
+introduces a character class.
+The end of the character class is indicated by a
+.Pq Dq \&] ;
+if the
+.Dq \&]
+is missing then the
+.Dq \&[
+matches a
+.Dq \&[
+rather than introducing a character class.
+A character class matches any of the characters between the square brackets.
+A range of characters may be specified using a minus sign.
+The character class may be complemented
+by making an exclamation point the first character of the character class.
+.Pp
+To include a
+.Dq \&]
+in a character class, make it the first character listed (after the
+.Dq \&! ,
+if any).
+To include a minus sign, make it the first or last character listed.
+.Ss Builtins
+This section lists the builtin commands which are builtin because they
+need to perform some operation that can't be performed by a separate
+process.
+In addition to these, there are several other commands that may
+be builtin for efficiency (e.g.
+.Xr printf 1 ,
+.Xr echo 1 ,
+.Xr test 1 ,
+etc).
+.Bl -tag -width 5n
+.It :
+A null command that returns a 0 (true) exit value.
+.It \&. file
+The commands in the specified file are read and executed by the shell.
+.It alias Op Ar name Ns Op Ar "=string ..."
+If
+.Ar name=string
+is specified, the shell defines the alias
+.Ar name
+with value
+.Ar string .
+If just
+.Ar name
+is specified, the value of the alias
+.Ar name
+is printed.
+With no arguments, the
+.Ic alias
+builtin prints the
+names and values of all defined aliases (see
+.Ic unalias ) .
+.It bg [ Ar job ] ...
+Continue the specified jobs (or the current job if no
+jobs are given) in the background.
+.It Xo command
+.Op Fl p
+.Op Fl v
+.Op Fl V
+.Ar command
+.Op Ar arg ...
+.Xc
+Execute the specified command but ignore shell functions when searching
+for it.
+(This is useful when you
+have a shell function with the same name as a builtin command.)
+.Bl -tag -width 5n
+.It Fl p
+search for command using a
+.Ev PATH
+that guarantees to find all the standard utilities.
+.It Fl V
+Do not execute the command but
+search for the command and print the resolution of the
+command search.
+This is the same as the type builtin.
+.It Fl v
+Do not execute the command but
+search for the command and print the absolute pathname
+of utilities, the name for builtins or the expansion of aliases.
+.El
+.It cd Op Ar directory Op Ar replace
+Switch to the specified directory (default
+.Ev $HOME ) .
+If
+.Ar replace
+is specified, then the new directory name is generated by replacing
+the first occurrence of
+.Ar directory
+in the current directory name with
+.Ar replace .
+Otherwise if an entry for
+.Ev CDPATH
+appears in the environment of the
+.Ic cd
+command or the shell variable
+.Ev CDPATH
+is set and the directory name does not begin with a slash, then the
+directories listed in
+.Ev CDPATH
+will be searched for the specified directory.
+The format of
+.Ev CDPATH
+is the same as that of
+.Ev PATH .
+In an interactive shell, the
+.Ic cd
+command will print out the name of the
+directory that it actually switched to if this is different from the name
+that the user gave.
+These may be different either because the
+.Ev CDPATH
+mechanism was used or because a symbolic link was crossed.
+.It eval Ar string ...
+Concatenate all the arguments with spaces.
+Then re-parse and execute the command.
+.It exec Op Ar command arg ...
+Unless command is omitted, the shell process is replaced with the
+specified program (which must be a real program, not a shell builtin or
+function).
+Any redirections on the
+.Ic exec
+command are marked as permanent, so that they are not undone when the
+.Ic exec
+command finishes.
+.It exit Op Ar exitstatus
+Terminate the shell process.
+If
+.Ar exitstatus
+is given it is used as the exit status of the shell; otherwise the
+exit status of the preceding command is used.
+.It export Ar name ...
+.It export Fl p
+The specified names are exported so that they will appear in the
+environment of subsequent commands.
+The only way to un-export a variable is to unset it.
+The shell allows the value of a variable to be set at the
+same time it is exported by writing
+.Pp
+.Dl export name=value
+.Pp
+With no arguments the export command lists the names of all exported variables.
+With the
+.Fl p
+option specified the output will be formatted suitably for non-interactive use.
+.It Xo fc Op Fl e Ar editor
+.Op Ar first Op Ar last
+.Xc
+.It Xo fc Fl l
+.Op Fl nr
+.Op Ar first Op Ar last
+.Xc
+.It Xo fc Fl s Op Ar old=new
+.Op Ar first
+.Xc
+The
+.Ic fc
+builtin lists, or edits and re-executes, commands previously entered
+to an interactive shell.
+.Bl -tag -width 5n
+.It Fl e No editor
+Use the editor named by editor to edit the commands.
+The editor string is a command name, subject to search via the
+.Ev PATH
+variable.
+The value in the
+.Ev FCEDIT
+variable is used as a default when
+.Fl e
+is not specified.
+If
+.Ev FCEDIT
+is null or unset, the value of the
+.Ev EDITOR
+variable is used.
+If
+.Ev EDITOR
+is null or unset,
+.Xr ed 1
+is used as the editor.
+.It Fl l No (ell)
+List the commands rather than invoking an editor on them.
+The commands are written in the sequence indicated by
+the first and last operands, as affected by
+.Fl r ,
+with each command preceded by the command number.
+.It Fl n
+Suppress command numbers when listing with -l.
+.It Fl r
+Reverse the order of the commands listed (with
+.Fl l )
+or edited (with neither
+.Fl l
+nor
+.Fl s ) .
+.It Fl s
+Re-execute the command without invoking an editor.
+.It first
+.It last
+Select the commands to list or edit.
+The number of previous commands that
+can be accessed are determined by the value of the
+.Ev HISTSIZE
+variable.
+The value of first or last or both are one of the following:
+.Bl -tag -width 5n
+.It [+]number
+A positive number representing a command number; command numbers can be
+displayed with the
+.Fl l
+option.
+.It Fl number
+A negative decimal number representing the command that was executed
+number of commands previously.
+For example, \-1 is the immediately previous command.
+.El
+.It string
+A string indicating the most recently entered command that begins with
+that string.
+If the old=new operand is not also specified with
+.Fl s ,
+the string form of the first operand cannot contain an embedded equal sign.
+.El
+.Pp
+The following environment variables affect the execution of fc:
+.Bl -tag -width HISTSIZE
+.It Ev FCEDIT
+Name of the editor to use.
+.It Ev HISTSIZE
+The number of previous commands that are accessible.
+.El
+.It fg Op Ar job
+Move the specified job or the current job to the foreground.
+.It getopts Ar optstring var
+The
+.Tn POSIX
+.Ic getopts
+command, not to be confused with the
+.Em Bell Labs
+-derived
+.Xr getopt 1 .
+.Pp
+The first argument should be a series of letters, each of which may be
+optionally followed by a colon to indicate that the option requires an
+argument.
+The variable specified is set to the parsed option.
+.Pp
+The
+.Ic getopts
+command deprecates the older
+.Xr getopt 1
+utility due to its handling of arguments containing whitespace.
+.Pp
+The
+.Ic getopts
+builtin may be used to obtain options and their arguments
+from a list of parameters.
+When invoked,
+.Ic getopts
+places the value of the next option from the option string in the list in
+the shell variable specified by
+.Va var
+and its index in the shell variable
+.Ev OPTIND .
+When the shell is invoked,
+.Ev OPTIND
+is initialized to 1.
+For each option that requires an argument, the
+.Ic getopts
+builtin will place it in the shell variable
+.Ev OPTARG .
+If an option is not allowed for in the
+.Va optstring ,
+then
+.Ev OPTARG
+will be unset.
+.Pp
+.Va optstring
+is a string of recognized option letters (see
+.Xr getopt 3 ) .
+If a letter is followed by a colon, the option is expected to have an
+argument which may or may not be separated from it by whitespace.
+If an option character is not found where expected,
+.Ic getopts
+will set the variable
+.Va var
+to a
+.Dq \&? ;
+.Ic getopts
+will then unset
+.Ev OPTARG
+and write output to standard error.
+By specifying a colon as the first character of
+.Va optstring
+all errors will be ignored.
+.Pp
+A nonzero value is returned when the last option is reached.
+If there are no remaining arguments,
+.Ic getopts
+will set
+.Va var
+to the special option,
+.Dq -- ,
+otherwise, it will set
+.Va var
+to
+.Dq \&? .
+.Pp
+The following code fragment shows how one might process the arguments
+for a command that can take the options
+.Op a
+and
+.Op b ,
+and the option
+.Op c ,
+which requires an argument.
+.Pp
+.Bd -literal -offset indent
+while getopts abc: f
+do
+ case $f in
+ a | b) flag=$f;;
+ c) carg=$OPTARG;;
+ \\?) echo $USAGE; exit 1;;
+ esac
+done
+shift `expr $OPTIND - 1`
+.Ed
+.Pp
+This code will accept any of the following as equivalent:
+.Pp
+.Bd -literal -offset indent
+cmd \-acarg file file
+cmd \-a \-c arg file file
+cmd \-carg -a file file
+cmd \-a \-carg \-\- file file
+.Ed
+.It hash Fl rv Ar command ...
+The shell maintains a hash table which remembers the
+locations of commands.
+With no arguments whatsoever,
+the
+.Ic hash
+command prints out the contents of this table.
+Entries which have not been looked at since the last
+.Ic cd
+command are marked with an asterisk; it is possible for these entries
+to be invalid.
+.Pp
+With arguments, the
+.Ic hash
+command removes the specified commands from the hash table (unless
+they are functions) and then locates them.
+With the
+.Fl v
+option, hash prints the locations of the commands as it finds them.
+The
+.Fl r
+option causes the hash command to delete all the entries in the hash table
+except for functions.
+.It inputrc Ar file
+Read the
+.Va file
+to set keybindings as defined by
+.Xr editrc 5 .
+.It jobid Op Ar job
+Print the process id's of the processes in the job.
+If the
+.Ar job
+argument is omitted, the current job is used.
+.It jobs
+This command lists out all the background processes
+which are children of the current shell process.
+.It pwd Op Fl LP
+Print the current directory.
+If
+.Fl L
+is specified the cached value (initially set from
+.Ev PWD )
+is checked to see if it refers to the current directory, if it does
+the value is printed.
+Otherwise the current directory name is found using
+.Xr getcwd(3) .
+The environment variable
+.Ev PWD
+is set to printed value.
+.Pp
+The default is
+.Ic pwd
+.Fl L ,
+but note that the builtin
+.Ic cd
+command doesn't currently support
+.Fl L
+or
+.Fl P
+and will cache (almost) the absolute path.
+If
+.Ic cd
+is changed,
+.Ic pwd
+may be changed to default to
+.Ic pwd
+.Fl P .
+.Pp
+If the current directory is renamed and replaced by a symlink to the
+same directory, or the initial
+.Ev PWD
+value followed a symbolic link, then the cached value may not
+be the absolute path.
+.Pp
+The builtin command may differ from the program of the same name because
+the program will use
+.Ev PWD
+and the builtin uses a separately cached value.
+.It Xo read Op Fl p Ar prompt
+.Op Fl r
+.Ar variable
+.Op Ar ...
+.Xc
+The prompt is printed if the
+.Fl p
+option is specified and the standard input is a terminal.
+Then a line is read from the standard input.
+The trailing newline is deleted from the
+line and the line is split as described in the section on word splitting
+above, and the pieces are assigned to the variables in order.
+If there are more pieces than variables, the remaining pieces
+(along with the characters in
+.Ev IFS
+that separated them) are assigned to the last variable.
+If there are more variables than pieces,
+the remaining variables are assigned the null string.
+The
+.Ic read
+builtin will indicate success unless EOF is encountered on input, in
+which case failure is returned.
+.Pp
+By default, unless the
+.Fl r
+option is specified, the backslash
+.Dq \e
+acts as an escape character, causing the following character to be treated
+literally.
+If a backslash is followed by a newline, the backslash and the
+newline will be deleted.
+.It readonly Ar name ...
+.It readonly Fl p
+The specified names are marked as read only, so that they cannot be
+subsequently modified or unset.
+The shell allows the value of a variable
+to be set at the same time it is marked read only by writing
+.Pp
+.Dl readonly name=value
+.Pp
+With no arguments the readonly command lists the names of all read only
+variables.
+With the
+.Fl p
+option specified the output will be formatted suitably for non-interactive use.
+.Pp
+.It Xo set
+.Oo {
+.Fl options | Cm +options | Cm -- }
+.Oc Ar arg ...
+.Xc
+The
+.Ic set
+command performs three different functions.
+.Pp
+With no arguments, it lists the values of all shell variables.
+.Pp
+If options are given, it sets the specified option
+flags, or clears them as described in the section called
+.Sx Argument List Processing .
+.Pp
+The third use of the set command is to set the values of the shell's
+positional parameters to the specified args.
+To change the positional
+parameters without changing any options, use
+.Dq --
+as the first argument to set.
+If no args are present, the set command
+will clear all the positional parameters (equivalent to executing
+.Dq shift $# . )
+.It setvar Ar variable Ar value
+Assigns value to variable.
+(In general it is better to write
+variable=value rather than using
+.Ic setvar .
+.Ic setvar
+is intended to be used in
+functions that assign values to variables whose names are passed as
+parameters.)
+.It shift Op Ar n
+Shift the positional parameters n times.
+A
+.Ic shift
+sets the value of
+.Va $1
+to the value of
+.Va $2 ,
+the value of
+.Va $2
+to the value of
+.Va $3 ,
+and so on, decreasing
+the value of
+.Va $#
+by one.
+If there are zero positional parameters,
+.Ic shift
+does nothing.
+.It Xo trap
+.Op Fl l
+.Xc
+.It Xo trap
+.Op Ar action
+.Ar signal ...
+.Xc
+Cause the shell to parse and execute action when any of the specified
+signals are received.
+The signals are specified by signal number or as the name of the signal.
+If
+.Ar signal
+is
+.Li 0 ,
+the action is executed when the shell exits.
+.Ar action
+may be null, which cause the specified signals to be ignored.
+With
+.Ar action
+omitted or set to `-' the specified signals are set to their default action.
+When the shell forks off a subshell, it resets trapped (but not ignored)
+signals to the default action.
+The
+.Ic trap
+command has no effect on signals that were
+ignored on entry to the shell.
+Issuing
+.Ic trap
+with option
+.Ar -l
+will print a list of valid signal names.
+.Ic trap
+without any arguments cause it to write a list of signals and their
+associated action to the standard output in a format that is suitable
+as an input to the shell that achieves the same trapping results.
+.Pp
+Examples:
+.Pp
+.Dl trap
+.Pp
+List trapped signals and their corresponding action
+.Pp
+.Dl trap -l
+.Pp
+Print a list of valid signals
+.Pp
+.Dl trap '' INT QUIT tstp 30
+.Pp
+Ignore signals INT QUIT TSTP USR1
+.Pp
+.Dl trap date INT
+.Pp
+Print date upon receiving signal INT
+.It type Op Ar name ...
+Interpret each name as a command and print the resolution of the command
+search.
+Possible resolutions are:
+shell keyword, alias, shell builtin,
+command, tracked alias and not found.
+For aliases the alias expansion is
+printed; for commands and tracked aliases the complete pathname of the
+command is printed.
+.It ulimit Xo
+.Op Fl H \*(Ba Fl S
+.Op Fl a \*(Ba Fl tfdscmlpn Op Ar value
+.Xc
+Inquire about or set the hard or soft limits on processes or set new
+limits.
+The choice between hard limit (which no process is allowed to
+violate, and which may not be raised once it has been lowered) and soft
+limit (which causes processes to be signaled but not necessarily killed,
+and which may be raised) is made with these flags:
+.Bl -tag -width Fl
+.It Fl H
+set or inquire about hard limits
+.It Fl S
+set or inquire about soft limits.
+If neither
+.Fl H
+nor
+.Fl S
+is specified, the soft limit is displayed or both limits are set.
+If both are specified, the last one wins.
+.El
+.Pp
+.Bl -tag -width Fl
+The limit to be interrogated or set, then, is chosen by specifying
+any one of these flags:
+.It Fl a
+show all the current limits
+.It Fl b
+show or set the limit on the socket buffer size of a process (in bytes)
+.It Fl t
+show or set the limit on CPU time (in seconds)
+.It Fl f
+show or set the limit on the largest file that can be created
+(in 512-byte blocks)
+.It Fl d
+show or set the limit on the data segment size of a process (in kilobytes)
+.It Fl s
+show or set the limit on the stack size of a process (in kilobytes)
+.It Fl c
+show or set the limit on the largest core dump size that can be produced
+(in 512-byte blocks)
+.It Fl m
+show or set the limit on the total physical memory that can be
+in use by a process (in kilobytes)
+.It Fl l
+show or set the limit on how much memory a process can lock with
+.Xr mlock 2
+(in kilobytes)
+.It Fl p
+show or set the limit on the number of processes this user can
+have at one time
+.It Fl n
+show or set the limit on the number of files a process can have open at once
+.El
+.Pp
+If none of these is specified, it is the limit on file size that is shown
+or set.
+If value is specified, the limit is set to that number; otherwise
+the current limit is displayed.
+.Pp
+Limits of an arbitrary process can be displayed or set using the
+.Xr sysctl 8
+utility.
+.Pp
+.It umask Op Ar mask
+Set the value of umask (see
+.Xr umask 2 )
+to the specified octal value.
+If the argument is omitted, the umask value is printed.
+.It unalias Xo
+.Op Fl a
+.Op Ar name
+.Xc
+If
+.Ar name
+is specified, the shell removes that alias.
+If
+.Fl a
+is specified, all aliases are removed.
+.It unset Ar name ...
+The specified variables and functions are unset and unexported.
+If a given name corresponds to both a variable and a function, both
+the variable and the function are unset.
+.It wait Op Ar job
+Wait for the specified job to complete and return the exit status of the
+last process in the job.
+If the argument is omitted, wait for all jobs to
+complete and then return an exit status of zero.
+.El
+.Ss Command Line Editing
+When
+.Nm
+is being used interactively from a terminal, the current command
+and the command history (see
+.Ic fc
+in
+.Sx Builtins )
+can be edited using emacs-mode or vi-mode command-line editing.
+The command
+.Ql set -o emacs
+enables emacs-mode editing.
+The command
+.Ql set -o vi
+enables vi-mode editing and places sh into vi insert mode.
+(See the
+.Sx Argument List Processing
+section above.)
+.Pp
+The vi mode uses commands similar to a subset of those described in the
+.Xr vi 1
+man page.
+With vi-mode
+enabled, sh can be switched between insert mode and command mode.
+It's similar to vi: typing
+.Aq ESC
+will throw you into command VI command mode.
+Hitting
+.Aq return
+while in command mode will pass the line to the shell.
+.Pp
+The emacs mode uses commands similar to a subset available in
+the emacs editor.
+With emacs-mode enabled, special keys can be used to modify the text
+in the buffer using the control key.
+.Pp
+.Nm
+uses the
+.Xr editline 3
+library.
+.Sh EXIT STATUS
+Errors that are detected by the shell, such as a syntax error, will cause the
+shell to exit with a non-zero exit status.
+If the shell is not an
+interactive shell, the execution of the shell file will be aborted.
+Otherwise
+the shell will return the exit status of the last command executed, or
+if the exit builtin is used with a numeric argument, it will return the
+argument.
+.Sh ENVIRONMENT
+.Bl -tag -width MAILCHECK
+.It Ev HOME
+Set automatically by
+.Xr login 1
+from the user's login directory in the password file
+.Pq Xr passwd 5 .
+This environment variable also functions as the default argument for the
+cd builtin.
+.It Ev PATH
+The default search path for executables.
+See the above section
+.Sx Path Search .
+.It Ev CDPATH
+The search path used with the cd builtin.
+.It Ev LANG
+The string used to specify localization information that allows users
+to work with different culture-specific and language conventions.
+See
+.Xr nls 7 .
+.It Ev MAIL
+The name of a mail file, that will be checked for the arrival of new mail.
+Overridden by
+.Ev MAILPATH .
+.It Ev MAILCHECK
+The frequency in seconds that the shell checks for the arrival of mail
+in the files specified by the
+.Ev MAILPATH
+or the
+.Ev MAIL
+file.
+If set to 0, the check will occur at each prompt.
+.It Ev MAILPATH
+A colon
+.Dq \&:
+separated list of file names, for the shell to check for incoming mail.
+This environment setting overrides the
+.Ev MAIL
+setting.
+There is a maximum of 10 mailboxes that can be monitored at once.
+.It Ev PS1
+The primary prompt string, which defaults to
+.Dq $ \ ,
+unless you are the superuser, in which case it defaults to
+.Dq # \ .
+.It Ev PS2
+The secondary prompt string, which defaults to
+.Dq \*[Gt] \ .
+.It Ev PS4
+Output before each line when execution trace (set -x) is enabled,
+defaults to
+.Dq + \ .
+.It Ev IFS
+Input Field Separators.
+This is normally set to
+.Aq space ,
+.Aq tab ,
+and
+.Aq newline .
+See the
+.Sx White Space Splitting
+section for more details.
+.It Ev TERM
+The default terminal setting for the shell.
+This is inherited by
+children of the shell, and is used in the history editing modes.
+.It Ev HISTSIZE
+The number of lines in the history buffer for the shell.
+.El
+.Sh FILES
+.Bl -item -width HOMEprofilexxxx
+.It
+.Pa $HOME/.profile
+.It
+.Pa /etc/profile
+.El
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr echo 1 ,
+.Xr getopt 1 ,
+.Xr ksh 1 ,
+.Xr login 1 ,
+.Xr printf 1 ,
+.Xr test 1 ,
+.Xr editline 3 ,
+.Xr getopt 3 ,
+.\" .Xr profile 4 ,
+.Xr editrc 5 ,
+.Xr passwd 5 ,
+.Xr environ 7 ,
+.Xr nls 7 ,
+.Xr sysctl 8
+.Sh HISTORY
+A
+.Nm
+command appeared in
+.At v1 .
+It was, however, unmaintainable so we wrote this one.
+.Sh BUGS
+Setuid shell scripts should be avoided at all costs, as they are a
+significant security risk.
+.Pp
+PS1, PS2, and PS4 should be subject to parameter expansion before
+being displayed.
+.Pp
+The characters generated by filename completion should probably be quoted
+to ensure that the filename is still valid after the input line has been
+processed.
diff --git a/src/kash/shell.h b/src/kash/shell.h
new file mode 100644
index 0000000..9a234cb
--- /dev/null
+++ b/src/kash/shell.h
@@ -0,0 +1,102 @@
+/* $NetBSD: shell.h,v 1.17 2003/08/07 09:05:38 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)shell.h 8.2 (Berkeley) 5/4/95
+ */
+
+/*
+ * The follow should be set to reflect the type of system you have:
+ * JOBS -> 1 if you have Berkeley job control, 0 otherwise.
+ * SHORTNAMES -> 1 if your linker cannot handle long names.
+ * define BSD if you are running 4.2 BSD or later.
+ * define SYSV if you are running under System V.
+ * define DEBUG=1 to compile in debugging ('set -o debug' to turn on)
+ * define DEBUG=2 to compile in and turn on debugging.
+ * define DO_SHAREDVFORK to indicate that vfork(2) shares its address
+ * with its parent.
+ *
+ * When debugging is on, debugging info will be written to ./trace and
+ * a quit signal will generate a core dump.
+ */
+
+#ifndef ___shell_h
+#define ___shell_h
+
+#ifndef _MSC_VER
+# include <sys/param.h>
+#endif
+
+#define JOBS 1
+#ifndef BSD
+# define BSD 1
+#endif
+
+#if 0
+#ifndef DO_SHAREDVFORK
+# if __NetBSD_Version__ >= 104000000
+# define DO_SHAREDVFORK
+# endif
+#endif
+#endif
+
+typedef void *pointer;
+#ifndef NULL
+# define NULL (void *)0
+#endif
+#define STATIC /* empty */
+#define MKINIT /* empty */
+
+#ifdef HAVE_SYS_CDEFS_H
+# include <sys/cdefs.h>
+#endif
+
+extern char nullstr[1]; /* null string */
+
+
+#ifdef DEBUG
+# define TRACE(param) trace param
+# define TRACEV(param) tracev param
+#else
+# define TRACE(param)
+# define TRACEV(param)
+#endif
+
+#include "shtypes.h"
+
+#ifndef SH_FORKED_MODE
+# ifndef KASH_USE_FORKSHELL2
+# define KASH_USE_FORKSHELL2
+# endif
+#endif
+
+#endif
diff --git a/src/kash/shfile.c b/src/kash/shfile.c
new file mode 100644
index 0000000..78f5332
--- /dev/null
+++ b/src/kash/shfile.c
@@ -0,0 +1,2656 @@
+/* $Id: shfile.c 3542 2022-01-29 01:36:00Z bird $ */
+/** @file
+ *
+ * File management.
+ *
+ * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "shfile.h"
+#include "shinstance.h" /* TRACE2 */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#if K_OS == K_OS_WINDOWS
+# include <ntstatus.h>
+# define WIN32_NO_STATUS
+# include <Windows.h>
+# if !defined(_WIN32_WINNT)
+# define _WIN32_WINNT 0x0502 /* Windows Server 2003 */
+# endif
+# include <winternl.h> //NTSTATUS
+#else
+# include <unistd.h>
+# include <fcntl.h>
+# include <dirent.h>
+#endif
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+/** @def SHFILE_IN_USE
+ * Whether the file descriptor table stuff is actually in use or not.
+ */
+#if K_OS == K_OS_WINDOWS \
+ || K_OS == K_OS_OPENBSD /* because of ugly pthread library pipe hacks */ \
+ || !defined(SH_FORKED_MODE)
+# define SHFILE_IN_USE
+#endif
+/** The max file table size. */
+#define SHFILE_MAX 1024
+/** The file table growth rate. */
+#define SHFILE_GROW 64
+/** The min native unix file descriptor. */
+#define SHFILE_UNIX_MIN_FD 32
+/** The path buffer size we use. */
+#define SHFILE_MAX_PATH 4096
+
+/** Set errno and return. Doing a trace in debug build. */
+#define RETURN_ERROR(rc, err, msg) \
+ do { \
+ TRACE2((NULL, "%s: " ## msg ## " - returning %d / %d\n", __FUNCTION__, (rc), (err))); \
+ errno = (err); \
+ return (rc); \
+ } while (0)
+
+#if K_OS == K_OS_WINDOWS
+ /* See msdos.h for description. */
+# define FOPEN 0x01
+# define FEOFLAG 0x02
+# define FCRLF 0x04
+# define FPIPE 0x08
+# define FNOINHERIT 0x10
+# define FAPPEND 0x20
+# define FDEV 0x40
+# define FTEXT 0x80
+
+# define MY_ObjectBasicInformation 0
+# define MY_FileNamesInformation 12
+
+typedef struct
+{
+ ULONG Attributes;
+ ACCESS_MASK GrantedAccess;
+ ULONG HandleCount;
+ ULONG PointerCount;
+ ULONG PagedPoolUsage;
+ ULONG NonPagedPoolUsage;
+ ULONG Reserved[3];
+ ULONG NameInformationLength;
+ ULONG TypeInformationLength;
+ ULONG SecurityDescriptorLength;
+ LARGE_INTEGER CreateTime;
+} MY_OBJECT_BASIC_INFORMATION;
+
+#if 0
+typedef struct
+{
+ union
+ {
+ LONG Status;
+ PVOID Pointer;
+ };
+ ULONG_PTR Information;
+} MY_IO_STATUS_BLOCK;
+#else
+typedef IO_STATUS_BLOCK MY_IO_STATUS_BLOCK;
+#endif
+typedef MY_IO_STATUS_BLOCK *PMY_IO_STATUS_BLOCK;
+
+typedef struct
+{
+ ULONG NextEntryOffset;
+ ULONG FileIndex;
+ ULONG FileNameLength;
+ WCHAR FileName[1];
+} MY_FILE_NAMES_INFORMATION, *PMY_FILE_NAMES_INFORMATION;
+
+typedef NTSTATUS (NTAPI * PFN_NtQueryObject)(HANDLE, int, void *, size_t, size_t *);
+typedef NTSTATUS (NTAPI * PFN_NtQueryDirectoryFile)(HANDLE, HANDLE, void *, void *, PMY_IO_STATUS_BLOCK, void *,
+ ULONG, int, int, PUNICODE_STRING, int);
+typedef NTSTATUS (NTAPI * PFN_RtlUnicodeStringToAnsiString)(PANSI_STRING, PCUNICODE_STRING, int);
+
+
+#endif /* K_OS_WINDOWS */
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#if K_OS == K_OS_WINDOWS
+static int g_shfile_globals_initialized = 0;
+static PFN_NtQueryObject g_pfnNtQueryObject = NULL;
+static PFN_NtQueryDirectoryFile g_pfnNtQueryDirectoryFile = NULL;
+static PFN_RtlUnicodeStringToAnsiString g_pfnRtlUnicodeStringToAnsiString = NULL;
+# ifdef KASH_ASYNC_CLOSE_HANDLE
+/** Data for the asynchronous CloseHandle machinery. */
+static struct shfileasyncclose
+{
+ /** Mutex protecting the asynchronous CloseHandle stuff. */
+ shmtx mtx;
+ /** Handle to event that the closer-threads are waiting on. */
+ HANDLE evt_workers;
+ /** The ring buffer read index (for closer-threads). */
+ unsigned volatile idx_read;
+ /** The ring buffer write index (for shfile_native_close).
+ * When idx_read and idx_write are the same, the ring buffer is empty. */
+ unsigned volatile idx_write;
+ /** Number of handles currently being pending closure (handles + current
+ * CloseHandle calls). */
+ unsigned volatile num_pending;
+ /** Set if the threads should terminate. */
+ KBOOL volatile terminate_threads;
+ /** Set if one or more shell threads have requested evt_sync to be signalled
+ * when there are no more pending requests. */
+ KBOOL volatile signal_sync;
+ /** Number of threads that have been spawned. */
+ KU8 num_threads;
+ /** Handle to event that the shell threads are waiting on to sync. */
+ HANDLE evt_sync;
+ /** Ring buffer containing handles to be closed. */
+ HANDLE handles[32];
+ /** Worker threads doing the asynchronous closing. */
+ HANDLE threads[8];
+} g_shfile_async_close;
+# endif
+#endif /* K_OS_WINDOWS */
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifdef SHFILE_IN_USE
+# if K_OS == K_OS_WINDOWS
+static HANDLE shfile_set_inherit_win(shfile *pfd, int set);
+# endif
+#endif
+
+
+#ifdef SHFILE_IN_USE
+
+# ifdef DEBUG
+# if K_OS == K_OS_WINDOWS
+static KU64 shfile_nano_ts(void)
+{
+ static KBOOL volatile s_has_factor = K_FALSE;
+ static double volatile s_factor;
+ double factor;
+ LARGE_INTEGER now;
+ if (s_has_factor)
+ factor = s_factor;
+ else
+ {
+ QueryPerformanceFrequency(&now);
+ s_factor = factor = (double)1000000000.0 / now.QuadPart;
+ s_has_factor = K_TRUE;
+ }
+ QueryPerformanceCounter(&now);
+ return (KU64)(now.QuadPart * factor);
+}
+# endif /* K_OS_WINDOWS */
+# endif /* DEBUG */
+
+# if K_OS == K_OS_WINDOWS && defined(KASH_ASYNC_CLOSE_HANDLE)
+/**
+ * The closer thread function.
+ */
+static unsigned __stdcall shfile_async_close_handle_thread(void *ignored)
+{
+ KBOOL decrement_pending = K_FALSE;
+ shthread_set_name("Async CloseHandle");
+ while (!g_shfile_async_close.terminate_threads)
+ {
+ HANDLE toclose;
+ unsigned idx;
+ shmtxtmp tmp;
+
+ /*
+ * Grab a handle if there is one:
+ */
+ shmtx_enter(&g_shfile_async_close.mtx, &tmp);
+
+ if (decrement_pending)
+ {
+ kHlpAssert(g_shfile_async_close.num_pending > 0);
+ g_shfile_async_close.num_pending -= 1;
+ }
+
+ idx = g_shfile_async_close.idx_read % K_ELEMENTS(g_shfile_async_close.handles);
+ if (idx != g_shfile_async_close.idx_write % K_ELEMENTS(g_shfile_async_close.handles))
+ {
+ kHlpAssert(g_shfile_async_close.num_pending > 0);
+ toclose = g_shfile_async_close.handles[idx];
+ kHlpAssert(toclose);
+ g_shfile_async_close.handles[idx] = NULL;
+ g_shfile_async_close.idx_read = (idx + 1) % K_ELEMENTS(g_shfile_async_close.handles);
+ }
+ else
+ {
+ /* Signal waiters if requested and we've reached zero pending requests. */
+ if (g_shfile_async_close.signal_sync && g_shfile_async_close.num_pending == 0)
+ {
+ BOOL rc = SetEvent(g_shfile_async_close.evt_sync);
+ kHlpAssert(rc);
+ g_shfile_async_close.signal_sync = FALSE;
+ }
+ toclose = NULL;
+ }
+ shmtx_leave(&g_shfile_async_close.mtx, &tmp);
+
+ /* Do the closing if we have something to close, otherwise wait for work to arrive. */
+ if (toclose != NULL)
+ {
+ BOOL rc = CloseHandle(toclose);
+ kHlpAssert(rc);
+ decrement_pending = K_TRUE;
+ }
+ else
+ {
+ DWORD dwRet = WaitForSingleObject(g_shfile_async_close.evt_workers, 10000 /*ms*/);
+ kHlpAssert(dwRet == WAIT_OBJECT_0 || dwRet == WAIT_TIMEOUT);
+ decrement_pending = K_FALSE;
+ }
+ }
+
+ K_NOREF(ignored);
+ return 0;
+}
+
+/**
+ * Try submit a handle for automatic closing.
+ *
+ * @returns K_TRUE if submitted successfully, K_FALSE if not.
+ */
+static KBOOL shfile_async_close_submit(HANDLE toclose)
+{
+ KBOOL ret;
+ unsigned idx;
+ unsigned idx_next;
+ unsigned idx_read;
+ unsigned num_pending = 0;
+ unsigned num_threads = 0;
+ shmtxtmp tmp;
+ shmtx_enter(&g_shfile_async_close.mtx, &tmp);
+
+ /* Get the write index and check that there is a free slot in the buffer: */
+ idx = g_shfile_async_close.idx_write % K_ELEMENTS(g_shfile_async_close.handles);
+ idx_next = (idx + 1) % K_ELEMENTS(g_shfile_async_close.handles);
+ idx_read = g_shfile_async_close.idx_read % K_ELEMENTS(g_shfile_async_close.handles);
+ if (idx_next != idx_read)
+ {
+ /* Write the handle to the ring buffer: */
+ kHlpAssert(g_shfile_async_close.handles[idx] == NULL);
+ g_shfile_async_close.handles[idx] = toclose;
+ g_shfile_async_close.idx_write = idx_next;
+
+ num_pending = g_shfile_async_close.num_pending + 1;
+ g_shfile_async_close.num_pending = num_pending;
+
+ ret = SetEvent(g_shfile_async_close.evt_workers);
+ kHlpAssert(ret);
+ if (ret)
+ {
+ /* If we have more pending requests than threads, create a new thread. */
+ num_threads = g_shfile_async_close.num_threads;
+ if (num_pending > num_threads && num_threads < K_ELEMENTS(g_shfile_async_close.threads))
+ {
+ int const savederrno = errno;
+ unsigned tid = 0;
+ intptr_t hThread = _beginthreadex(NULL /*security*/, 0 /*stack_size*/, shfile_async_close_handle_thread,
+ NULL /*arg*/, 0 /*initflags*/, &tid);
+ kHlpAssert(hThread != -1);
+ if (hThread != -1)
+ {
+ g_shfile_async_close.threads[num_threads] = (HANDLE)hThread;
+ g_shfile_async_close.num_threads = ++num_threads;
+ }
+ else
+ {
+ TRACE2((NULL, "shfile_async_close_submit: _beginthreadex failed: %d\n", errno));
+ if (num_threads == 0)
+ ret = K_FALSE;
+ }
+ errno = savederrno;
+ }
+ }
+ else
+ TRACE2((NULL, "shfile_async_close_submit: SetEvent(%p) failed: %u\n", g_shfile_async_close.evt_workers, GetLastError()));
+
+ /* cleanup on failure. */
+ if (ret)
+ { /* likely */ }
+ else
+ {
+ g_shfile_async_close.handles[idx] = NULL;
+ g_shfile_async_close.idx_write = idx;
+ g_shfile_async_close.num_pending = num_pending - 1;
+ }
+ }
+ else
+ ret = K_FALSE;
+
+ shmtx_leave(&g_shfile_async_close.mtx, &tmp);
+ TRACE2((NULL, "shfile_async_close_submit: toclose=%p idx=%d #pending=%u #thread=%u -> %d\n",
+ toclose, idx, num_pending, num_threads, ret));
+ return ret;
+}
+
+/**
+ * Wait for all pending CloseHandle calls to complete.
+ */
+void shfile_async_close_sync(void)
+{
+ shmtxtmp tmp;
+ shmtx_enter(&g_shfile_async_close.mtx, &tmp);
+
+ if (g_shfile_async_close.num_pending > 0)
+ {
+ DWORD dwRet;
+
+/** @todo help out? */
+ if (!g_shfile_async_close.signal_sync)
+ {
+ BOOL rc = ResetEvent(g_shfile_async_close.evt_sync);
+ kHlpAssert(rc); K_NOREF(rc);
+
+ g_shfile_async_close.signal_sync = K_TRUE;
+ }
+
+ shmtx_leave(&g_shfile_async_close.mtx, &tmp);
+
+ TRACE2((NULL, "shfile_async_close_sync: Calling WaitForSingleObject...\n"));
+ dwRet = WaitForSingleObject(g_shfile_async_close.evt_sync, 10000 /*ms*/);
+ kHlpAssert(dwRet == WAIT_OBJECT_0);
+ kHlpAssert(g_shfile_async_close.num_pending == 0);
+ TRACE2((NULL, "shfile_async_close_sync: WaitForSingleObject returned %u...\n", dwRet));
+ }
+ else
+ shmtx_leave(&g_shfile_async_close.mtx, &tmp);
+}
+
+# endif /* K_OS == K_OS_WINDOWS && defined(KASH_ASYNC_CLOSE_HANDLE) */
+
+/**
+ * Close the specified native handle.
+ *
+ * @param native The native file handle.
+ * @param file The file table entry if available.
+ * @param inheritable The inheritability of the handle on windows, K_FALSE elsewhere.
+ */
+static void shfile_native_close(intptr_t native, shfile *file, KBOOL inheritable)
+{
+# if K_OS == K_OS_WINDOWS
+# ifdef KASH_ASYNC_CLOSE_HANDLE
+ /*
+ * CloseHandle may take several milliseconds on NTFS after we've appended
+ * a few bytes to a file. When a script uses lots of 'echo line-text >> file'
+ * we end up executing very slowly, even if 'echo' is builtin and the statement
+ * requires no subshells to be spawned.
+ *
+ * So, detect problematic handles and do CloseHandle asynchronously. When
+ * executing a child process, we probably will have to make sure the CloseHandle
+ * operation has completed before we do ResumeThread on the child to make 100%
+ * sure we can't have any sharing conflicts or end up with incorrect CRT stat()
+ * results. Sharing conflicts are not a problem for builtin commands, and for
+ * stat we do not use the CRT code but ntstat.c/h and it seems to work fine
+ * (might be a tiny bit slower, so (TODO) might be worth reducing what we ask for).
+ *
+ * If child processes are spawned using handle inheritance and the handle in
+ * question is inheritable, we will have to fix the inheriability before pushing
+ * on the async-close queue. This shouldn't have the CloseHandle issues.
+ */
+ if ( file
+ && (file->shflags & (SHFILE_FLAGS_DIRTY | SHFILE_FLAGS_TYPE_MASK))
+ == (SHFILE_FLAGS_DIRTY | SHFILE_FLAGS_FILE))
+ {
+ if (inheritable)
+ native = (intptr_t)shfile_set_inherit_win(file, 0);
+ if (shfile_async_close_submit((HANDLE)native))
+ return;
+ }
+
+ /*
+ * Otherwise close it right here:
+ */
+# endif
+ {
+# ifdef DEBUG
+ KU64 ns = shfile_nano_ts();
+ BOOL fRc = CloseHandle((HANDLE)native);
+ kHlpAssert(fRc); K_NOREF(fRc);
+ ns = shfile_nano_ts() - ns;
+ if (ns > 1000000)
+ TRACE2((NULL, "shfile_native_close: %u ns %p oflags=%#x %s\n",
+ ns, native, file ? file->oflags : 0, file ? file->dbgname : NULL));
+# else
+ BOOL fRc = CloseHandle((HANDLE)native);
+ kHlpAssert(fRc); K_NOREF(fRc);
+# endif
+ }
+
+# else /* K_OS != K_OS_WINDOWS */
+ int s = errno;
+ close(native);
+ errno = s;
+# endif /* K_OS != K_OS_WINDOWS */
+ K_NOREF(file);
+}
+
+/**
+ * Grows the descriptor table, making sure that it can hold @a fdMin,
+ *
+ * @returns The max(fdMin, fdFirstNew) on success, -1 on failure.
+ * @param pfdtab The table to grow.
+ * @param fdMin Grow to include this index.
+ */
+static int shfile_grow_tab_locked(shfdtab *pfdtab, int fdMin)
+{
+ /*
+ * Grow the descriptor table.
+ */
+ int fdRet = -1;
+ shfile *new_tab;
+ int new_size = pfdtab->size + SHFILE_GROW;
+ while (new_size < fdMin)
+ new_size += SHFILE_GROW;
+ TRACE2((NULL, "shfile_grow_tab_locked: old %p / %d entries; new size: %d\n", pfdtab->tab, pfdtab->size, new_size));
+ new_tab = sh_realloc(shthread_get_shell(), pfdtab->tab, new_size * sizeof(shfile));
+ if (new_tab)
+ {
+ int i;
+ for (i = pfdtab->size; i < new_size; i++)
+ {
+ new_tab[i].fd = -1;
+ new_tab[i].oflags = 0;
+ new_tab[i].shflags = 0;
+ new_tab[i].native = -1;
+# ifdef DEBUG
+ new_tab[i].dbgname = NULL;
+# endif
+ }
+
+ fdRet = pfdtab->size;
+ if (fdRet < fdMin)
+ fdRet = fdMin;
+
+ pfdtab->tab = new_tab;
+ pfdtab->size = new_size;
+
+ TRACE2((NULL, "shfile_grow_tab_locked: new %p / %d entries\n", pfdtab->tab, pfdtab->size));
+ }
+
+ return fdRet;
+}
+
+/**
+ * Inserts the file into the descriptor table.
+ *
+ * If we're out of memory and cannot extend the table, we'll close the
+ * file, set errno to EMFILE and return -1.
+ *
+ * @returns The file descriptor number. -1 and errno on failure.
+ * @param pfdtab The file descriptor table.
+ * @param native The native file handle.
+ * @param oflags The flags the it was opened/created with.
+ * @param shflags The shell file flags.
+ * @param fdMin The minimum file descriptor number, pass -1 if any is ok.
+ * @param who Who we're doing this for (for logging purposes).
+ * @param dbgname The filename, if applicable/available.
+ */
+static int shfile_insert(shfdtab *pfdtab, intptr_t native, unsigned oflags, unsigned shflags, int fdMin,
+ const char *who, const char *dbgname)
+{
+ shmtxtmp tmp;
+ int fd;
+ int i;
+
+ /*
+ * Fend of bad stuff.
+ */
+ if (fdMin >= SHFILE_MAX)
+ {
+ TRACE2((NULL, "shfile_insert: fdMin=%d is out of bounds; native=%p %s\n", fdMin, native, dbgname));
+ shfile_native_close(native, NULL, K_FALSE);
+ errno = EMFILE;
+ return -1;
+ }
+# if K_OS != K_OS_WINDOWS
+ if (fcntl((int)native, F_SETFD, fcntl((int)native, F_GETFD, 0) | FD_CLOEXEC) == -1)
+ {
+ int e = errno;
+ TRACE2((NULL, "shfile_insert: F_SETFD failed %d; native=%p %s\n", e, native, dbgname));
+ close((int)native);
+ errno = e;
+ return -1;
+ }
+# endif
+
+ shmtx_enter(&pfdtab->mtx, &tmp);
+
+ /*
+ * Search for a fitting unused location.
+ */
+ fd = -1;
+ for (i = fdMin >= 0 ? fdMin : 0; (unsigned)i < pfdtab->size; i++)
+ if (pfdtab->tab[i].fd == -1)
+ {
+ fd = i;
+ break;
+ }
+ if (fd == -1)
+ fd = shfile_grow_tab_locked(pfdtab, fdMin);
+
+ /*
+ * Fill in the entry if we've found one.
+ */
+ if (fd != -1)
+ {
+ pfdtab->tab[fd].fd = fd;
+ pfdtab->tab[fd].oflags = oflags;
+ pfdtab->tab[fd].shflags = shflags;
+ pfdtab->tab[fd].native = native;
+#ifdef DEBUG
+ pfdtab->tab[fd].dbgname = dbgname ? sh_strdup(NULL, dbgname) : NULL;
+#endif
+ TRACE2((NULL, "shfile_insert: #%d: native=%p oflags=%#x shflags=%#x %s\n", fd, native, oflags, shflags, dbgname));
+ }
+ else
+ shfile_native_close(native, NULL, K_FALSE);
+
+ shmtx_leave(&pfdtab->mtx, &tmp);
+
+ if (fd == -1)
+ errno = EMFILE;
+ (void)who;
+ return fd;
+}
+
+# if K_OS != K_OS_WINDOWS
+/**
+ * Makes a copy of the native file, closes the original, and inserts the copy
+ * into the descriptor table.
+ *
+ * If we're out of memory and cannot extend the table, we'll close the
+ * file, set errno to EMFILE and return -1.
+ *
+ * @returns The file descriptor number. -1 and errno on failure.
+ * @param pfdtab The file descriptor table.
+ * @param pnative The native file handle on input, -1 on output.
+ * @param oflags The flags the it was opened/created with.
+ * @param shflags The shell file flags.
+ * @param fdMin The minimum file descriptor number, pass -1 if any is ok.
+ * @param who Who we're doing this for (for logging purposes).
+ * @param dbgname The filename, if applicable/available.
+ */
+static int shfile_copy_insert_and_close(shfdtab *pfdtab, int *pnative, unsigned oflags, unsigned shflags, int fdMin,
+ const char *who, const char *dbgname)
+{
+ int fd = -1;
+ int s = errno;
+ int native_copy = fcntl(*pnative, F_DUPFD, SHFILE_UNIX_MIN_FD);
+ close(*pnative);
+ *pnative = -1;
+ errno = s;
+
+ if (native_copy != -1)
+ fd = shfile_insert(pfdtab, native_copy, oflags, shflags, fdMin, who, dbgname);
+ return fd;
+}
+# endif /* !K_OS_WINDOWS */
+
+/**
+ * Gets a file descriptor and lock the file descriptor table.
+ *
+ * @returns Pointer to the file and table ownership on success,
+ * NULL and errno set to EBADF on failure.
+ * @param pfdtab The file descriptor table.
+ * @param fd The file descriptor number.
+ * @param ptmp See shmtx_enter.
+ */
+static shfile *shfile_get(shfdtab *pfdtab, int fd, shmtxtmp *ptmp)
+{
+ shfile *file = NULL;
+ if ( fd >= 0
+ && (unsigned)fd < pfdtab->size)
+ {
+ shmtx_enter(&pfdtab->mtx, ptmp);
+ if ((unsigned)fd < pfdtab->size
+ && pfdtab->tab[fd].fd != -1)
+ file = &pfdtab->tab[fd];
+ else
+ shmtx_leave(&pfdtab->mtx, ptmp);
+ }
+ if (!file)
+ errno = EBADF;
+ return file;
+}
+
+/**
+ * Puts back a file descriptor and releases the table ownership.
+ *
+ * @param pfdtab The file descriptor table.
+ * @param file The file.
+ * @param ptmp See shmtx_leave.
+ */
+static void shfile_put(shfdtab *pfdtab, shfile *file, shmtxtmp *ptmp)
+{
+ shmtx_leave(&pfdtab->mtx, ptmp);
+ kHlpAssert(file);
+ (void)file;
+}
+
+/**
+ * Constructs a path from the current directory and the passed in path.
+ *
+ * @returns 0 on success, -1 and errno set to ENAMETOOLONG or EINVAL on failure.
+ *
+ * @param pfdtab The file descriptor table
+ * @param path The path the caller supplied.
+ * @param buf Where to put the path. This is assumed to be SHFILE_MAX_PATH
+ * chars in size.
+ */
+int shfile_make_path(shfdtab *pfdtab, const char *path, char *buf)
+{
+ size_t path_len = strlen(path);
+ if (path_len == 0)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+ if (path_len >= SHFILE_MAX_PATH)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ if ( *path == '/'
+# if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+ || *path == '\\'
+ || ( *path
+ && path[1] == ':'
+ && ( (*path >= 'A' && *path <= 'Z')
+ || (*path >= 'a' && *path <= 'z')))
+# endif
+ )
+ {
+ memcpy(buf, path, path_len + 1);
+ }
+ else
+ {
+ size_t cwd_len;
+ shmtxtmp tmp;
+
+ shmtx_enter(&pfdtab->mtx, &tmp);
+
+ cwd_len = strlen(pfdtab->cwd);
+ memcpy(buf, pfdtab->cwd, cwd_len);
+
+ shmtx_leave(&pfdtab->mtx, &tmp);
+
+ if (cwd_len + path_len + 1 >= SHFILE_MAX_PATH)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ if ( !cwd_len
+ || buf[cwd_len - 1] != '/')
+ buf[cwd_len++] = '/';
+ memcpy(buf + cwd_len, path, path_len + 1);
+ }
+
+# if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+ if (!strcmp(buf, "/dev/null"))
+ strcpy(buf, "NUL");
+# endif
+ return 0;
+}
+
+# if K_OS == K_OS_WINDOWS
+
+/**
+ * Adjusts the file name if it ends with a trailing directory slash.
+ *
+ * Windows APIs doesn't like trailing slashes.
+ *
+ * @returns 1 if it has a directory slash, 0 if not.
+ *
+ * @param abspath The path to adjust (SHFILE_MAX_PATH).
+ */
+static int shfile_trailing_slash_hack(char *abspath)
+{
+ /*
+ * Anything worth adjust here?
+ */
+ size_t path_len = strlen(abspath);
+ if ( path_len == 0
+ || ( abspath[path_len - 1] != '/'
+# if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+ && abspath[path_len - 1] != '\\'
+# endif
+ )
+ )
+ return 0;
+
+ /*
+ * Ok, make the adjustment.
+ */
+ if (path_len + 2 <= SHFILE_MAX_PATH)
+ {
+ /* Add a '.' to the end. */
+ abspath[path_len++] = '.';
+ abspath[path_len] = '\0';
+ }
+ else
+ {
+ /* No space for a dot, remove the slash if it's alone or just remove
+ one and add a dot like above. */
+ if ( abspath[path_len - 2] != '/'
+# if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+ && abspath[path_len - 2] != '\\'
+# endif
+ )
+ abspath[--path_len] = '\0';
+ else
+ abspath[path_len - 1] = '.';
+ }
+
+ return 1;
+}
+
+
+/**
+ * Converts a DOS(/Windows) error code to errno,
+ * assigning it to errno.
+ *
+ * @returns -1
+ * @param err The DOS error.
+ */
+static int shfile_dos2errno(int err)
+{
+ switch (err)
+ {
+ case ERROR_BAD_ENVIRONMENT: errno = E2BIG; break;
+ case ERROR_ACCESS_DENIED: errno = EACCES; break;
+ case ERROR_CURRENT_DIRECTORY: errno = EACCES; break;
+ case ERROR_LOCK_VIOLATION: errno = EACCES; break;
+ case ERROR_NETWORK_ACCESS_DENIED: errno = EACCES; break;
+ case ERROR_CANNOT_MAKE: errno = EACCES; break;
+ case ERROR_FAIL_I24: errno = EACCES; break;
+ case ERROR_DRIVE_LOCKED: errno = EACCES; break;
+ case ERROR_SEEK_ON_DEVICE: errno = EACCES; break;
+ case ERROR_NOT_LOCKED: errno = EACCES; break;
+ case ERROR_LOCK_FAILED: errno = EACCES; break;
+ case ERROR_NO_PROC_SLOTS: errno = EAGAIN; break;
+ case ERROR_MAX_THRDS_REACHED: errno = EAGAIN; break;
+ case ERROR_NESTING_NOT_ALLOWED: errno = EAGAIN; break;
+ case ERROR_INVALID_HANDLE: errno = EBADF; break;
+ case ERROR_INVALID_TARGET_HANDLE: errno = EBADF; break;
+ case ERROR_DIRECT_ACCESS_HANDLE: errno = EBADF; break;
+ case ERROR_WAIT_NO_CHILDREN: errno = ECHILD; break;
+ case ERROR_CHILD_NOT_COMPLETE: errno = ECHILD; break;
+ case ERROR_FILE_EXISTS: errno = EEXIST; break;
+ case ERROR_ALREADY_EXISTS: errno = EEXIST; break;
+ case ERROR_INVALID_FUNCTION: errno = EINVAL; break;
+ case ERROR_INVALID_ACCESS: errno = EINVAL; break;
+ case ERROR_INVALID_DATA: errno = EINVAL; break;
+ case ERROR_INVALID_PARAMETER: errno = EINVAL; break;
+ case ERROR_NEGATIVE_SEEK: errno = EINVAL; break;
+ case ERROR_TOO_MANY_OPEN_FILES: errno = EMFILE; break;
+ case ERROR_FILE_NOT_FOUND: errno = ENOENT; break;
+ case ERROR_PATH_NOT_FOUND: errno = ENOENT; break;
+ case ERROR_INVALID_DRIVE: errno = ENOENT; break;
+ case ERROR_NO_MORE_FILES: errno = ENOENT; break;
+ case ERROR_BAD_NETPATH: errno = ENOENT; break;
+ case ERROR_BAD_NET_NAME: errno = ENOENT; break;
+ case ERROR_BAD_PATHNAME: errno = ENOENT; break;
+ case ERROR_FILENAME_EXCED_RANGE: errno = ENOENT; break;
+ case ERROR_BAD_FORMAT: errno = ENOEXEC; break;
+ case ERROR_ARENA_TRASHED: errno = ENOMEM; break;
+ case ERROR_NOT_ENOUGH_MEMORY: errno = ENOMEM; break;
+ case ERROR_INVALID_BLOCK: errno = ENOMEM; break;
+ case ERROR_NOT_ENOUGH_QUOTA: errno = ENOMEM; break;
+ case ERROR_DISK_FULL: errno = ENOSPC; break;
+ case ERROR_DIR_NOT_EMPTY: errno = ENOTEMPTY; break;
+ case ERROR_BROKEN_PIPE: errno = EPIPE; break;
+ case ERROR_NOT_SAME_DEVICE: errno = EXDEV; break;
+ default: errno = EINVAL; break;
+ }
+ return -1;
+}
+
+/**
+ * Converts an NT status code to errno,
+ * assigning it to errno.
+ *
+ * @returns -1
+ * @param rcNt The NT status code.
+ */
+static int shfile_nt2errno(NTSTATUS rcNt)
+{
+ switch (rcNt)
+ {
+ default: errno = EINVAL; break;
+ }
+ return -1;
+}
+
+DWORD shfile_query_handle_access_mask(HANDLE h, PACCESS_MASK pMask)
+{
+ MY_OBJECT_BASIC_INFORMATION BasicInfo;
+ NTSTATUS rcNt;
+
+ if (!g_pfnNtQueryObject)
+ return ERROR_NOT_SUPPORTED;
+
+ rcNt = g_pfnNtQueryObject(h, MY_ObjectBasicInformation, &BasicInfo, sizeof(BasicInfo), NULL);
+ if (rcNt >= 0)
+ {
+ *pMask = BasicInfo.GrantedAccess;
+ return NO_ERROR;
+ }
+ if (rcNt != STATUS_INVALID_HANDLE)
+ return ERROR_GEN_FAILURE;
+ return ERROR_INVALID_HANDLE;
+}
+
+# endif /* K_OS == K_OS_WINDOWS */
+
+#endif /* SHFILE_IN_USE */
+
+/**
+ * Converts DOS slashes to UNIX slashes if necessary.
+ *
+ * @param pszPath The path to fix.
+ */
+K_INLINE void shfile_fix_slashes(char *pszPath)
+{
+#if K_OS == K_OS_WINDOWS || K_OS == K_OS_OS2
+ while ((pszPath = strchr(pszPath, '\\')))
+ *pszPath++ = '/';
+#else
+ (void)pszPath;
+#endif
+}
+
+/**
+ * Initializes the global variables in this file.
+ */
+static void shfile_init_globals(void)
+{
+#if K_OS == K_OS_WINDOWS
+ if (!g_shfile_globals_initialized)
+ {
+ HMODULE hNtDll = GetModuleHandle("NTDLL");
+ g_pfnNtQueryObject = (PFN_NtQueryObject) GetProcAddress(hNtDll, "NtQueryObject");
+ g_pfnNtQueryDirectoryFile = (PFN_NtQueryDirectoryFile)GetProcAddress(hNtDll, "NtQueryDirectoryFile");
+ g_pfnRtlUnicodeStringToAnsiString = (PFN_RtlUnicodeStringToAnsiString)GetProcAddress(hNtDll, "RtlUnicodeStringToAnsiString");
+ if ( !g_pfnRtlUnicodeStringToAnsiString
+ || !g_pfnNtQueryDirectoryFile)
+ {
+ /* fatal error */
+ }
+
+# ifdef KASH_ASYNC_CLOSE_HANDLE
+ /*
+ * Init the async CloseHandle state.
+ */
+ shmtx_init(&g_shfile_async_close.mtx);
+ g_shfile_async_close.evt_workers = CreateEventW(NULL, FALSE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pwszName*/);
+ g_shfile_async_close.evt_sync = CreateEventW(NULL, TRUE /*fManualReset*/, FALSE /*fInitialState*/, NULL /*pwszName*/);
+ if ( !g_shfile_async_close.evt_workers
+ || !g_shfile_async_close.evt_sync)
+ {
+ fprintf(stderr, "fatal error: CreateEventW failed: %u\n", GetLastError());
+ _exit(19);
+ }
+ g_shfile_async_close.idx_read = 0;
+ g_shfile_async_close.idx_write = 0;
+ g_shfile_async_close.num_pending = 0;
+ g_shfile_async_close.terminate_threads = K_FALSE;
+ g_shfile_async_close.signal_sync = K_FALSE;
+ g_shfile_async_close.num_threads = 0;
+ {
+ unsigned i = K_ELEMENTS(g_shfile_async_close.handles);
+ while (i-- > 0)
+ g_shfile_async_close.handles[i] = NULL;
+ i = K_ELEMENTS(g_shfile_async_close.threads);
+ while (i-- > 0)
+ g_shfile_async_close.threads[i] = NULL;
+ }
+# endif
+
+ g_shfile_globals_initialized = 1;
+ }
+#endif
+}
+
+/**
+ * Initializes a file descriptor table.
+ *
+ * @returns 0 on success, -1 and errno on failure.
+ * @param pfdtab The table to initialize.
+ * @param inherit File descriptor table to inherit from. If not specified
+ * we will inherit from the current process as it were.
+ */
+int shfile_init(shfdtab *pfdtab, shfdtab *inherit)
+{
+ int rc;
+
+ shfile_init_globals();
+
+ pfdtab->cwd = NULL;
+ pfdtab->size = 0;
+ pfdtab->tab = NULL;
+ rc = shmtx_init(&pfdtab->mtx);
+ if (!rc)
+ {
+#ifdef SHFILE_IN_USE
+ /* Get CWD with unix slashes. */
+ if (!inherit)
+ {
+ char buf[SHFILE_MAX_PATH];
+ if (getcwd(buf, sizeof(buf)))
+ {
+ shfile_fix_slashes(buf);
+ pfdtab->cwd = sh_strdup(NULL, buf);
+ }
+ if (pfdtab->cwd)
+ {
+# if K_OS == K_OS_WINDOWS
+ static const struct
+ {
+ DWORD dwStdHandle;
+ unsigned fFlags;
+ } aStdHandles[3] =
+ {
+ { STD_INPUT_HANDLE, _O_RDONLY },
+ { STD_OUTPUT_HANDLE, _O_WRONLY },
+ { STD_ERROR_HANDLE, _O_WRONLY }
+ };
+ int i;
+ STARTUPINFO Info;
+ ACCESS_MASK Mask;
+ DWORD dwErr;
+
+ rc = 0;
+
+ /* Try pick up the Visual C++ CRT file descriptor info. */
+ __try {
+ GetStartupInfo(&Info);
+ } __except (EXCEPTION_EXECUTE_HANDLER) {
+ memset(&Info, 0, sizeof(Info));
+ }
+
+ if ( Info.cbReserved2 > sizeof(int)
+ && (uintptr_t)Info.lpReserved2 >= 0x1000
+ && (i = *(int *)Info.lpReserved2) >= 1
+ && i <= 2048
+ && ( Info.cbReserved2 == i * 5 + 4
+ //|| Info.cbReserved2 == i * 5 + 1 - check the cygwin sources.
+ || Info.cbReserved2 == i * 9 + 4))
+ {
+ uint8_t *paf = (uint8_t *)Info.lpReserved2 + sizeof(int);
+ int dwPerH = 1 + (Info.cbReserved2 == i * 9 + 4);
+ DWORD *ph = (DWORD *)(paf + i) + dwPerH * i;
+ HANDLE aStdHandles2[3];
+ int j;
+
+ //if (Info.cbReserved2 == i * 5 + 1) - check the cygwin sources.
+ // i--;
+
+ for (j = 0; j < 3; j++)
+ aStdHandles2[j] = GetStdHandle(aStdHandles[j].dwStdHandle);
+
+ while (i-- > 0)
+ {
+ ph -= dwPerH;
+
+ if ( (paf[i] & (FOPEN | FNOINHERIT)) == FOPEN
+ && *ph != (uint32_t)INVALID_HANDLE_VALUE
+ && *ph != 0)
+ {
+ HANDLE h = (HANDLE)(intptr_t)*ph;
+ int fd2;
+ int fFlags;
+ int fFlags2;
+
+ if ( h == aStdHandles2[j = 0]
+ || h == aStdHandles2[j = 1]
+ || h == aStdHandles2[j = 2])
+ fFlags = aStdHandles[j].fFlags;
+ else
+ {
+ dwErr = shfile_query_handle_access_mask(h, &Mask);
+ if (dwErr == ERROR_INVALID_HANDLE)
+ continue;
+ if (dwErr == NO_ERROR)
+ {
+ fFlags = 0;
+ if ( (Mask & (GENERIC_READ | FILE_READ_DATA))
+ && (Mask & (GENERIC_WRITE | FILE_WRITE_DATA | FILE_APPEND_DATA)))
+ fFlags |= O_RDWR;
+ else if (Mask & (GENERIC_READ | FILE_READ_DATA))
+ fFlags |= O_RDONLY;
+ else if (Mask & (GENERIC_WRITE | FILE_WRITE_DATA | FILE_APPEND_DATA))
+ fFlags |= O_WRONLY;
+ else
+ fFlags |= O_RDWR;
+ if ((Mask & (FILE_WRITE_DATA | FILE_APPEND_DATA)) == FILE_APPEND_DATA)
+ fFlags |= O_APPEND;
+ }
+ else
+ fFlags = O_RDWR;
+ }
+
+ if (paf[i] & FPIPE)
+ fFlags2 = SHFILE_FLAGS_PIPE;
+ else if (paf[i] & FDEV)
+ fFlags2 = SHFILE_FLAGS_TTY;
+ else
+ fFlags2 = 0;
+
+ fd2 = shfile_insert(pfdtab, (intptr_t)h, fFlags, fFlags2, i, "shtab_init", NULL);
+ kHlpAssert(fd2 == i); (void)fd2;
+ if (fd2 != i)
+ rc = -1;
+ }
+ }
+ }
+
+ /* Check the three standard handles. */
+ for (i = 0; i < 3; i++)
+ if ( (unsigned)i >= pfdtab->size
+ || pfdtab->tab[i].fd == -1)
+ {
+ HANDLE hFile = GetStdHandle(aStdHandles[i].dwStdHandle);
+ if ( hFile != INVALID_HANDLE_VALUE
+ && hFile != NULL)
+ {
+ DWORD dwType = GetFileType(hFile);
+ unsigned fFlags = aStdHandles[i].fFlags;
+ unsigned fFlags2;
+ int fd2;
+ if (dwType == FILE_TYPE_CHAR)
+ fFlags2 = SHFILE_FLAGS_TTY;
+ else if (dwType == FILE_TYPE_PIPE)
+ fFlags2 = SHFILE_FLAGS_PIPE;
+ else
+ fFlags2 = SHFILE_FLAGS_FILE;
+ fd2 = shfile_insert(pfdtab, (intptr_t)hFile, fFlags, fFlags2, i, "shtab_init", NULL);
+ kHlpAssert(fd2 == i); (void)fd2;
+ if (fd2 != i)
+ rc = -1;
+ }
+ }
+# else
+ /*
+ * Annoying...
+ */
+ int fd;
+
+ for (fd = 0; fd < 10; fd++)
+ {
+ int oflags = fcntl(fd, F_GETFL, 0);
+ if (oflags != -1)
+ {
+ int cox = fcntl(fd, F_GETFD, 0);
+ struct stat st;
+ if ( cox != -1
+ && fstat(fd, &st) != -1)
+ {
+ int native;
+ int fd2;
+ int fFlags2 = 0;
+ if (cox & FD_CLOEXEC)
+ fFlags2 |= SHFILE_FLAGS_CLOSE_ON_EXEC;
+ if (S_ISREG(st.st_mode))
+ fFlags2 |= SHFILE_FLAGS_FILE;
+ else if (S_ISDIR(st.st_mode))
+ fFlags2 |= SHFILE_FLAGS_DIR;
+ else if (S_ISCHR(st.st_mode))
+ fFlags2 |= SHFILE_FLAGS_TTY;
+ else if (S_ISFIFO(st.st_mode))
+ fFlags2 |= SHFILE_FLAGS_PIPE;
+ else
+ fFlags2 |= SHFILE_FLAGS_TTY;
+
+ native = fcntl(fd, F_DUPFD, SHFILE_UNIX_MIN_FD);
+ if (native == -1)
+ native = fd;
+ fd2 = shfile_insert(pfdtab, native, oflags, fFlags2, fd, "shtab_init", NULL);
+ kHlpAssert(fd2 == fd); (void)fd2;
+ if (fd2 != fd)
+ rc = -1;
+ if (native != fd)
+ close(fd);
+ }
+ }
+ }
+
+# endif
+ }
+ else
+ rc = -1;
+ }
+ else
+ {
+ /*
+ * Inherit from parent shell's file table.
+ */
+ shfile const *src;
+ shfile *dst;
+ shmtxtmp tmp;
+ unsigned fdcount;
+ unsigned fd;
+
+ shmtx_enter(&inherit->mtx, &tmp);
+
+ /* allocate table and cwd: */
+ fdcount = inherit->size;
+ pfdtab->tab = dst = (shfile *)(fdcount ? sh_calloc(NULL, sizeof(pfdtab->tab[0]), fdcount) : NULL);
+ pfdtab->cwd = sh_strdup(NULL, inherit->cwd);
+ if ( pfdtab->cwd
+ && (pfdtab->tab || fdcount == 0))
+ {
+ /* duplicate table entries: */
+ for (fd = 0, src = inherit->tab; fd < fdcount; fd++, src++, dst++)
+ if (src->fd == -1)
+ dst->native = dst->fd = -1;
+ else
+ {
+# if K_OS == K_OS_WINDOWS
+# ifdef SH_FORKED_MODE
+ KBOOL const cox = !!(src->shflags & SHFILE_FLAGS_CLOSE_ON_EXEC);
+# else
+ KBOOL const cox = K_TRUE;
+# endif
+# endif
+ *dst = *src;
+# ifdef DEBUG
+ if (src->dbgname)
+ dst->dbgname = sh_strdup(NULL, src->dbgname);
+# endif
+# if K_OS == K_OS_WINDOWS
+ if (DuplicateHandle(GetCurrentProcess(),
+ (HANDLE)src->native,
+ GetCurrentProcess(),
+ (HANDLE *)&dst->native,
+ 0,
+ FALSE /* bInheritHandle */,
+ DUPLICATE_SAME_ACCESS))
+ TRACE2((NULL, "shfile_init: %d (%#x, %#x) %p (was %p)\n",
+ dst->fd, dst->oflags, dst->shflags, dst->native, src->native));
+ else
+ {
+ dst->native = (intptr_t)INVALID_HANDLE_VALUE;
+ rc = shfile_dos2errno(GetLastError());
+ TRACE2((NULL, "shfile_init: %d (%#x, %#x) %p - failed %d / %u!\n",
+ dst->fd, dst->oflags, dst->shflags, src->native, rc, GetLastError()));
+ break;
+ }
+
+# elif K_OS == K_OS_LINUX /* 2.6.27 / glibc 2.9 */ || K_OS == K_OS_FREEBSD /* 10.0 */ || K_OS == K_OS_NETBSD /* 6.0 */
+ dst->native = dup3(src->native, -1, cox ? O_CLOEXEC : 0);
+# else
+ if (cox)
+ {
+# ifndef SH_FORKED_MODE
+ shmtxtmp tmp2;
+ shmtx_enter(&global_exec_something, &tmp)
+# endif
+ dst->native = dup2(src->native, -1);
+ if (dst->native >= 0)
+ rc = fcntl(dst->native, F_SETFD, FD_CLOEXEC);
+# ifndef SH_FORKED_MODE
+ shmtx_leave(&global_exec_something, &tmp)
+# endif
+ if (rc != 0)
+ break;
+ }
+ else
+ dst->native = dup2(src->native, -1);
+ if (dst->native < 0)
+ {
+ rc = -1;
+ break;
+ }
+# endif
+ }
+ }
+ else
+ rc = -1;
+ pfdtab->size = fd;
+ shmtx_leave(&inherit->mtx, &tmp);
+ } /* inherit != NULL */
+#endif
+ }
+ return rc;
+}
+
+/**
+ * Deletes the file descriptor table.
+ *
+ * Safe to call more than once.
+ */
+void shfile_uninit(shfdtab *pfdtab, int tracefd)
+{
+ if (!pfdtab)
+ return;
+
+ if (pfdtab->tab)
+ {
+ unsigned left = pfdtab->size;
+ struct shfile *pfd = pfdtab->tab;
+ unsigned tracefdfound = 0;
+ while (left-- > 0)
+ {
+ if (pfd->fd != -1)
+ {
+ if (pfd->fd != tracefd)
+ {
+#if K_OS == K_OS_WINDOWS
+ BOOL rc = CloseHandle((HANDLE)pfd->native);
+ kHlpAssert(rc == TRUE); K_NOREF(rc);
+#else
+ int rc = close((int)pfd->native);
+ kHlpAssert(rc == 0); K_NOREF(rc);
+#endif
+ pfd->fd = -1;
+ pfd->native = -1;
+ }
+ else
+ tracefdfound++; /* there is only the one */
+ }
+ pfd++;
+ }
+
+ if (!tracefdfound)
+ { /* likely */ }
+ else
+ return;
+
+ sh_free(NULL, pfdtab->tab);
+ pfdtab->tab = NULL;
+ }
+
+ shmtx_delete(&pfdtab->mtx);
+
+ sh_free(NULL, pfdtab->cwd);
+ pfdtab->cwd = NULL;
+}
+
+#if K_OS == K_OS_WINDOWS && defined(SHFILE_IN_USE)
+
+/**
+ * Changes the inheritability of a file descriptor, taking console handles into
+ * account.
+ *
+ * @note This MAY change the native handle for the entry.
+ *
+ * @returns The native handle.
+ * @param pfd The file descriptor to change.
+ * @param set If set, make child processes inherit the handle, if clear
+ * make them not inherit it.
+ */
+static HANDLE shfile_set_inherit_win(shfile *pfd, int set)
+{
+ HANDLE hFile = (HANDLE)pfd->native;
+ if (!SetHandleInformation(hFile, HANDLE_FLAG_INHERIT, set ? HANDLE_FLAG_INHERIT : 0))
+ {
+ /* SetHandleInformation doesn't work for console handles,
+ so we have to duplicate the handle to change the
+ inheritability. */
+ DWORD err = GetLastError();
+ if ( err == ERROR_INVALID_PARAMETER
+ && DuplicateHandle(GetCurrentProcess(),
+ hFile,
+ GetCurrentProcess(),
+ &hFile,
+ 0,
+ set ? TRUE : FALSE /* bInheritHandle */,
+ DUPLICATE_SAME_ACCESS))
+ {
+ TRACE2((NULL, "shfile_set_inherit_win: %p -> %p (set=%d)\n", pfd->native, hFile, set));
+ if (!CloseHandle((HANDLE)pfd->native))
+ kHlpAssert(0);
+ pfd->native = (intptr_t)hFile;
+ }
+ else
+ {
+ err = GetLastError();
+ kHlpAssert(0);
+ hFile = (HANDLE)pfd->native;
+ }
+ }
+ return hFile;
+}
+
+# ifdef SH_FORKED_MODE
+/**
+ * Helper for shfork.
+ *
+ * @param pfdtab The file descriptor table.
+ * @param set Whether to make all handles inheritable (1) or
+ * to restore them to the rigth state (0).
+ * @param hndls Where to store the three standard handles.
+ */
+void shfile_fork_win(shfdtab *pfdtab, int set, intptr_t *hndls)
+{
+ shmtxtmp tmp;
+ unsigned i;
+
+ shmtx_enter(&pfdtab->mtx, &tmp);
+ TRACE2((NULL, "shfile_fork_win: set=%d\n", set));
+
+ i = pfdtab->size;
+ while (i-- > 0)
+ {
+ if (pfdtab->tab[i].fd == i)
+ {
+ shfile_set_inherit_win(&pfdtab->tab[i], set);
+ if (set)
+ TRACE2((NULL, " #%d: native=%#x oflags=%#x shflags=%#x\n",
+ i, pfdtab->tab[i].native, pfdtab->tab[i].oflags, pfdtab->tab[i].shflags));
+ }
+ }
+
+ if (hndls)
+ {
+ for (i = 0; i < 3; i++)
+ {
+ if ( pfdtab->size > i
+ && pfdtab->tab[i].fd == i)
+ hndls[i] = pfdtab->tab[i].native;
+ else
+ hndls[i] = (intptr_t)INVALID_HANDLE_VALUE;
+ TRACE2((NULL, "shfile_fork_win: i=%d size=%d fd=%d native=%d hndls[%d]=%p\n",
+ i, pfdtab->size, pfdtab->tab[i].fd, pfdtab->tab[i].native, i, hndls[i]));
+ }
+ }
+
+ shmtx_leave(&pfdtab->mtx, &tmp);
+}
+# endif /* SH_FORKED_MODE */
+
+/** shfile_exec_win helper that make sure there are no _O_APPEND handles. */
+static KBOOL shfile_exec_win_no_append(shfdtab *pfdtab, unsigned count)
+{
+ unsigned i;
+ for (i = 0; i < count; i++)
+ if ( (pfdtab->tab[i].oflags & _O_APPEND)
+ && (pfdtab->tab[i].oflags & (_O_WRONLY | _O_RDWR)))
+ return K_FALSE;
+ return K_TRUE;
+}
+
+/**
+ * Helper for sh_execve.
+ *
+ * This is called before and after CreateProcess. On the first call it
+ * will mark the non-close-on-exec handles as inheritable and produce
+ * the startup info for the CRT. On the second call, after CreateProcess,
+ * it will restore the handle inheritability properties.
+ *
+ * @returns 0 on success, non-zero on failure.
+ * @param pfdtab The file descriptor table.
+ * @param prepare Which call, 1 if before, 0 if after and success, -1 if after on failure.
+ * @param info The info structure.
+ */
+int shfile_exec_win(shfdtab *pfdtab, int prepare, shfdexecwin *info)
+{
+ STARTUPINFOA *strtinfo = (STARTUPINFOA *)info->strtinfo;
+ int rc = 0;
+ shmtxtmp tmp;
+ unsigned count;
+ unsigned i;
+
+ shmtx_enter(&pfdtab->mtx, &tmp);
+ TRACE2((NULL, "shfile_exec_win: prepare=%p\n", prepare));
+
+ count = pfdtab->size < (0x10000-4) / (1 + sizeof(HANDLE))
+ ? pfdtab->size
+ : (0x10000-4) / (1 + sizeof(HANDLE));
+ while ( count > 3
+ && ( pfdtab->tab[count - 1].fd == -1
+ || (pfdtab->tab[count - 1].shflags & SHFILE_FLAGS_CLOSE_ON_EXEC)))
+ count--;
+
+ if (prepare > 0)
+ {
+ if (count <= 3 && shfile_exec_win_no_append(pfdtab, count))
+ {
+ info->inherithandles = 0;
+ info->startsuspended = 1;
+ strtinfo->cbReserved2 = 0;
+ strtinfo->lpReserved2 = NULL;
+ }
+ else
+ {
+ size_t cbData = sizeof(int) + count * (1 + sizeof(HANDLE));
+ uint8_t *pbData = sh_malloc(shthread_get_shell(), cbData);
+ uint8_t *paf = pbData + sizeof(int);
+ HANDLE *pah = (HANDLE *)(paf + count);
+
+ info->inherithandles = 1;
+# ifdef KASH_ASYNC_CLOSE_HANDLE
+ info->startsuspended = g_shfile_async_close.num_pending > 0;
+# else
+ info->startsuspended = 0;
+# endif
+ strtinfo->cbReserved2 = (unsigned short)cbData;
+ strtinfo->lpReserved2 = pbData;
+
+# ifndef SH_FORKED_MODE
+ shmtx_leave(&pfdtab->mtx, &tmp); /* should be harmless as this isn't really necessary at all. */
+ shmtx_enter(&g_sh_exec_inherit_mtx, &info->tmp);
+ shmtx_enter(&pfdtab->mtx, &tmp);
+# endif
+
+ *(int *)pbData = count;
+
+ i = count;
+ while (i-- > 0)
+ {
+ if ( pfdtab->tab[i].fd == i
+ && !(pfdtab->tab[i].shflags & SHFILE_FLAGS_CLOSE_ON_EXEC))
+ {
+ HANDLE hFile = shfile_set_inherit_win(&pfdtab->tab[i], 1);
+ TRACE2((NULL, " #%d: native=%#x oflags=%#x shflags=%#x\n",
+ i, hFile, pfdtab->tab[i].oflags, pfdtab->tab[i].shflags));
+ paf[i] = FOPEN;
+ if (pfdtab->tab[i].oflags & _O_APPEND)
+ paf[i] |= FAPPEND;
+ if (pfdtab->tab[i].oflags & _O_TEXT)
+ paf[i] |= FTEXT;
+ switch (pfdtab->tab[i].shflags & SHFILE_FLAGS_TYPE_MASK)
+ {
+ case SHFILE_FLAGS_TTY: paf[i] |= FDEV; break;
+ case SHFILE_FLAGS_PIPE: paf[i] |= FPIPE; break;
+ }
+ pah[i] = hFile;
+ }
+ else
+ {
+ paf[i] = 0;
+ pah[i] = INVALID_HANDLE_VALUE;
+ }
+ }
+ }
+
+ for (i = 0; i < 3; i++)
+ {
+ if ( i < count
+ && pfdtab->tab[i].fd == i)
+ {
+ info->replacehandles[i] = 1;
+ info->handles[i] = pfdtab->tab[i].native;
+ }
+ else
+ {
+ info->replacehandles[i] = 0;
+ info->handles[i] = (intptr_t)INVALID_HANDLE_VALUE;
+ }
+ TRACE2((NULL, "shfile_exec_win: i=%d count=%d fd=%d native=%d hndls[%d]=\n",
+ i, count, pfdtab->tab[i].fd, pfdtab->tab[i].native, i, info->handles[i]));
+ }
+ }
+ else
+ {
+ shfile *file = pfdtab->tab;
+
+ sh_free(NULL, strtinfo->lpReserved2);
+ strtinfo->lpReserved2 = NULL;
+
+ i = count;
+ if (prepare == 0)
+ for (i = 0; i < count; i++, file++)
+ {
+ if ( file->fd == i
+ && !(file->shflags & SHFILE_FLAGS_TRACE))
+ {
+ shfile_native_close(file->native, file, info->inherithandles);
+
+ file->fd = -1;
+ file->oflags = 0;
+ file->shflags = 0;
+ file->native = -1;
+# ifdef DEBUG
+ sh_free(NULL, file->dbgname);
+ file->dbgname = NULL;
+# endif
+ }
+ }
+ else if (info->inherithandles)
+ for (i = 0; i < count; i++, file++)
+ if ( file->fd == i
+ && !(file->shflags & SHFILE_FLAGS_CLOSE_ON_EXEC))
+ shfile_set_inherit_win(file, 0);
+
+# ifndef SH_FORKED_MODE
+ if (info->inherithandles)
+ shmtx_leave(&g_sh_exec_inherit_mtx, &info->tmp);
+# endif
+ }
+
+ shmtx_leave(&pfdtab->mtx, &tmp);
+ return rc;
+}
+
+#endif /* K_OS_WINDOWS */
+
+#if K_OS != K_OS_WINDOWS
+/**
+ * Prepare file handles for inherting before a execve call.
+ *
+ * This is only used in the normal mode, so we've forked and need not worry
+ * about cleaning anything up after us. Nor do we need think about locking.
+ *
+ * @returns 0 on success, -1 on failure.
+ */
+int shfile_exec_unix(shfdtab *pfdtab)
+{
+ int rc = 0;
+# ifdef SHFILE_IN_USE
+ unsigned fd;
+
+ for (fd = 0; fd < pfdtab->size; fd++)
+ {
+ if ( pfdtab->tab[fd].fd != -1
+ && !(pfdtab->tab[fd].shflags & SHFILE_FLAGS_CLOSE_ON_EXEC) )
+ {
+ TRACE2((NULL, "shfile_exec_unix: %d => %d\n", pfdtab->tab[fd].native, fd));
+ if (dup2(pfdtab->tab[fd].native, fd) < 0)
+ {
+ /* fatal_error(NULL, "shfile_exec_unix: failed to move %d to %d", pfdtab->tab[fd].fd, fd); */
+ rc = -1;
+ }
+ }
+ }
+# endif
+ return rc;
+}
+#endif /* !K_OS_WINDOWS */
+
+/**
+ * open().
+ */
+int shfile_open(shfdtab *pfdtab, const char *name, unsigned flags, mode_t mode)
+{
+ int fd;
+#ifdef SHFILE_IN_USE
+ char absname[SHFILE_MAX_PATH];
+# if K_OS == K_OS_WINDOWS
+ HANDLE hFile;
+ DWORD dwDesiredAccess;
+ DWORD dwShareMode;
+ DWORD dwCreationDisposition;
+ DWORD dwFlagsAndAttributes;
+ SECURITY_ATTRIBUTES SecurityAttributes;
+
+# ifndef _O_ACCMODE
+# define _O_ACCMODE (_O_RDONLY|_O_WRONLY|_O_RDWR)
+# endif
+ switch (flags & (_O_ACCMODE | _O_APPEND))
+ {
+ case _O_RDONLY: dwDesiredAccess = GENERIC_READ; break;
+ case _O_RDONLY | _O_APPEND: dwDesiredAccess = GENERIC_READ; break;
+ case _O_WRONLY: dwDesiredAccess = GENERIC_WRITE; break;
+ case _O_WRONLY | _O_APPEND: dwDesiredAccess = (FILE_GENERIC_WRITE & ~FILE_WRITE_DATA); break;
+ case _O_RDWR: dwDesiredAccess = GENERIC_READ | GENERIC_WRITE; break;
+ case _O_RDWR | _O_APPEND: dwDesiredAccess = GENERIC_READ | (FILE_GENERIC_WRITE & ~FILE_WRITE_DATA); break;
+
+ default:
+ RETURN_ERROR(-1, EINVAL, "invalid mode");
+ }
+
+ dwShareMode = FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE;
+
+ SecurityAttributes.nLength = sizeof(SecurityAttributes);
+ SecurityAttributes.lpSecurityDescriptor = NULL;
+ SecurityAttributes.bInheritHandle = FALSE;
+
+ if (flags & _O_CREAT)
+ {
+ if ((flags & (_O_EXCL | _O_TRUNC)) == (_O_EXCL | _O_TRUNC))
+ RETURN_ERROR(-1, EINVAL, "_O_EXCL | _O_TRUNC");
+
+ if (flags & _O_TRUNC)
+ dwCreationDisposition = CREATE_ALWAYS; /* not 100%, but close enough */
+ else if (flags & _O_EXCL)
+ dwCreationDisposition = CREATE_NEW;
+ else
+ dwCreationDisposition = OPEN_ALWAYS;
+ }
+ else if (flags & _O_TRUNC)
+ dwCreationDisposition = TRUNCATE_EXISTING;
+ else
+ dwCreationDisposition = OPEN_EXISTING;
+
+ if (!(flags & _O_CREAT) || (mode & 0222))
+ dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
+ else
+ dwFlagsAndAttributes = FILE_ATTRIBUTE_READONLY;
+
+ fd = shfile_make_path(pfdtab, name, &absname[0]);
+ if (!fd)
+ {
+# ifdef DEBUG
+ KU64 ns = shfile_nano_ts();
+# endif
+ SetLastError(0);
+ hFile = CreateFileA(absname,
+ dwDesiredAccess,
+ dwShareMode,
+ &SecurityAttributes,
+ dwCreationDisposition,
+ dwFlagsAndAttributes,
+ NULL /* hTemplateFile */);
+# ifdef DEBUG
+ ns = shfile_nano_ts() - ns;
+ if (ns > 1000000)
+ TRACE2((NULL, "shfile_open: %u ns hFile=%p (%d) %s\n", ns, hFile, GetLastError(), absname));
+# endif
+ if (hFile != INVALID_HANDLE_VALUE)
+ fd = shfile_insert(pfdtab, (intptr_t)hFile, flags, 0, -1, "shfile_open", absname);
+ else
+ fd = shfile_dos2errno(GetLastError());
+ }
+
+# else /* K_OS != K_OS_WINDOWS */
+ fd = shfile_make_path(pfdtab, name, &absname[0]);
+ if (!fd)
+ {
+ fd = open(absname, flags, mode);
+ if (fd != -1)
+ fd = shfile_copy_insert_and_close(pfdtab, &fd, flags, 0, -1, "shfile_open", absname);
+ }
+
+# endif /* K_OS != K_OS_WINDOWS */
+
+#else
+ fd = open(name, flags, mode);
+#endif
+
+ TRACE2((NULL, "shfile_open(%p:{%s}, %#x, 0%o) -> %d [%d]\n", name, name, flags, mode, fd, errno));
+ return fd;
+}
+
+int shfile_pipe(shfdtab *pfdtab, int fds[2])
+{
+ int rc = -1;
+#ifdef SHFILE_IN_USE
+# if K_OS == K_OS_WINDOWS
+ HANDLE hRead = INVALID_HANDLE_VALUE;
+ HANDLE hWrite = INVALID_HANDLE_VALUE;
+ SECURITY_ATTRIBUTES SecurityAttributes;
+
+ SecurityAttributes.nLength = sizeof(SecurityAttributes);
+ SecurityAttributes.lpSecurityDescriptor = NULL;
+ SecurityAttributes.bInheritHandle = FALSE;
+
+ fds[1] = fds[0] = -1;
+ if (CreatePipe(&hRead, &hWrite, &SecurityAttributes, SHFILE_PIPE_SIZE))
+ {
+ fds[0] = shfile_insert(pfdtab, (intptr_t)hRead, O_RDONLY, SHFILE_FLAGS_PIPE, -1, "shfile_pipe", "pipe-rd");
+ if (fds[0] != -1)
+ {
+ fds[1] = shfile_insert(pfdtab, (intptr_t)hWrite, O_WRONLY, SHFILE_FLAGS_PIPE, -1, "shfile_pipe", "pipe-wr");
+ if (fds[1] != -1)
+ rc = 0;
+ }
+
+# else
+ int native_fds[2];
+
+ fds[1] = fds[0] = -1;
+ if (!pipe(native_fds))
+ {
+ fds[0] = shfile_copy_insert_and_close(pfdtab, &native_fds[0], O_RDONLY, SHFILE_FLAGS_PIPE, -1, "shfile_pipe", "pipe-rd");
+ if (fds[0] != -1)
+ {
+ fds[1] = shfile_copy_insert_and_close(pfdtab, &native_fds[1], O_WRONLY, SHFILE_FLAGS_PIPE, -1, "shfile_pipe", "pipe-wr");
+ if (fds[1] != -1)
+ rc = 0;
+ }
+# endif
+ if (fds[1] == -1)
+ {
+ int s = errno;
+ if (fds[0] != -1)
+ {
+ shmtxtmp tmp;
+ shmtx_enter(&pfdtab->mtx, &tmp);
+ rc = fds[0];
+ pfdtab->tab[rc].fd = -1;
+ pfdtab->tab[rc].oflags = 0;
+ pfdtab->tab[rc].shflags = 0;
+ pfdtab->tab[rc].native = -1;
+ shmtx_leave(&pfdtab->mtx, &tmp);
+ }
+
+# if K_OS == K_OS_WINDOWS
+ CloseHandle(hRead);
+ CloseHandle(hWrite);
+# else
+ close(native_fds[0]);
+ close(native_fds[1]);
+# endif
+ fds[0] = fds[1] = -1;
+ errno = s;
+ rc = -1;
+ }
+ }
+ else
+ {
+# if K_OS == K_OS_WINDOWS
+ errno = shfile_dos2errno(GetLastError());
+# endif
+ rc = -1;
+ }
+
+#else
+ rc = pipe(fds);
+#endif
+
+ TRACE2((NULL, "shfile_pipe() -> %d{%d,%d} [%d]\n", rc, fds[0], fds[1], errno));
+ return rc;
+}
+
+/**
+ * dup().
+ */
+int shfile_dup(shfdtab *pfdtab, int fd)
+{
+ return shfile_fcntl(pfdtab,fd, F_DUPFD, 0);
+}
+
+/**
+ * Move the file descriptor, closing any existing descriptor at @a fdto.
+ *
+ * @returns fdto on success, -1 and errno on failure.
+ * @param pfdtab The file descriptor table.
+ * @param fdfrom The descriptor to move.
+ * @param fdto Where to move it.
+ */
+int shfile_movefd(shfdtab *pfdtab, int fdfrom, int fdto)
+{
+#ifdef SHFILE_IN_USE
+ int rc;
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fdfrom, &tmp);
+ if (file)
+ {
+ /* prepare the new entry */
+ if ((unsigned)fdto >= pfdtab->size)
+ shfile_grow_tab_locked(pfdtab, fdto);
+ if ((unsigned)fdto < pfdtab->size)
+ {
+ if (pfdtab->tab[fdto].fd != -1)
+ shfile_native_close(pfdtab->tab[fdto].native, &pfdtab->tab[fdto], K_FALSE);
+
+ /* setup the target. */
+ pfdtab->tab[fdto].fd = fdto;
+ pfdtab->tab[fdto].oflags = file->oflags;
+ pfdtab->tab[fdto].shflags = file->shflags;
+ pfdtab->tab[fdto].native = file->native;
+# ifdef DEBUG
+ pfdtab->tab[fdto].dbgname = file->dbgname;
+# endif
+
+ /* close the source. */
+ file->fd = -1;
+ file->oflags = 0;
+ file->shflags = 0;
+ file->native = -1;
+# ifdef DEBUG
+ file->dbgname = NULL;
+# endif
+
+ rc = fdto;
+ }
+ else
+ {
+ errno = EMFILE;
+ rc = -1;
+ }
+
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ rc = -1;
+ return rc;
+
+#else
+ int fdnew = dup2(fdfrom, fdto);
+ if (fdnew >= 0)
+ close(fdfrom);
+ return fdnew;
+#endif
+}
+
+/**
+ * Move the file descriptor to somewhere at @a fdMin or above.
+ *
+ * @returns the new file descriptor success, -1 and errno on failure.
+ * @param pfdtab The file descriptor table.
+ * @param fdfrom The descriptor to move.
+ * @param fdMin The minimum descriptor.
+ */
+int shfile_movefd_above(shfdtab *pfdtab, int fdfrom, int fdMin)
+{
+#ifdef SHFILE_IN_USE
+ int fdto;
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fdfrom, &tmp);
+ if (file)
+ {
+ /* find a new place */
+ int i;
+ fdto = -1;
+ for (i = fdMin; (unsigned)i < pfdtab->size; i++)
+ if (pfdtab->tab[i].fd == -1)
+ {
+ fdto = i;
+ break;
+ }
+ if (fdto == -1)
+ fdto = shfile_grow_tab_locked(pfdtab, fdMin);
+ if (fdto != -1)
+ {
+ /* setup the target. */
+ pfdtab->tab[fdto].fd = fdto;
+ pfdtab->tab[fdto].oflags = file->oflags;
+ pfdtab->tab[fdto].shflags = file->shflags;
+ pfdtab->tab[fdto].native = file->native;
+# ifdef DEBUG
+ pfdtab->tab[fdto].dbgname = file->dbgname;
+# endif
+
+ /* close the source. */
+ file->fd = -1;
+ file->oflags = 0;
+ file->shflags = 0;
+ file->native = -1;
+# ifdef DEBUG
+ file->dbgname = NULL;
+# endif
+ }
+ else
+ {
+ errno = EMFILE;
+ fdto = -1;
+ }
+
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ fdto = -1;
+ return fdto;
+
+#else
+ int fdnew = fcntl(fdfrom, F_DUPFD, fdMin);
+ if (fdnew >= 0)
+ close(fdfrom);
+ return fdnew;
+#endif
+}
+
+/**
+ * close().
+ */
+int shfile_close(shfdtab *pfdtab, unsigned fd)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+ shfile_native_close(file->native, file, K_FALSE);
+
+ file->fd = -1;
+ file->oflags = 0;
+ file->shflags = 0;
+ file->native = -1;
+# ifdef DEBUG
+ sh_free(NULL, file->dbgname);
+ file->dbgname = NULL;
+# endif
+
+ shfile_put(pfdtab, file, &tmp);
+ rc = 0;
+ }
+ else
+ rc = -1;
+
+#else
+ rc = close(fd);
+#endif
+
+ TRACE2((NULL, "shfile_close(%d) -> %d [%d]\n", fd, rc, errno));
+ return rc;
+}
+
+/**
+ * read().
+ */
+long shfile_read(shfdtab *pfdtab, int fd, void *buf, size_t len)
+{
+ long rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+# if K_OS == K_OS_WINDOWS
+ DWORD dwRead = 0;
+ if (ReadFile((HANDLE)file->native, buf, (DWORD)len, &dwRead, NULL))
+ rc = dwRead;
+ else
+ rc = shfile_dos2errno(GetLastError());
+# else
+ rc = read(file->native, buf, len);
+# endif
+
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ rc = -1;
+
+#else
+ rc = read(fd, buf, len);
+#endif
+ return rc;
+}
+
+/**
+ * write().
+ */
+long shfile_write(shfdtab *pfdtab, int fd, const void *buf, size_t len)
+{
+ long rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+# if K_OS == K_OS_WINDOWS
+ DWORD dwWritten = 0;
+ if (WriteFile((HANDLE)file->native, buf, (DWORD)len, &dwWritten, NULL))
+ rc = dwWritten;
+ else
+ rc = shfile_dos2errno(GetLastError());
+# else
+ rc = write(file->native, buf, len);
+# endif
+
+ file->shflags |= SHFILE_FLAGS_DIRTY; /* there should be no concurrent access, so this is safe. */
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ rc = -1;
+
+# ifdef DEBUG
+ if (fd != shthread_get_shell()->tracefd)
+ TRACE2((NULL, "shfile_write(%d,,%d) -> %d [%d]\n", fd, len, rc, errno));
+# endif
+
+#else
+ if (fd != shthread_get_shell()->tracefd)
+ {
+ int iSavedErrno = errno;
+ struct stat s;
+ int x;
+ x = fstat(fd, &s);
+ TRACE2((NULL, "shfile_write(%d) - %lu bytes (%d) - pos %lu - before; %o\n",
+ fd, (long)s.st_size, x, (long)lseek(fd, 0, SEEK_CUR), s.st_mode ));
+ K_NOREF(x);
+ errno = iSavedErrno;
+ }
+
+ rc = write(fd, buf, len);
+#endif
+ return rc;
+}
+
+/**
+ * lseek().
+ */
+long shfile_lseek(shfdtab *pfdtab, int fd, long off, int whench)
+{
+ long rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+# if K_OS == K_OS_WINDOWS
+ kHlpAssert(SEEK_SET == FILE_BEGIN);
+ kHlpAssert(SEEK_CUR == FILE_CURRENT);
+ kHlpAssert(SEEK_END == FILE_END);
+ rc = SetFilePointer((HANDLE)file->native, off, NULL, whench);
+ if (rc == INVALID_SET_FILE_POINTER)
+ rc = shfile_dos2errno(GetLastError());
+# else
+ rc = lseek(file->native, off, whench);
+# endif
+
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ rc = -1;
+
+#else
+ rc = lseek(fd, off, whench);
+#endif
+
+ return rc;
+}
+
+int shfile_fcntl(shfdtab *pfdtab, int fd, int cmd, int arg)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+ switch (cmd)
+ {
+ case F_GETFL:
+ rc = file->oflags;
+ break;
+
+ case F_SETFL:
+ {
+ unsigned mask = O_NONBLOCK | O_APPEND | O_BINARY | O_TEXT;
+# ifdef O_DIRECT
+ mask |= O_DIRECT;
+# endif
+# ifdef O_ASYNC
+ mask |= O_ASYNC;
+# endif
+# ifdef O_SYNC
+ mask |= O_SYNC;
+# endif
+ if ((file->oflags & mask) == (arg & mask))
+ rc = 0;
+ else
+ {
+# if K_OS == K_OS_WINDOWS
+ kHlpAssert(0);
+ errno = EINVAL;
+ rc = -1;
+# else
+ rc = fcntl(file->native, F_SETFL, arg);
+ if (rc != -1)
+ file->oflags = (file->oflags & ~mask) | (arg & mask);
+# endif
+ }
+ break;
+ }
+
+ case F_DUPFD:
+ {
+# if K_OS == K_OS_WINDOWS
+ HANDLE hNew = INVALID_HANDLE_VALUE;
+ if (DuplicateHandle(GetCurrentProcess(),
+ (HANDLE)file->native,
+ GetCurrentProcess(),
+ &hNew,
+ 0,
+ FALSE /* bInheritHandle */,
+ DUPLICATE_SAME_ACCESS))
+ rc = shfile_insert(pfdtab, (intptr_t)hNew, file->oflags, file->shflags, arg,
+ "shfile_fcntl", SHFILE_DBGNAME(file->dbgname));
+ else
+ rc = shfile_dos2errno(GetLastError());
+# else
+ int nativeNew = fcntl(file->native, F_DUPFD, SHFILE_UNIX_MIN_FD);
+ if (nativeNew != -1)
+ rc = shfile_insert(pfdtab, nativeNew, file->oflags, file->shflags, arg,
+ "shfile_fcntl", SHFILE_DBGNAME(file->dbgname));
+ else
+ rc = -1;
+# endif
+ break;
+ }
+
+ default:
+ errno = -EINVAL;
+ rc = -1;
+ break;
+ }
+
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ rc = -1;
+
+#else
+ rc = fcntl(fd, cmd, arg);
+#endif
+
+ switch (cmd)
+ {
+ case F_GETFL: TRACE2((NULL, "shfile_fcntl(%d,F_GETFL,ignored=%d) -> %d [%d]\n", fd, arg, rc, errno)); break;
+ case F_SETFL: TRACE2((NULL, "shfile_fcntl(%d,F_SETFL,newflags=%#x) -> %d [%d]\n", fd, arg, rc, errno)); break;
+ case F_DUPFD: TRACE2((NULL, "shfile_fcntl(%d,F_DUPFD,minfd=%d) -> %d [%d]\n", fd, arg, rc, errno)); break;
+ default: TRACE2((NULL, "shfile_fcntl(%d,%d,%d) -> %d [%d]\n", fd, cmd, arg, rc, errno)); break;
+ }
+ return rc;
+}
+
+int shfile_stat(shfdtab *pfdtab, const char *path, struct stat *pst)
+{
+#ifdef SHFILE_IN_USE
+ char abspath[SHFILE_MAX_PATH];
+ int rc;
+ rc = shfile_make_path(pfdtab, path, &abspath[0]);
+ if (!rc)
+ {
+# if K_OS == K_OS_WINDOWS
+# if 1
+ rc = birdStatFollowLink(abspath, pst);
+# else
+ int dir_slash = shfile_trailing_slash_hack(abspath);
+ rc = stat(abspath, pst); /** @todo re-implement stat. */
+ if (!rc && dir_slash && !S_ISDIR(pst->st_mode))
+ {
+ rc = -1;
+ errno = ENOTDIR;
+
+ }
+# endif
+# else
+ rc = stat(abspath, pst);
+# endif
+ }
+ TRACE2((NULL, "shfile_stat(,%s,) -> %d [%d] st_size=%llu st_mode=%o\n",
+ path, rc, errno, (unsigned long long)pst->st_size, pst->st_mode));
+ return rc;
+#else
+ return stat(path, pst);
+#endif
+}
+
+/**
+ * @retval 1 if regular file.
+ * @retval 0 if found but not a regular file.
+ * @retval -1 and errno on failure
+ */
+int shfile_stat_isreg(shfdtab *pfdtab, const char *path)
+{
+#if defined(SHFILE_IN_USE) && K_OS == K_OS_WINDOWS
+ char abspath[SHFILE_MAX_PATH];
+ KU16 mode = 0;
+ int rc = shfile_make_path(pfdtab, path, &abspath[0]);
+ if (!rc)
+ {
+ rc = birdStatModeOnly(abspath, &mode, 0 /*fFollowLink*/);
+ if (rc >= 0)
+ rc = S_ISREG(mode) ? 1 : 0;
+ }
+ TRACE2((NULL, "shfile_stat_isreg(,%s,) -> %d [%d] st_mode=%o\n", path, rc, errno, mode));
+ return rc;
+#else
+ struct stat st;
+ int rc = shfile_stat(pfdtab, path, &st);
+ if (rc >= 0)
+ rc = S_ISREG(st.st_mode) ? 1 : 0;
+ return rc;
+#endif
+}
+
+/**
+ * Same as shfile_stat, but without the data structure.
+ */
+int shfile_stat_exists(shfdtab *pfdtab, const char *path)
+{
+#if defined(SHFILE_IN_USE) && K_OS == K_OS_WINDOWS
+ char abspath[SHFILE_MAX_PATH];
+ KU16 mode = 0;
+ int rc = shfile_make_path(pfdtab, path, &abspath[0]);
+ if (!rc)
+ rc = birdStatModeOnly(abspath, &mode, 0 /*fFollowLink*/);
+ TRACE2((NULL, "shfile_stat_exists(,%s,) -> %d [%d] st_mode=%o\n", path, rc, errno, mode));
+ return rc;
+#else
+ struct stat ignored;
+ return shfile_stat(pfdtab, path, &ignored);
+#endif
+}
+
+int shfile_lstat(shfdtab *pfdtab, const char *path, struct stat *pst)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ char abspath[SHFILE_MAX_PATH];
+
+ rc = shfile_make_path(pfdtab, path, &abspath[0]);
+ if (!rc)
+ {
+# if K_OS == K_OS_WINDOWS
+# if 1
+ rc = birdStatOnLink(abspath, pst);
+# else
+ int dir_slash = shfile_trailing_slash_hack(abspath);
+ rc = stat(abspath, pst); /** @todo re-implement stat. */
+ if (!rc && dir_slash && !S_ISDIR(pst->st_mode))
+ {
+ rc = -1;
+ errno = ENOTDIR;
+ }
+# endif
+# else
+ rc = lstat(abspath, pst);
+# endif
+ }
+#else
+ rc = stat(path, pst);
+#endif
+ TRACE2((NULL, "shfile_lstat(,%s,) -> %d [%d] st_size=%llu st_mode=%o\n",
+ path, rc, errno, (unsigned long long)pst->st_size, pst->st_mode));
+ return rc;
+}
+
+/**
+ * chdir().
+ */
+int shfile_chdir(shfdtab *pfdtab, const char *path)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ shinstance *psh = shthread_get_shell();
+ char abspath[SHFILE_MAX_PATH];
+
+ rc = shfile_make_path(pfdtab, path, &abspath[0]);
+ if (!rc)
+ {
+ char *abspath_copy = sh_strdup(psh, abspath);
+ char *free_me = abspath_copy;
+ rc = chdir(abspath);
+ if (!rc)
+ {
+ shmtxtmp tmp;
+ shmtx_enter(&pfdtab->mtx, &tmp);
+
+ shfile_fix_slashes(abspath_copy);
+ free_me = pfdtab->cwd;
+ pfdtab->cwd = abspath_copy;
+
+ shmtx_leave(&pfdtab->mtx, &tmp);
+ }
+ sh_free(psh, free_me);
+ }
+ else
+ rc = -1;
+#else
+ rc = chdir(path);
+#endif
+
+ TRACE2((NULL, "shfile_chdir(,%s) -> %d [%d]\n", path, rc, errno));
+ return rc;
+}
+
+/**
+ * getcwd().
+ */
+char *shfile_getcwd(shfdtab *pfdtab, char *buf, int size)
+{
+ char *ret;
+#ifdef SHFILE_IN_USE
+
+ ret = NULL;
+ if (buf && !size)
+ errno = -EINVAL;
+ else
+ {
+ size_t cwd_size;
+ shmtxtmp tmp;
+ shmtx_enter(&pfdtab->mtx, &tmp);
+
+ cwd_size = strlen(pfdtab->cwd) + 1;
+ if (buf)
+ {
+ if (cwd_size <= (size_t)size)
+ ret = memcpy(buf, pfdtab->cwd, cwd_size);
+ else
+ errno = ERANGE;
+ }
+ else
+ {
+ if ((size_t)size < cwd_size)
+ size = (int)cwd_size;
+ ret = sh_malloc(shthread_get_shell(), size);
+ if (ret)
+ ret = memcpy(ret, pfdtab->cwd, cwd_size);
+ else
+ errno = ENOMEM;
+ }
+
+ shmtx_leave(&pfdtab->mtx, &tmp);
+ }
+#else
+ ret = getcwd(buf, size);
+#endif
+
+ TRACE2((NULL, "shfile_getcwd(,%p,%d) -> %s [%d]\n", buf, size, ret, errno));
+ return ret;
+}
+
+/**
+ * access().
+ */
+int shfile_access(shfdtab *pfdtab, const char *path, int type)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ char abspath[SHFILE_MAX_PATH];
+
+ rc = shfile_make_path(pfdtab, path, &abspath[0]);
+ if (!rc)
+ {
+# ifdef _MSC_VER
+ if (type & X_OK)
+ type = (type & ~X_OK) | R_OK;
+# endif
+ rc = access(abspath, type);
+ }
+#else
+# ifdef _MSC_VER
+ if (type & X_OK)
+ type = (type & ~X_OK) | R_OK;
+# endif
+ rc = access(path, type);
+#endif
+
+ TRACE2((NULL, "shfile_access(,%s,%#x) -> %d [%d]\n", path, type, rc, errno));
+ return rc;
+}
+
+/**
+ * isatty()
+ */
+int shfile_isatty(shfdtab *pfdtab, int fd)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+# if K_OS == K_OS_WINDOWS
+ rc = (file->shflags & SHFILE_FLAGS_TYPE_MASK) == SHFILE_FLAGS_TTY;
+# else
+ rc = isatty(file->native);
+# endif
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ rc = 0;
+#else
+ rc = isatty(fd);
+#endif
+
+ TRACE2((NULL, "isatty(%d) -> %d [%d]\n", fd, rc, errno));
+ return rc;
+}
+
+/**
+ * fcntl F_SETFD / FD_CLOEXEC.
+ */
+int shfile_cloexec(shfdtab *pfdtab, int fd, int closeit)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+ if (closeit)
+ file->shflags |= SHFILE_FLAGS_CLOSE_ON_EXEC;
+ else
+ file->shflags &= ~SHFILE_FLAGS_CLOSE_ON_EXEC;
+ shfile_put(pfdtab, file, &tmp);
+ rc = 0;
+ }
+ else
+ rc = -1;
+#else
+ rc = fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0)
+ | (closeit ? FD_CLOEXEC : 0));
+#endif
+
+ TRACE2((NULL, "shfile_cloexec(%d, %d) -> %d [%d]\n", fd, closeit, rc, errno));
+ return rc;
+}
+
+/**
+ * Sets the SHFILE_FLAGS_TRACE flag.
+ */
+int shfile_set_trace(shfdtab *pfdtab, int fd)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+ file->shflags |= SHFILE_FLAGS_TRACE;
+ shfile_put(pfdtab, file, &tmp);
+ rc = 0;
+ }
+ else
+ rc = -1;
+#else
+ rc = 0;
+#endif
+
+ TRACE2((NULL, "shfile_set_trace(%d) -> %d\n", fd, rc));
+ return rc;
+}
+
+
+int shfile_ioctl(shfdtab *pfdtab, int fd, unsigned long request, void *buf)
+{
+ int rc;
+#ifdef SHFILE_IN_USE
+ shmtxtmp tmp;
+ shfile *file = shfile_get(pfdtab, fd, &tmp);
+ if (file)
+ {
+# if K_OS == K_OS_WINDOWS
+ rc = -1;
+ errno = ENOSYS;
+# else
+ rc = ioctl(file->native, request, buf);
+# endif
+ shfile_put(pfdtab, file, &tmp);
+ }
+ else
+ rc = -1;
+#else
+ rc = ioctl(fd, request, buf);
+#endif
+
+ TRACE2((NULL, "ioctl(%d, %#x, %p) -> %d\n", fd, request, buf, rc));
+ return rc;
+}
+
+
+mode_t shfile_get_umask(shfdtab *pfdtab)
+{
+ /** @todo */
+ return 022;
+}
+
+void shfile_set_umask(shfdtab *pfdtab, mode_t mask)
+{
+ /** @todo */
+ (void)mask;
+}
+
+
+
+shdir *shfile_opendir(shfdtab *pfdtab, const char *dir)
+{
+#if defined(SHFILE_IN_USE) && K_OS == K_OS_WINDOWS
+ shdir *pdir = NULL;
+
+ TRACE2((NULL, "shfile_opendir: dir='%s'\n", dir));
+ shfile_init_globals();
+ if (g_pfnNtQueryDirectoryFile)
+ {
+ char abspath[SHFILE_MAX_PATH];
+ if (shfile_make_path(pfdtab, dir, &abspath[0]) == 0)
+ {
+ HANDLE hFile;
+ SECURITY_ATTRIBUTES SecurityAttributes;
+
+ SecurityAttributes.nLength = sizeof(SecurityAttributes);
+ SecurityAttributes.lpSecurityDescriptor = NULL;
+ SecurityAttributes.bInheritHandle = FALSE;
+
+ hFile = CreateFileA(abspath,
+ GENERIC_READ,
+ FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+ &SecurityAttributes,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_DIRECTORY | FILE_FLAG_BACKUP_SEMANTICS,
+ NULL /* hTemplateFile */);
+ if (hFile != INVALID_HANDLE_VALUE)
+ {
+ pdir = (shdir *)sh_malloc(shthread_get_shell(), sizeof(*pdir));
+ if (pdir)
+ {
+ pdir->pfdtab = pfdtab;
+ pdir->native = hFile;
+ pdir->off = ~(size_t)0;
+ }
+ else
+ CloseHandle(hFile);
+ }
+ else
+ {
+ errno = shfile_dos2errno(GetLastError());
+ TRACE2((NULL, "shfile_opendir: CreateFileA(%s) -> %d/%d\n", abspath, GetLastError(), errno));
+ }
+ }
+ }
+ else
+ errno = ENOSYS;
+ return pdir;
+#else
+ TRACE2((NULL, "shfile_opendir: dir='%s'\n", dir));
+ return (shdir *)opendir(dir);
+#endif
+}
+
+shdirent *shfile_readdir(struct shdir *pdir)
+{
+#if defined(SHFILE_IN_USE) && K_OS == K_OS_WINDOWS
+ if (pdir)
+ {
+ NTSTATUS rcNt;
+
+ if ( pdir->off == ~(size_t)0
+ || pdir->off + sizeof(MY_FILE_NAMES_INFORMATION) >= pdir->cb)
+ {
+ MY_IO_STATUS_BLOCK Ios;
+
+ memset(&Ios, 0, sizeof(Ios));
+ rcNt = g_pfnNtQueryDirectoryFile(pdir->native,
+ NULL /*Event*/,
+ NULL /*ApcRoutine*/,
+ NULL /*ApcContext*/,
+ &Ios,
+ &pdir->buf[0],
+ sizeof(pdir->buf),
+ MY_FileNamesInformation,
+ FALSE /*ReturnSingleEntry*/,
+ NULL /*FileName*/,
+ pdir->off == ~(size_t)0 /*RestartScan*/);
+ if (rcNt >= 0 && rcNt != STATUS_PENDING)
+ {
+ pdir->cb = Ios.Information;
+ pdir->off = 0;
+ }
+ else if (rcNt == STATUS_NO_MORE_FILES)
+ errno = 0; /* wrong? */
+ else
+ shfile_nt2errno(rcNt);
+ }
+
+ if ( pdir->off != ~(size_t)0
+ && pdir->off + sizeof(MY_FILE_NAMES_INFORMATION) <= pdir->cb)
+ {
+ PMY_FILE_NAMES_INFORMATION pcur = (PMY_FILE_NAMES_INFORMATION)&pdir->buf[pdir->off];
+ ANSI_STRING astr;
+ UNICODE_STRING ustr;
+
+ astr.Length = astr.MaximumLength = sizeof(pdir->ent.name);
+ astr.Buffer = &pdir->ent.name[0];
+
+ ustr.Length = ustr.MaximumLength = pcur->FileNameLength < ~(USHORT)0 ? (USHORT)pcur->FileNameLength : ~(USHORT)0;
+ ustr.Buffer = &pcur->FileName[0];
+
+ rcNt = g_pfnRtlUnicodeStringToAnsiString(&astr, &ustr, 0/*AllocateDestinationString*/);
+ if (rcNt < 0)
+ sprintf(pdir->ent.name, "conversion-failed-%08x-rcNt=%08x-len=%u",
+ pcur->FileIndex, rcNt, pcur->FileNameLength);
+ if (pcur->NextEntryOffset)
+ pdir->off += pcur->NextEntryOffset;
+ else
+ pdir->off = pdir->cb;
+ return &pdir->ent;
+ }
+ }
+ else
+ errno = EINVAL;
+ return NULL;
+#else
+ struct dirent *pde = readdir((DIR *)pdir);
+ return pde ? (shdirent *)&pde->d_name[0] : NULL;
+#endif
+}
+
+void shfile_closedir(struct shdir *pdir)
+{
+#if defined(SHFILE_IN_USE) && K_OS == K_OS_WINDOWS
+ if (pdir)
+ {
+ CloseHandle(pdir->native);
+ pdir->pfdtab = NULL;
+ pdir->native = INVALID_HANDLE_VALUE;
+ sh_free(shthread_get_shell(), pdir);
+ }
+ else
+ errno = EINVAL;
+#else
+ closedir((DIR *)pdir);
+#endif
+}
+
diff --git a/src/kash/shfile.h b/src/kash/shfile.h
new file mode 100644
index 0000000..0052428
--- /dev/null
+++ b/src/kash/shfile.h
@@ -0,0 +1,229 @@
+/* $Id: shfile.h 3473 2020-09-16 21:12:58Z bird $ */
+/** @file
+ * File management.
+ */
+
+/*
+ * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef ___shfile_h
+#define ___shfile_h
+
+#include "shtypes.h"
+#include "shthread.h"
+#include <fcntl.h>
+#include <sys/stat.h>
+#ifdef _MSC_VER
+# define _PATH_DEVNULL "nul"
+# define _PATH_DEFPATH "."
+#else
+# if !defined(__sun__)
+# include <paths.h>
+# endif
+# ifndef _PATH_DEVNULL
+# define _PATH_DEVNULL "/dev/null"
+# endif
+# ifndef _PATH_DEFPATH
+# define _PATH_DEFPATH "/bin:/usr/bin:/sbin:/usr/sbin"
+# endif
+#endif
+#include <limits.h> /* for PIPE_BUF */
+#ifndef _MSC_VER
+# include <fcntl.h>
+# include <unistd.h>
+# ifndef O_BINARY
+# define O_BINARY 0
+# endif
+# ifndef O_TEXT
+# define O_TEXT 0
+# endif
+
+#else
+# include <io.h>
+# include <direct.h>
+
+# define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
+# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
+# define S_ISLNK(m) 0
+# define S_IRWXU (_S_IREAD | _S_IWRITE | _S_IEXEC)
+# define S_IXUSR _S_IEXEC
+# define S_IWUSR _S_IWRITE
+# define S_IRUSR _S_IREAD
+# define S_IRWXG 0000070
+# define S_IRGRP 0000040
+# define S_IWGRP 0000020
+# define S_IXGRP 0000010
+# define S_IRWXO 0000007
+# define S_IROTH 0000004
+# define S_IWOTH 0000002
+# define S_IXOTH 0000001
+# define S_ISUID 0004000
+# define S_ISGID 0002000
+# define ALLPERMS 0000777
+
+# define F_DUPFD 0
+# define F_GETFD 1
+# define F_SETFD 2
+# define F_GETFL 3
+# define F_SETFL 4
+# define FD_CLOEXEC 1
+
+# define F_OK 0
+# define X_OK 1
+# define W_OK 2
+# define R_OK 4
+
+# define O_NONBLOCK 0 /** @todo */
+
+#endif
+#if K_OS == K_OS_WINDOWS
+# include "nt/ntstat.h"
+#endif
+
+
+/**
+ * One file.
+ */
+typedef struct shfile
+{
+ int fd; /**< The shell file descriptor number. */
+ unsigned oflags; /**< Open flags. */
+ unsigned shflags; /**< The shell file descriptor flags. */
+ intptr_t native; /**< The native file descriptor number. */
+#ifdef DEBUG
+ char *dbgname; /**< The name of the file, if applicable, debug builds only. */
+# define SHFILE_DBGNAME(a) a
+# else
+# define SHFILE_DBGNAME(a) NULL
+#endif
+} shfile;
+
+/** @name shfile::shflags values.
+ * @{
+ */
+#define SHFILE_FLAGS_CLOSE_ON_EXEC 0x0001
+#define SHFILE_FLAGS_TRACE 0x0002 /**< The 'trace' file, keep open after execve. */
+#define SHFILE_FLAGS_TYPE_MASK 0x00f0
+#define SHFILE_FLAGS_FILE 0x0000
+#define SHFILE_FLAGS_PIPE 0x0010
+#define SHFILE_FLAGS_DIR 0x0020
+#define SHFILE_FLAGS_TTY 0x0030
+#define SHFILE_FLAGS_DIRTY 0x0100 /**< The file has been written to. */
+/** @} */
+
+/**
+ * The file descriptor table for a shell.
+ */
+typedef struct shfdtab
+{
+ shmtx mtx; /**< Mutex protecting any operations on the table and it's handles. */
+ char *cwd; /**< The current directory for this shell instance. */
+ unsigned size; /**< The size of the table (number of entries). */
+ shfile *tab; /**< Pointer to the table. */
+} shfdtab;
+
+int shfile_init(shfdtab *, shfdtab *);
+void shfile_uninit(shfdtab *, int);
+void shfile_fork_win(shfdtab *pfdtab, int set, intptr_t *hndls);
+typedef struct shfdexecwin
+{
+ int inherithandles;
+ int startsuspended;
+ shmtxtmp tmp;
+ int replacehandles[3];
+ intptr_t handles[3];
+ void *strtinfo;
+} shfdexecwin;
+int shfile_exec_win(shfdtab *pfdtab, int prepare, shfdexecwin *info);
+int shfile_exec_unix(shfdtab *pfdtab);
+#if K_OS == K_OS_WINDOWS && defined(KASH_ASYNC_CLOSE_HANDLE)
+void shfile_async_close_sync(void);
+#endif
+
+int shfile_open(shfdtab *, const char *, unsigned, mode_t);
+#if K_OS == K_OS_WINDOWS
+# define SHFILE_PIPE_SIZE 65536
+#elif defined(PIPE_BUF)
+# define SHFILE_PIPE_SIZE PIPE_BUF
+#else
+# define SHFILE_PIPE_SIZE 4096
+#endif
+int shfile_pipe(shfdtab *, int [2]);
+int shfile_close(shfdtab *, unsigned);
+long shfile_read(shfdtab *, int, void *, size_t);
+long shfile_write(shfdtab *, int, const void *, size_t);
+long shfile_lseek(shfdtab *, int, long, int);
+int shfile_fcntl(shfdtab *, int fd, int cmd, int arg);
+int shfile_dup(shfdtab *, int fd);
+int shfile_movefd(shfdtab *, int fdfrom, int fdto);
+int shfile_movefd_above(shfdtab *, int fdfrom, int fdmin);
+
+int shfile_stat(shfdtab *, const char *, struct stat *);
+int shfile_stat_isreg(shfdtab *, const char *); /**< returns -1, 0 or 1. */
+int shfile_stat_exists(shfdtab *, const char *); /**< same as shfile_stat, but discards the stat data. */
+int shfile_lstat(shfdtab *, const char *, struct stat *);
+int shfile_chdir(shfdtab *, const char *);
+char *shfile_getcwd(shfdtab *, char *, int);
+int shfile_access(shfdtab *, const char *, int);
+int shfile_isatty(shfdtab *, int);
+int shfile_cloexec(shfdtab *, int, int);
+int shfile_set_trace(shfdtab *, int);
+int shfile_ioctl(shfdtab *, int, unsigned long, void *);
+#if defined(_MSC_VER) || defined(__OS2__)
+# define TIOCGWINSZ 0x4201
+typedef struct sh_winsize
+{
+ unsigned ws_row; /**< Rows, in characters. */
+ unsigned ws_col; /**< Columns, in characters. */
+ unsigned ws_xpixel; /**< Horizontal size, pixels. */
+ unsigned ws_ypixel; /**< Vertical size, pixels. */
+} sh_winsize;
+#else
+typedef struct winsize sh_winsize;
+#endif
+mode_t shfile_get_umask(shfdtab *);
+void shfile_set_umask(shfdtab *, mode_t);
+
+
+typedef struct sh_dirent
+{
+ char name[260];
+} shdirent;
+
+typedef struct shdir
+{
+ shfdtab *pfdtab;
+ void *native;
+ shdirent ent;
+#if K_OS == K_OS_WINDOWS
+ size_t off;
+ size_t cb;
+ char buf[32768 - sizeof(void *) * 2 - sizeof(shdirent) * 2];
+#endif
+} shdir;
+
+shdir *shfile_opendir(shfdtab *, const char *);
+shdirent *shfile_readdir(struct shdir *);
+void shfile_closedir(struct shdir *);
+
+#endif
+
diff --git a/src/kash/shfork-win.c b/src/kash/shfork-win.c
new file mode 100644
index 0000000..7ca0d56
--- /dev/null
+++ b/src/kash/shfork-win.c
@@ -0,0 +1,290 @@
+
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include <string.h>
+#include <locale.h>
+#include "shinstance.h"
+#include <Windows.h>
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+/** The stack size. This is also defined in shforkA-win.asm. */
+#define SHFORK_STACK_SIZE (1*1024*1024)
+
+
+/*******************************************************************************
+* Global Variables *
+*******************************************************************************/
+static void *g_stack_base = 0;
+static void *g_stack_limit = 0;
+
+
+/*******************************************************************************
+* Internal Functions *
+*******************************************************************************/
+static void *shfork_string_to_ptr(const char *str, const char *argv0, const char *what);
+
+/* in shforkA-win.asm: */
+extern pid_t shfork_do_it(shinstance *psh);
+extern void shfork_resume(void *cur, void *base, void *limit);
+
+/* called by shforkA-win.asm: */
+void *shfork_maybe_forked(int argc, char **argv, char **envp);
+extern int shfork_body(shinstance *psh, void *stack_ptr);
+extern void init_syntax(void);
+
+
+/**
+ * Called by shforkA-win.asm to check whether we're a forked child
+ * process or not.
+ *
+ * In the former case we will resume execution at the fork resume
+ * point. In the latter we'll allocate a new stack of the forkable
+ * heap and return it to the caller so real_main() in main.c can be
+ * invoked on it.
+ *
+ * @returns Stack or not at all.
+ * @param argc Argument count.
+ * @param argv Argument vector.
+ * @param envp Environment vector.
+ */
+void *shfork_maybe_forked(int argc, char **argv, char **envp)
+{
+ void *stack_ptr;
+
+ /*
+ * Are we actually forking?
+ */
+ if ( argc != 8
+ || strcmp(argv[1], "--!forked!--")
+ || strcmp(argv[2], "--stack-address")
+ || strcmp(argv[4], "--stack-base")
+ || strcmp(argv[6], "--stack-limit"))
+ {
+ char *stack;
+ shheap_init(NULL);
+ g_stack_limit = stack = (char *)sh_malloc(NULL, SHFORK_STACK_SIZE);
+ g_stack_base = stack += SHFORK_STACK_SIZE;
+ return stack;
+ }
+
+ /*
+ * Do any init that needs to be done before resuming the
+ * fork() call.
+ */
+ setlocale(LC_ALL, "");
+
+ /*
+ * Convert the stack addresses.
+ */
+ stack_ptr = shfork_string_to_ptr(argv[3], argv[0], "--stack-address");
+ g_stack_base = shfork_string_to_ptr(argv[5], argv[0], "--stack-base");
+ g_stack_limit = shfork_string_to_ptr(argv[7], argv[0], "--stack-limit");
+ kHlpAssert((uintptr_t)stack_ptr < (uintptr_t)g_stack_base);
+ kHlpAssert((uintptr_t)stack_ptr > (uintptr_t)g_stack_limit);
+
+ /*
+ * Switch stack and jump to the fork resume point.
+ */
+ shfork_resume(stack_ptr, g_stack_base, g_stack_limit);
+ /* (won't get here) */
+ return NULL;
+}
+
+/***
+ * Converts a string into a pointer.
+ *
+ * @returns Pointer.
+ * @param argv0 The program name in case of error.
+ * @param str The string to convert.
+ */
+static void *shfork_string_to_ptr(const char *str, const char *argv0, const char *what)
+{
+ const char *start = str;
+ intptr_t ptr = 0;
+ if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X'))
+ str += 2;
+ while (*str)
+ {
+ unsigned digit;
+ switch (*str)
+ {
+ case '0': digit = 0; break;
+ case '1': digit = 1; break;
+ case '2': digit = 2; break;
+ case '3': digit = 3; break;
+ case '4': digit = 4; break;
+ case '5': digit = 5; break;
+ case '6': digit = 6; break;
+ case '7': digit = 7; break;
+ case '8': digit = 8; break;
+ case '9': digit = 9; break;
+ case 'a': case 'A': digit = 0xa; break;
+ case 'b': case 'B': digit = 0xb; break;
+ case 'c': case 'C': digit = 0xc; break;
+ case 'd': case 'D': digit = 0xd; break;
+ case 'e': case 'E': digit = 0xe; break;
+ case 'f': case 'F': digit = 0xf; break;
+ default:
+ fprintf(stderr, "%s: fatal error: Invalid %s '%s'\n", argv0, what, start);
+ exit(2);
+ }
+ ptr <<= 4;
+ ptr |= digit;
+ str++;
+ }
+ return (void *)ptr;
+}
+
+/**
+ * Do the fork.
+ * @returns same as fork().
+ * @param psh The shell that's forking.
+ */
+int shfork_do(shinstance *psh)
+{
+ /* save globals */
+ void *pheap_head = shheap_get_head();
+ pid_t pid = shfork_do_it(psh);
+ if (pid == 0)
+ {
+ /* reinit stuff, only the heap is copied! */
+ shthread_set_shell(psh);
+ shheap_init(pheap_head);
+ setlocale(LC_ALL, "");
+ init_syntax();
+ sh_init_globals();
+ }
+ return pid;
+}
+
+/**
+ * Create the child process making sure it inherits all our handles,
+ * copy of the forkable heap and kick it off.
+ *
+ * Called by shfork_do_it() in shforkA-win.asm.
+ *
+ * @returns child pid on success, -1 and errno on failure.
+ * @param psh The shell that's forking.
+ * @param stack_ptr The stack address at which the guest is suppost to resume.
+ */
+int shfork_body(shinstance *psh, void *stack_ptr)
+{
+ PROCESS_INFORMATION ProcInfo;
+ STARTUPINFO StrtInfo;
+ intptr_t hndls[3];
+ char szExeName[1024];
+ char szCmdLine[1024+256];
+ DWORD cch;
+ int rc = 0;
+
+ kHlpAssert((uintptr_t)stack_ptr < (uintptr_t)g_stack_base);
+ kHlpAssert((uintptr_t)stack_ptr > (uintptr_t)g_stack_limit);
+
+ /*
+ * Mark all handles inheritable and get the three standard handles.
+ */
+ shfile_fork_win(&psh->fdtab, 1 /* set */, &hndls[0]);
+
+ /*
+ * Create the process.
+ */
+ cch = GetModuleFileName(GetModuleHandle(NULL), szExeName, sizeof(szExeName));
+ if (cch > 0)
+ {
+#if 0 /* quoting the program name doesn't seems to be working :/ */
+ szCmdLine[0] = '"';
+ memcpy(&szCmdLine[1], szExeName, cch);
+ szCmdLine[++cch] = '"';
+#else
+ memcpy(&szCmdLine[0], szExeName, cch);
+#endif
+ cch += sprintf(&szCmdLine[cch], " --!forked!-- --stack-address %p --stack-base %p --stack-limit %p",
+ stack_ptr, g_stack_base, g_stack_limit);
+ szCmdLine[cch+1] = '\0';
+ TRACE2((NULL, "shfork_body: szCmdLine=%s\n", szCmdLine));
+
+ memset(&StrtInfo, '\0', sizeof(StrtInfo)); /* just in case. */
+ StrtInfo.cb = sizeof(StrtInfo);
+ StrtInfo.lpReserved = NULL;
+ StrtInfo.lpDesktop = NULL;
+ StrtInfo.lpTitle = NULL;
+ StrtInfo.dwX = 0;
+ StrtInfo.dwY = 0;
+ StrtInfo.dwXSize = 0;
+ StrtInfo.dwYSize = 0;
+ StrtInfo.dwXCountChars = 0;
+ StrtInfo.dwYCountChars = 0;
+ StrtInfo.dwFillAttribute = 0;
+ StrtInfo.dwFlags = STARTF_USESTDHANDLES;;
+ StrtInfo.wShowWindow = 0;
+ StrtInfo.cbReserved2 = 0;
+ StrtInfo.lpReserved2 = NULL;
+ StrtInfo.hStdInput = (HANDLE)hndls[0];
+ StrtInfo.hStdOutput = (HANDLE)hndls[1];
+ StrtInfo.hStdError = (HANDLE)hndls[2];
+ if (CreateProcess(szExeName,
+ szCmdLine,
+ NULL, /* pProcessAttributes */
+ NULL, /* pThreadAttributes */
+ TRUE, /* bInheritHandles */
+ CREATE_SUSPENDED,
+ NULL, /* pEnvironment */
+ NULL, /* pCurrentDirectory */
+ &StrtInfo,
+ &ProcInfo))
+ {
+ /*
+ * Copy the memory to the child.
+ */
+ rc = shheap_fork_copy_to_child(ProcInfo.hProcess);
+ if (!rc)
+ {
+ if (ResumeThread(ProcInfo.hThread) != (DWORD)-1)
+ {
+ rc = sh_add_child(psh, ProcInfo.dwProcessId, ProcInfo.hProcess, NULL);
+ if (!rc)
+ rc = (int)ProcInfo.dwProcessId;
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ fprintf(stderr, "shfork: ResumeThread() -> %d\n", dwErr);
+ errno = EINVAL;
+ rc = -1;
+ }
+ }
+ if (rc == -1)
+ {
+ TerminateProcess(ProcInfo.hProcess, 127);
+ /* needed?: ResumeThread(ProcInfo.hThread); */
+ CloseHandle(ProcInfo.hProcess);
+ }
+ CloseHandle(ProcInfo.hThread);
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ fprintf(stderr, "shfork: CreateProcess(%s) -> %d\n", szExeName, dwErr);
+ errno = EINVAL;
+ rc = -1;
+ }
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ fprintf(stderr, "shfork: GetModuleFileName() -> %d\n", dwErr);
+ errno = EINVAL;
+ rc = -1;
+ }
+
+ /*
+ * Restore the handle inherit property.
+ */
+ shfile_fork_win(&psh->fdtab, 0 /* restore */, NULL);
+
+ return rc;
+}
diff --git a/src/kash/shforkA-win.asm b/src/kash/shforkA-win.asm
new file mode 100644
index 0000000..6db4f28
--- /dev/null
+++ b/src/kash/shforkA-win.asm
@@ -0,0 +1,338 @@
+; $Id: shforkA-win.asm 2416 2010-09-14 00:30:30Z bird $
+;; @file
+; shforkA-win.asm - assembly routines used when forking on Windows.
+;
+
+;
+; Copyright (c) 2009-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+;
+; This file is part of kBuild.
+;
+; kBuild is free software; you can redistribute it and/or modify
+; it under the terms of the GNU General Public License as published by
+; the Free Software Foundation; either version 3 of the License, or
+; (at your option) any later version.
+;
+; kBuild is distributed in the hope that it will be useful,
+; but WITHOUT ANY WARRANTY; without even the implied warranty of
+; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+; GNU General Public License for more details.
+;
+; You should have received a copy of the GNU General Public License
+; along with kBuild. If not, see <http://www.gnu.org/licenses/>
+;
+;
+
+;*******************************************************************************
+;* Defined Constants And Macros *
+;*******************************************************************************
+%ifdef KBUILD_ARCH_AMD64
+ %define NAME(name) name
+%else
+ %define NAME(name) _ %+ name
+%endif
+
+;; The stack size. This is also defined in shfork-win.c.
+%define SHFORK_STACK_SIZE (1*1024*1024)
+
+
+;*******************************************************************************
+;* External Symbols *
+;*******************************************************************************
+extern NAME(real_main)
+extern NAME(shfork_maybe_forked)
+extern NAME(shfork_body)
+
+
+[section .text]
+
+;;
+; C main() wrapper.
+;
+NAME(main):
+global NAME(main)
+%ifdef KBUILD_ARCH_AMD64
+[proc_frame main]
+%endif
+
+ ;
+ ; Prolog, spilling parameters from registers.
+ ;
+%ifdef KBUILD_ARCH_AMD64
+ [pushreg rbp]
+ push rbp
+ [setframe rbp, 0]
+ mov rbp, rsp
+ [allocstack 0x40]
+ sub rsp, 40h
+ and rsp, ~1fh
+ mov [rbp-08h], rcx ; argc
+ mov [rbp-10h], rdx ; argv
+ mov [rbp-18h], r8 ; envp
+ [endprolog]
+%else
+ push ebp
+ mov ebp, esp
+ sub esp, 40h
+ and esp, ~1fh
+%endif
+
+ ;
+ ; Call shfork_maybe_forked. This will not return if we're forking.
+ ;
+%ifndef KBUILD_ARCH_AMD64
+ mov ecx, [ebp + 8h] ; argc
+ mov edx, [ebp + 0ch] ; argv
+ mov eax, [ebp + 10h] ; envp
+ mov [esp ], ecx
+ mov [esp + 4h], edx
+ mov [esp + 8h], eax
+%endif
+ call NAME(shfork_maybe_forked)
+
+ ;
+ ; Ok, it returned which means we're not forking.
+ ;
+ ; The accumulator register is now pointing to the top of the
+ ; stack we're going to call real_main on. Switch and call it.
+ ;
+ ; The TIB adjustments is required or we'll crash in longjmp/unwind.
+ ;
+%ifdef KBUILD_ARCH_AMD64
+ mov [rsp + 18h], rax
+ mov [rax - 8h], rsp
+
+ mov r10, [gs:08h] ; StackBase (the higher value)
+ mov r11, [gs:10h] ; StackLimit (the lower value)
+ mov [rax - 10h], r10
+ mov [rax - 18h], r11
+ cmp rax, r10
+ jb .below
+ mov [gs:08h], rax
+.below:
+ lea r9, [rax - SHFORK_STACK_SIZE]
+ cmp r9, r11
+ ja .above
+ mov [gs:10h], r9
+.above:
+
+ mov rcx, [rbp - 08h] ; argc
+ mov rdx, [rbp - 10h] ; argv
+ mov r8, [rbp - 18h] ; envp
+
+ lea rsp, [rax - 40h] ; Switch!
+%else
+ mov [esp + 18h], eax
+ mov [eax - 4], esp
+ lea esp, [eax - 40h] ; Switch!
+
+ mov edx, [fs:04h] ; StackBase (the higher value)
+ mov ecx, [fs:08h] ; StackLimit (the lower value)
+ mov [eax - 10h], edx
+ mov [eax - 18h], ecx
+ cmp eax, edx
+ jb .below
+ mov [fs:04h], eax
+.below:
+ lea edx, [eax - SHFORK_STACK_SIZE]
+ cmp edx, ecx
+ ja .above
+ mov [fs:08h], edx
+.above:
+
+ mov ecx, [ebp + 8h] ; argc
+ mov edx, [ebp + 0ch] ; argv
+ mov eax, [ebp + 10h] ; envp
+
+ mov [esp ], ecx
+ mov [esp + 4h], edx
+ mov [esp + 8h], eax
+%endif
+ call NAME(real_main)
+
+ ;
+ ; Switch back the stack, restore the TIB fields and we're done.
+ ;
+%ifdef KBUILD_ARCH_AMD64
+ lea r11, [rsp + 40h]
+ mov rsp, [rsp + 38h]
+ mov r8, [r11 - 10h]
+ mov r9, [r11 - 18h]
+ mov [gs:08h], r8
+ mov [gs:10h], r9
+%else
+ lea edx, [esp + 40h]
+ mov esp, [esp + 2ch]
+ mov ecx, [edx - 10h]
+ mov edx, [edx - 18h]
+ mov [fs:04h], ecx
+ mov [fs:08h], edx
+%endif
+ leave
+ ret
+%ifdef KBUILD_ARCH_AMD64
+[endproc_frame main]
+%endif
+
+
+;;
+; sh_fork() worker
+;
+; @returns See fork().
+; @param psh
+;
+NAME(shfork_do_it):
+global NAME(shfork_do_it)
+%ifdef KBUILD_ARCH_AMD64
+ [proc_frame shfork_do_it]
+ [pushreg rbp]
+ push rbp
+ [setframe rbp, 0]
+ mov rbp, rsp
+ [allocstack 0x400]
+ sub rsp, 400h
+ and rsp, ~1ffh
+[endprolog]
+%else
+ push ebp
+ mov ebp, esp
+ sub esp, 400h
+ and esp, ~1ffh
+%endif
+
+ ;
+ ; Save most registers so they can be restored in the child.
+ ;
+%ifdef KBUILD_ARCH_AMD64
+ fxsave [rsp]
+ mov [rsp + 200h], rbp
+ mov [rsp + 208h], rax
+ mov [rsp + 210h], rbx
+ mov [rsp + 218h], rcx
+ mov [rsp + 220h], rdx
+ mov [rsp + 228h], rsi
+ mov [rsp + 230h], rdi
+ mov [rsp + 238h], r8
+ mov [rsp + 240h], r9
+ mov [rsp + 248h], r10
+ mov [rsp + 250h], r11
+ mov [rsp + 258h], r12
+ mov [rsp + 260h], r13
+ mov [rsp + 268h], r14
+ mov [rsp + 270h], r15
+%else
+ fxsave [esp]
+ mov [esp + 200h], ebp
+ mov [esp + 208h], eax
+ mov [esp + 210h], ebx
+ mov [esp + 218h], ecx
+ mov [esp + 220h], edx
+ mov [esp + 228h], esi
+ mov [esp + 230h], edi
+%endif
+
+ ;
+ ; Call the shfork_body that will spawn the child and all that.
+ ;
+%ifdef KBUILD_ARCH_AMD64
+ ;mov rcx, rcx ; psh
+ mov rdx, rsp ; stack_ptr
+ sub rsp, 20h
+ call NAME(shfork_body)
+ lea rsp, [rsp + 20h]
+%else
+ mov edx, esp
+ mov ecx, [ebp + 8h] ; psh
+ sub esp, 20h
+ mov [esp ], ecx
+ mov [esp + 4], edx ; stack_ptr
+ call NAME(shfork_body)
+ lea esp, [esp + 20h]
+%endif
+
+ ;
+ ; Just leave the function, no need to restore things.
+ ;
+ leave
+ ret
+%ifdef KBUILD_ARCH_AMD64
+[endproc_frame shfork_do_it]
+%endif
+
+
+;;
+; Switch the stack, restore the register and leave as if we'd called shfork_do_it.
+;
+; @param cur Current stack pointer.
+; @param base The stack base (higher value).
+; @param limit The stack limit (lower value).
+;
+NAME(shfork_resume):
+global NAME(shfork_resume)
+%ifdef KBUILD_ARCH_AMD64
+ mov rsp, rcx
+%else
+ mov ecx, [esp + 4]
+ mov edx, [esp + 8]
+ mov eax, [esp + 12]
+ mov esp, ecx
+%endif
+
+ ;
+ ; Adjust stack stuff in the TIB (longjmp/unwind).
+ ;
+%ifdef KBUILD_ARCH_AMD64
+ cmp rdx, [gs:08h] ; StackBase (the higher value)
+ jb .below
+ mov [gs:08h], rdx
+.below:
+ cmp r8, [gs:10h] ; StackLimit
+ ja .above
+ mov [gs:10h], r8
+.above:
+%else
+ cmp edx, [fs:04h] ; StackBase (the higher value)
+ jb .below
+ mov [fs:04h], edx
+.below:
+ cmp eax, [fs:08h] ; StackLimit
+ ja .above
+ mov [fs:08h], eax
+.above:
+%endif
+
+ ;
+ ; Restore most of the registers.
+ ;
+ ;; @todo xmm registers may require explicit saving/restoring...
+%ifdef KBUILD_ARCH_AMD64
+ frstor [rsp]
+ mov rbp, [rsp + 200h]
+ mov rax, [rsp + 208h]
+ mov rbx, [rsp + 210h]
+ mov rcx, [rsp + 218h]
+ mov rdx, [rsp + 220h]
+ mov rsi, [rsp + 228h]
+ mov rdi, [rsp + 230h]
+ mov r8, [rsp + 238h]
+ mov r9, [rsp + 240h]
+ mov r10, [rsp + 248h]
+ mov r11, [rsp + 250h]
+ mov r12, [rsp + 258h]
+ mov r13, [rsp + 260h]
+ mov r14, [rsp + 268h]
+ mov r15, [rsp + 270h]
+%else
+ frstor [esp]
+ mov ebp, [esp + 200h]
+ mov eax, [esp + 208h]
+ mov ebx, [esp + 210h]
+ mov ecx, [esp + 218h]
+ mov edx, [esp + 220h]
+ mov esi, [esp + 228h]
+ mov edi, [esp + 230h]
+%endif
+ xor eax, eax ; the child returns 0.
+ leave
+ ret
+
diff --git a/src/kash/shheap.c b/src/kash/shheap.c
new file mode 100644
index 0000000..cc481fb
--- /dev/null
+++ b/src/kash/shheap.c
@@ -0,0 +1,596 @@
+/* $Id: shheap.c 3477 2020-09-17 21:52:16Z bird $ */
+/** @file
+ * The shell memory heap methods.
+ */
+
+/*
+ * Copyright (c) 2009-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include "shheap.h"
+#include <string.h>
+#include <stdlib.h>
+#include "shinstance.h"
+
+#if K_OS == K_OS_WINDOWS && defined(SH_FORKED_MODE)
+# define SHHEAP_IN_USE
+#endif
+
+#ifdef SHHEAP_IN_USE
+# if K_OS == K_OS_WINDOWS
+# include <Windows.h>
+# else
+# include <unistd.h>
+# endif
+#endif
+
+
+/*******************************************************************************
+* Structures and Typedefs *
+*******************************************************************************/
+#ifdef SHHEAP_IN_USE
+/**
+ * heap memory block header.
+ */
+typedef struct shmemhdr
+{
+ size_t magic; /**< Magic value */
+ size_t size; /**< The block size */
+ struct shmemhdr *next; /**< Forward pointer. */
+ struct shmemhdr *prev; /**< Backward pointer. */
+ struct shmemhdr *next2; /**< Free/Shell list forward. */
+ struct shmemhdr *prev2; /**< Free/Shell list backward. */
+ struct shinstance *psh; /**< The shell who allocated it. */
+ struct shmemchunk *chunk; /**< The chunk who owns this. */
+} shmemhdr;
+
+/** Free block magic (shmemhdr::magic) */
+#define SHMEMHDR_MAGIC_FREE 0xbeeff00d
+/** Used block magic (shmemhdr::magic) */
+#define SHMEMHDR_MAGIC_USED 0xfeedface
+
+typedef struct shmemchunk
+{
+ struct shmemhdr *head; /**< Head of the block list. */
+ struct shmemhdr *free_head; /**< Head of the free list. */
+ struct shmemchunk *next; /**< The next block. */
+ struct shmemchunk *prev; /**< The previous block. */
+ size_t size; /**< Chunk size. */
+ size_t magic; /**< Magic value. */
+ size_t padding0;
+ size_t padding1;
+} shmemchunk;
+
+/** shmemchunk::magic */
+#define SHMEMCHUNK_MAGIC 0x12345678
+
+#endif /* K_OS_WINDOWS */
+
+
+/*******************************************************************************
+* Defined Constants And Macros *
+*******************************************************************************/
+#define SHHEAP_ALIGN(sz) (((sz) + 31) & ~(size_t)31)
+#define SHHEAP_CHUNK_ALIGN(sz) (((sz) + 0xffff) & ~(size_t)0xffff)
+#define SHHEAP_MIN_CHUNK 0x80000 //(1024*1024)
+#ifdef NDEBUG
+# define SHHEAP_CHECK() do { } while (0)
+# define SHHEAP_CHECK_2() do { } while (0)
+# define SHHEAP_ASSERT(expr) do { } while (0)
+# define SHHEAP_POISON_PSH(p,v) (p)
+# define SHHEAP_POISON_NULL(v) NULL
+#else
+# define SHHEAP_CHECK() shheap_check()
+# define SHHEAP_CHECK_2() shheap_check()
+# define SHHEAP_ASSERT(expr) kHlpAssert(expr)
+# define SHHEAP_POISON_PSH(p,v) ((shinstance *)(v))
+# define SHHEAP_POISON_NULL(v) ((void *)(v))
+#endif
+
+
+/*******************************************************************************
+* Global Variables *
+*******************************************************************************/
+#ifdef SHHEAP_IN_USE
+/** The heap lock. */
+static shmtx g_sh_heap_mtx;
+/** The heap.
+ * This is a list of chunks. */
+static shmemchunk *g_sh_heap;
+#endif
+
+
+int shheap_init(void *phead)
+{
+ int rc;
+#ifdef SHHEAP_IN_USE
+ SHHEAP_ASSERT(SHHEAP_ALIGN(sizeof(shmemhdr)) == sizeof(shmemhdr));
+ rc = shmtx_init(&g_sh_heap_mtx);
+ g_sh_heap = (shmemchunk *)phead; /* non-zero on fork() */
+#else
+ rc = 0;
+#endif
+ return rc;
+}
+
+#ifdef SHHEAP_IN_USE
+
+# if K_OS == K_OS_WINDOWS
+
+/**
+ * Get the head so the child can pass it to shheap_init() after fork().
+ *
+ * @returns g_sh_heap.
+ */
+void *shheap_get_head(void)
+{
+ return g_sh_heap;
+}
+
+/**
+ * Copies the heap into the child process.
+ *
+ * @returns 0 on success, -1 and errno on failure.
+ * @param hChild Handle to the child process.
+ */
+int shheap_fork_copy_to_child(void *hChild)
+{
+ shmemchunk *chunk;
+ shmtxtmp tmp;
+ int err = 0;
+
+ shmtx_enter(&g_sh_heap_mtx, &tmp);
+
+ for (chunk = g_sh_heap; chunk; chunk = chunk->next)
+ {
+ void *chld_chnk;
+
+ chld_chnk = (shmemchunk *)VirtualAllocEx(hChild, chunk, chunk->size,
+ MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
+ if (chld_chnk != chunk)
+ {
+ err = GetLastError();
+ fprintf(stderr, "shfork: VirtualAllocEx(,%p,%p,) -> %p/%d\n", chunk, chunk->size, chld_chnk, err);
+ break;
+ }
+
+ if (!WriteProcessMemory(hChild, chunk, chunk, chunk->size, NULL /* pNumberOfBytesWritten */))
+ {
+ err = GetLastError();
+ fprintf(stderr, "shfork: WriteProcessMemory(,%p,,%p,) -> %d\n", chunk, chunk->size, err);
+ break;
+ }
+ }
+
+ shmtx_leave(&g_sh_heap_mtx, &tmp);
+
+ if (!err)
+ return 0;
+ errno = EINVAL;
+ return -1;
+}
+
+# endif /* K_OS == K_OS_WINDOWS */
+
+/**
+ * Checks a heap chunk.
+ * @param chunk The chunk to check.
+ */
+static void shheap_check_chunk(shmemchunk *chunk)
+{
+ size_t free_count;
+ struct shmemhdr *mem;
+ struct shmemhdr *prev;
+
+ SHHEAP_ASSERT(chunk->magic == SHMEMCHUNK_MAGIC);
+ SHHEAP_ASSERT(chunk->head);
+ SHHEAP_ASSERT(chunk->size == SHHEAP_CHUNK_ALIGN(chunk->size));
+
+ free_count = 0;
+ prev = NULL;
+ for (mem = chunk->head; mem; mem = mem->next)
+ {
+ size_t size = (mem->next ? (char *)mem->next : (char *)chunk + chunk->size) - (char *)(mem + 1);
+ SHHEAP_ASSERT(mem->size == size);
+ SHHEAP_ASSERT(mem->prev == prev);
+ if (mem->magic == SHMEMHDR_MAGIC_FREE)
+ free_count++;
+ else
+ SHHEAP_ASSERT(mem->magic == SHMEMHDR_MAGIC_USED);
+ prev = mem;
+ }
+
+ prev = NULL;
+ for (mem = chunk->free_head; mem; mem = mem->next2)
+ {
+ size_t size = (mem->next ? (char *)mem->next : (char *)chunk + chunk->size) - (char *)(mem + 1);
+ SHHEAP_ASSERT(mem->size == size);
+ SHHEAP_ASSERT(mem->prev2 == prev);
+ SHHEAP_ASSERT(mem->magic == SHMEMHDR_MAGIC_FREE);
+ free_count--;
+ prev = mem;
+ }
+ SHHEAP_ASSERT(free_count == 0);
+}
+
+/**
+ * Checks the heap.
+ */
+static void shheap_check(void)
+{
+ shmemchunk *chunk;
+ for (chunk = g_sh_heap; chunk; chunk = chunk->next)
+ shheap_check_chunk(chunk);
+}
+
+/**
+ * Grows the heap with another chunk carving out a block
+ *
+ * @returns Pointer to a used entry of size @a size1. NULL
+ * if we're out of memory
+ * @param size1 The size of the block to be returned (aligned).
+ */
+static shmemhdr *shheap_grow(size_t size1)
+{
+ shmemchunk *chunk;
+ shmemhdr *used;
+ shmemhdr *avail;
+ size_t chunk_size;
+
+ /* Calc the chunk size and allocate it. */
+ chunk_size = SHHEAP_ALIGN(size1) + SHHEAP_ALIGN(sizeof(*chunk)) + SHHEAP_ALIGN(sizeof(*used)) * 10;
+ if (chunk_size < SHHEAP_MIN_CHUNK)
+ chunk_size = SHHEAP_MIN_CHUNK;
+ else
+ chunk_size = SHHEAP_CHUNK_ALIGN(chunk_size);
+
+# if K_OS == K_OS_WINDOWS
+ chunk = (shmemchunk *)VirtualAlloc(NULL, chunk_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
+# else
+ chunk = NULL;
+# endif
+
+ if (!chunk)
+ return NULL;
+
+ used = (shmemhdr *)((char *)chunk + SHHEAP_ALIGN(sizeof(*chunk)));
+ avail = (shmemhdr *)((char *)(used + 1) + size1);
+
+ used->magic = SHMEMHDR_MAGIC_USED;
+ used->size = size1;
+ used->next = avail;
+ used->prev = NULL;
+ used->next2 = SHHEAP_POISON_NULL(0x41);
+ used->prev2 = SHHEAP_POISON_NULL(0x41);
+ used->psh = NULL;
+ used->chunk = chunk;
+
+ avail->magic = SHMEMHDR_MAGIC_FREE;
+ avail->size = (char *)chunk + chunk_size - (char *)(avail + 1);
+ avail->next = NULL;
+ avail->prev = used;
+ avail->next2 = NULL;
+ avail->prev2 = NULL;
+ avail->psh = NULL;
+ avail->chunk = chunk;
+
+ chunk->head = used;
+ chunk->free_head = avail;
+ chunk->size = chunk_size;
+ chunk->magic = SHMEMCHUNK_MAGIC;
+ chunk->prev = NULL;
+ chunk->next = g_sh_heap;
+ if (g_sh_heap)
+ g_sh_heap->prev = chunk;
+ g_sh_heap = chunk;
+ chunk->padding0 = 0;
+ chunk->padding1 = 0;
+
+ SHHEAP_CHECK_2();
+ return used;
+}
+
+/***
+ * Splits a big memory block into two smaller, one with the
+ * size @a size1.
+ *
+ * The one with the given size is removed from the free list
+ * while the other one remains there.
+ *
+ * @returns The @a size1 sized block, NULL on failure.
+ * @param big The block that is too big.
+ * @param size1 The size of the block to be returned (aligned).
+ */
+static shmemhdr *shheap_split(shmemhdr *big, size_t size1)
+{
+ shmemhdr *split;
+ SHHEAP_ASSERT(SHHEAP_ALIGN(sizeof(*big)) == sizeof(*big));
+ SHHEAP_ASSERT(big->magic == SHMEMHDR_MAGIC_FREE);
+ SHHEAP_ASSERT(!big->next2 || big->next2->magic == SHMEMHDR_MAGIC_FREE);
+ SHHEAP_ASSERT(!big->prev2 || big->prev2->magic == SHMEMHDR_MAGIC_FREE);
+
+ split = (shmemhdr *)((uint8_t *)(big + 1) + size1);
+ split->magic = SHMEMHDR_MAGIC_FREE;
+ split->size = big->size - size1 - sizeof(*split);
+ split->next = big->next;
+ split->prev = big;
+ split->next2 = big->next2;
+ split->prev2 = big->prev2;
+ split->psh = SHHEAP_POISON_NULL(0x54);
+ split->chunk = big->chunk;
+
+ if (big->next2)
+ big->next2->prev2 = split;
+ if (big->prev2)
+ big->prev2->next2 = split;
+ else
+ big->chunk->free_head = split;
+
+ big->magic = SHMEMHDR_MAGIC_USED;
+ big->next2 = big->prev2 = SHHEAP_POISON_NULL(0x41);
+
+ if (big->next)
+ big->next->prev = split;
+ big->next = split;
+ big->size = size1;
+
+ SHHEAP_CHECK_2();
+ return big;
+}
+
+/***
+ * Unlinks a free memory block.
+ * @param mem The block to unlink.
+ */
+static void shheap_unlink_free(shmemhdr *mem)
+{
+ if (mem->next2)
+ mem->next2->prev2 = mem->prev2;
+ if (mem->prev2)
+ mem->prev2->next2 = mem->next2;
+ else
+ mem->chunk->free_head = mem->next2;
+ mem->magic = SHMEMHDR_MAGIC_USED;
+ mem->next2 = mem->prev2 = SHHEAP_POISON_NULL(0x42);
+}
+
+#endif /* SHHEAP_IN_USE */
+
+
+/** free() */
+void sh_free(shinstance *psh, void *ptr)
+{
+#ifdef SHHEAP_IN_USE
+ shmemhdr *mem;
+ shmemhdr *right;
+ shmemhdr *left;
+ shmtxtmp tmp;
+
+ if (ptr)
+ mem = (shmemhdr *)ptr - 1;
+ else
+ return;
+
+ if (mem->magic != SHMEMHDR_MAGIC_USED)
+ {
+ SHHEAP_ASSERT(0);
+ return;
+ }
+
+ shmtx_enter(&g_sh_heap_mtx, &tmp);
+ SHHEAP_CHECK();
+
+ /* join right. */
+ right = mem->next;
+ if ( right
+ && right->magic == SHMEMHDR_MAGIC_FREE)
+ {
+ mem->next = right->next;
+ if (right->next)
+ right->next->prev = mem;
+
+ mem->next2 = right->next2;
+ if (right->next2)
+ right->next2->prev2 = mem;
+ mem->prev2 = right->prev2;
+ if (right->prev2)
+ mem->prev2->next2 = mem;
+ else
+ mem->chunk->free_head = mem;
+
+ mem->size += sizeof(*right) + right->size;
+ mem->magic = SHMEMHDR_MAGIC_FREE;
+ right->magic = ~SHMEMHDR_MAGIC_FREE;
+ mem->psh = SHHEAP_POISON_NULL(0x50);
+ SHHEAP_CHECK_2();
+ }
+
+ /* join left */
+ left = mem->prev;
+ if ( left
+ && left->magic == SHMEMHDR_MAGIC_FREE)
+ {
+ left->next = mem->next;
+ if (mem->next)
+ mem->next->prev = left;
+
+ if (mem->magic == SHMEMHDR_MAGIC_FREE)
+ {
+ if (mem->next2)
+ mem->next2->prev2 = mem->prev2;
+ if (mem->prev2)
+ mem->prev2->next2 = mem->next2;
+ else
+ mem->chunk->free_head = mem->next2;
+ }
+
+ left->size += sizeof(*mem) + mem->size;
+ mem->magic = ~SHMEMHDR_MAGIC_USED;
+ left->psh = SHHEAP_POISON_NULL(0x51);
+ }
+
+ /* insert as free if necessary */
+ else if (mem->magic == SHMEMHDR_MAGIC_USED)
+ {
+ mem->prev2 = NULL;
+ mem->next2 = mem->chunk->free_head;
+ if (mem->chunk->free_head)
+ mem->chunk->free_head->prev2 = mem;
+ mem->chunk->free_head = mem;
+ mem->magic = SHMEMHDR_MAGIC_FREE;
+ mem->psh = SHHEAP_POISON_NULL(0x52);
+ }
+
+ SHHEAP_CHECK();
+ shmtx_leave(&g_sh_heap_mtx, &tmp);
+#else
+ if (ptr)
+ free(ptr);
+ (void)psh;
+#endif
+}
+
+/** malloc() */
+void *sh_malloc(shinstance *psh, size_t size)
+{
+#ifdef SHHEAP_IN_USE
+ shmemchunk *chunk;
+ shmemhdr *mem;
+ shmtxtmp tmp;
+
+ size = SHHEAP_ALIGN(size);
+ SHHEAP_ASSERT(size);
+ if (!size)
+ size = SHHEAP_ALIGN(1);
+
+ shmtx_enter(&g_sh_heap_mtx, &tmp);
+ SHHEAP_CHECK();
+
+
+ /* Search for fitting block */
+ mem = NULL;
+ chunk = g_sh_heap;
+ while (chunk)
+ {
+ mem = chunk->free_head;
+ while (mem && mem->size < size)
+ mem = mem->next2;
+ if (mem)
+ break;
+ chunk = chunk->next;
+ }
+ if (mem)
+ {
+ /* split it, or just unlink it? */
+ if (mem->size - size > sizeof(*mem) * 2)
+ mem = shheap_split(mem, size);
+ else
+ shheap_unlink_free(mem);
+ }
+ else
+ {
+ /* no block found, try grow the heap. */
+ mem = shheap_grow(size);
+ if (!mem)
+ {
+ shmtx_leave(&g_sh_heap_mtx, &tmp);
+ return NULL;
+ }
+ }
+
+ SHHEAP_CHECK();
+ shmtx_leave(&g_sh_heap_mtx, &tmp);
+
+ mem->psh = SHHEAP_POISON_PSH(psh, 0x53);
+
+ return mem + 1;
+
+#else
+ (void)psh;
+ return malloc(size);
+#endif
+}
+
+/** calloc() */
+void *sh_calloc(shinstance *psh, size_t num, size_t item_size)
+{
+#ifdef SHHEAP_IN_USE
+ size_t size = num * item_size;
+ void *pv = sh_malloc(psh, size);
+ if (pv)
+ pv = memset(pv, '\0', size);
+ return pv;
+#else
+ (void)psh;
+ return calloc(num, item_size);
+#endif
+}
+
+/** realloc() */
+void *sh_realloc(shinstance *psh, void *old, size_t new_size)
+{
+#ifdef SHHEAP_IN_USE
+ void *pv;
+ if (new_size)
+ {
+ if (old)
+ {
+ shmemhdr *hdr = (shmemhdr *)old - 1;
+ if (hdr->size < new_size)
+ {
+ pv = sh_malloc(psh, new_size);
+ if (pv)
+ {
+ memcpy(pv, old, hdr->size);
+ sh_free(psh, old);
+ }
+ }
+ else
+ pv = old;
+ }
+ else
+ pv = sh_malloc(psh, new_size);
+ }
+ else
+ {
+ sh_free(psh, old);
+ pv = NULL;
+ }
+ return pv;
+#else
+ return realloc(old, new_size);
+#endif
+}
+
+/** strdup() */
+char *sh_strdup(shinstance *psh, const char *string)
+{
+ size_t len = strlen(string);
+ char *ret = sh_malloc(psh, len + 1);
+ if (ret)
+ memcpy(ret, string, len + 1);
+ return ret;
+}
+
+
diff --git a/src/kash/shheap.h b/src/kash/shheap.h
new file mode 100644
index 0000000..06d5343
--- /dev/null
+++ b/src/kash/shheap.h
@@ -0,0 +1,45 @@
+/* $Id: shheap.h 2416 2010-09-14 00:30:30Z bird $ */
+/** @file
+ * The shell memory heap methods.
+ */
+
+/*
+ * Copyright (c) 2009-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef ___shheap_h
+#define ___shheap_h
+
+#include "shtypes.h"
+
+/* heap */
+int shheap_init(void *phead);
+void *shheap_get_head(void);
+int shheap_fork_copy_to_child(void *);
+
+void *sh_malloc(shinstance *, size_t);
+void *sh_calloc(shinstance *, size_t, size_t);
+void *sh_realloc(shinstance *, void *, size_t);
+char *sh_strdup(shinstance *, const char *);
+void sh_free(shinstance *, void *);
+
+#endif
+
diff --git a/src/kash/shinstance.c b/src/kash/shinstance.c
new file mode 100644
index 0000000..868a3c3
--- /dev/null
+++ b/src/kash/shinstance.c
@@ -0,0 +1,2389 @@
+/* $Id: shinstance.c 3570 2022-07-09 14:42:02Z bird $ */
+/** @file
+ * The shell instance methods.
+ */
+
+/*
+ * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <string.h>
+#include <stdlib.h>
+#ifdef _MSC_VER
+# include <process.h>
+#else
+# include <unistd.h>
+# include <pwd.h>
+#endif
+#include "shinstance.h"
+
+#include "alias.h"
+#include "error.h"
+#include "input.h"
+#include "jobs.h"
+#include "memalloc.h"
+#include "nodes.h"
+#include "redir.h"
+#include "shell.h"
+#include "trap.h"
+
+#if K_OS == K_OS_WINDOWS
+# include <Windows.h>
+# include "nt/nt_child_inject_standard_handles.h"
+# ifdef SH_FORKED_MODE
+extern pid_t shfork_do(shinstance *psh); /* shforkA-win.asm */
+# endif
+#endif
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+#ifndef SH_FORKED_MODE
+/** Used by sh__exit/sh_thread_wrapper for passing zero via longjmp. */
+# define SH_EXIT_ZERO 0x0d15ea5e
+#endif
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifndef SH_FORKED_MODE
+/** Mutex serializing exec/spawn to prevent unwanted file inherting. */
+shmtx g_sh_exec_inherit_mtx;
+/** Mutex protecting g_sh_sts_free. */
+static shmtx g_sh_sts_mtx;
+/** List of free subshell status structure (saves CreateEvent calls). */
+static shsubshellstatus * volatile g_sh_sts_free = NULL;
+#endif
+/** The mutex protecting the the globals and some shell instance members (sigs). */
+static shmtx g_sh_mtx;
+/** The root shell instance. */
+static shinstance *g_sh_root;
+/** The first shell instance. */
+static shinstance *g_sh_head;
+/** The last shell instance. */
+static shinstance *g_sh_tail;
+/** The number of shells. */
+static int volatile g_num_shells;
+/* Statistics: Number of subshells spawned. */
+static KU64 g_stat_subshells = 0;
+/* Statistics: Number of program exec'ed. */
+static KU64 volatile g_stat_execs = 0;
+#if K_OS == K_OS_WINDOWS
+/* Statistics: Number of serialized exec calls. */
+static KU64 volatile g_stat_execs_serialized = 0;
+#endif
+/** Per signal state for determining a common denominator.
+ * @remarks defaults and unmasked actions aren't counted. */
+struct shsigstate
+{
+ /** The current signal action. */
+#ifndef _MSC_VER
+ struct sigaction sa;
+#else
+ struct
+ {
+ void (*sa_handler)(int);
+ int sa_flags;
+ shsigset_t sa_mask;
+ } sa;
+#endif
+ /** The number of restarts (siginterrupt / SA_RESTART). */
+ int num_restart;
+ /** The number of ignore handlers. */
+ int num_ignore;
+ /** The number of specific handlers. */
+ int num_specific;
+ /** The number of threads masking it. */
+ int num_masked;
+} g_sig_state[NSIG];
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifndef SH_FORKED_MODE
+static void shsubshellstatus_signal_and_release(shinstance *psh, int iExit);
+#endif
+
+
+
+int shmtx_init(shmtx *pmtx)
+{
+#if K_OS == K_OS_WINDOWS
+ typedef int mtxsizecheck[sizeof(CRITICAL_SECTION) + sizeof(KU64) <= sizeof(*pmtx) ? 2 : 0];
+ InitializeCriticalSection((CRITICAL_SECTION *)pmtx);
+#else
+ pmtx->b[0] = 0;
+#endif
+ pmtx->au64[SHMTX_MAGIC_IDX] = SHMTX_MAGIC;
+ return 0;
+}
+
+/**
+ * Safe to call more than once.
+ */
+void shmtx_delete(shmtx *pmtx)
+{
+ if (pmtx->au64[SHMTX_MAGIC_IDX] != SHMTX_MAGIC)
+ {
+#if K_OS == K_OS_WINDOWS
+ DeleteCriticalSection((CRITICAL_SECTION *)pmtx);
+#else
+ pmtx->b[0] = 0;
+#endif
+ pmtx->au64[SHMTX_MAGIC_IDX] = ~SHMTX_MAGIC;
+ }
+}
+
+void shmtx_enter(shmtx *pmtx, shmtxtmp *ptmp)
+{
+#if K_OS == K_OS_WINDOWS
+ EnterCriticalSection((CRITICAL_SECTION *)pmtx);
+ ptmp->i = 0x42;
+#else
+ pmtx->b[0] = 0;
+ ptmp->i = 0;
+#endif
+}
+
+void shmtx_leave(shmtx *pmtx, shmtxtmp *ptmp)
+{
+#if K_OS == K_OS_WINDOWS
+ kHlpAssert(ptmp->i == 0x42);
+ LeaveCriticalSection((CRITICAL_SECTION *)pmtx);
+ ptmp->i = 0x21;
+#else
+ pmtx->b[0] = 0;
+ ptmp->i = 432;
+#endif
+}
+
+/**
+ * Initialize globals in shinstance.c.
+ *
+ * Called when creating the rootshell and on windows after forking.
+ */
+void sh_init_globals(void)
+{
+ kHlpAssert(g_sh_mtx.au64[SHMTX_MAGIC_IDX] != SHMTX_MAGIC);
+ shmtx_init(&g_sh_mtx);
+#ifndef SH_FORKED_MODE
+ shmtx_init(&g_sh_exec_inherit_mtx);
+ shmtx_init(&g_sh_sts_mtx);
+#endif
+}
+
+
+/**
+ * Links the shell instance.
+ *
+ * @param psh The shell.
+ */
+static void sh_int_link(shinstance *psh)
+{
+ shmtxtmp tmp;
+ shmtx_enter(&g_sh_mtx, &tmp);
+
+ if (psh->rootshell)
+ g_sh_root = psh;
+ else
+ g_stat_subshells++;
+
+ psh->next = NULL;
+ psh->prev = g_sh_tail;
+ if (g_sh_tail)
+ g_sh_tail->next = psh;
+ else
+ g_sh_tail = g_sh_head = psh;
+ g_sh_tail = psh;
+
+ g_num_shells++;
+
+ psh->linked = 1;
+
+ shmtx_leave(&g_sh_mtx, &tmp);
+}
+
+/**
+ * Unlink the shell instance.
+ *
+ * @param psh The shell.
+ */
+static void sh_int_unlink(shinstance *psh)
+{
+ if (psh->linked)
+ {
+ shinstance *pshcur;
+ shmtxtmp tmp;
+ shmtx_enter(&g_sh_mtx, &tmp);
+
+ g_num_shells--;
+
+ if (g_sh_tail == psh)
+ g_sh_tail = psh->prev;
+ else
+ psh->next->prev = psh->prev;
+
+ if (g_sh_head == psh)
+ g_sh_head = psh->next;
+ else
+ psh->prev->next = psh->next;
+
+ if (g_sh_root == psh)
+ g_sh_root = NULL;
+
+ /* Orphan children: */
+ for (pshcur = g_sh_head; pshcur; pshcur = pshcur->next)
+ if (pshcur->parent == psh)
+ pshcur->parent = NULL;
+
+ shmtx_leave(&g_sh_mtx, &tmp);
+ }
+}
+
+/**
+ * Frees a string vector like environ or argv.
+ *
+ * @param psh The shell to associate the deallocations with.
+ * @param vecp Pointer to the vector pointer.
+ */
+static void sh_free_string_vector(shinstance *psh, char ***vecp)
+{
+ char **vec = *vecp;
+ if (vec)
+ {
+ char *str;
+ size_t i = 0;
+ while ((str = vec[i]) != NULL)
+ {
+ sh_free(psh, str);
+ vec[i] = NULL;
+ i++;
+ }
+
+ sh_free(psh, vec);
+ *vecp = NULL;
+ }
+}
+
+
+/**
+ * Destroys the shell instance.
+ *
+ * This will work on partially initialized instances (because I'm lazy).
+ *
+ * @param psh The shell instance to be destroyed.
+ * @note invalidate thread arguments.
+ */
+static void sh_destroy(shinstance *psh)
+{
+ unsigned left, i;
+
+ INTOFF;
+
+ sh_int_unlink(psh);
+
+ /* shinstance stuff: */
+ shfile_uninit(&psh->fdtab, psh->tracefd);
+ sh_free_string_vector(psh, &psh->shenviron);
+ sh_free(psh, psh->children);
+ psh->children = NULL;
+#ifndef SH_FORKED_MODE
+ /** @todo children. */
+ sh_free(psh, psh->threadarg);
+ psh->threadarg = NULL;
+ kHlpAssert(!psh->subshellstatus);
+ if (psh->subshellstatus)
+ {
+ shsubshellstatus_signal_and_release(psh, psh->exitstatus);
+ psh->subshellstatus = NULL;
+ }
+#endif
+
+ /* alias.c */
+ left = psh->aliases;
+ if (left > 0)
+ for (i = 0; i < K_ELEMENTS(psh->atab); i++)
+ {
+ struct alias *cur = psh->atab[i];
+ if (cur)
+ {
+ do
+ {
+ struct alias *next = cur->next;
+ sh_free(psh, cur->val);
+ sh_free(psh, cur->name);
+ sh_free(psh, cur);
+ cur = next;
+ left--;
+ } while (cur);
+ psh->atab[i] = NULL;
+ if (!left)
+ break;
+ }
+ }
+
+ /* cd.c */
+ sh_free(psh, psh->curdir);
+ psh->curdir = NULL;
+ sh_free(psh, psh->prevdir);
+ psh->prevdir = NULL;
+ psh->cdcomppath = NULL; /* stalloc */
+
+ /* eval.h */
+ if (psh->commandnamemalloc)
+ sh_free(psh, psh->commandname);
+ psh->commandname = NULL;
+ psh->cmdenviron = NULL;
+
+ /* expand.c */
+ if (psh->ifsfirst.next)
+ {
+ struct ifsregion *ifsrgn = psh->ifsfirst.next;
+ psh->ifsfirst.next = NULL;
+ do
+ {
+ struct ifsregion *next = ifsrgn->next;
+ sh_free(psh, ifsrgn);
+ ifsrgn = next;
+ } while (ifsrgn);
+ }
+ psh->ifslastp = NULL;
+ sh_free(psh, psh->expdir);
+ psh->expdir = NULL;
+
+ /* exec.h/exec.c */
+ psh->pathopt = NULL;
+ for (i = 0; i < CMDTABLESIZE; i++)
+ {
+ struct tblentry *cur = psh->cmdtable[i];
+ if (cur)
+ {
+ do
+ {
+ struct tblentry *next = cur->next;
+ if (cur->cmdtype == CMDFUNCTION)
+ {
+ freefunc(psh, cur->param.func);
+ cur->param.func = NULL;
+ }
+ sh_free(psh, cur);
+ cur = next;
+ } while (cur);
+ psh->cmdtable[i] = NULL;
+ }
+ }
+
+ /* input.h/c */
+ if (psh->parsefile != NULL)
+ {
+ popallfiles(psh);
+ while (psh->basepf.strpush)
+ popstring(psh);
+ }
+
+ /* jobs.h/c */
+ if (psh->jobtab)
+ {
+ int j = psh->njobs;
+ while (j-- > 0)
+ if (psh->jobtab[j].used && psh->jobtab[j].ps != &psh->jobtab[j].ps0)
+ {
+ sh_free(psh, psh->jobtab[j].ps);
+ psh->jobtab[j].ps = &psh->jobtab[j].ps0;
+ }
+ sh_free(psh, psh->jobtab);
+ psh->jobtab = NULL;
+ psh->njobs = 0;
+ }
+
+ /* myhistedit.h */
+#ifndef SMALL
+# error FIXME
+ History *hist;
+ EditLine *el;
+#endif
+
+ /* output.h */
+ if (psh->output.buf != NULL)
+ {
+ ckfree(psh, psh->output.buf);
+ psh->output.buf = NULL;
+ }
+ if (psh->errout.buf != NULL)
+ {
+ ckfree(psh, psh->errout.buf);
+ psh->errout.buf = NULL;
+ }
+ if (psh->memout.buf != NULL)
+ {
+ ckfree(psh, psh->memout.buf);
+ psh->memout.buf = NULL;
+ }
+
+ /* options.h */
+ if (psh->arg0malloc)
+ {
+ sh_free(psh, psh->arg0);
+ psh->arg0 = NULL;
+ }
+ if (psh->shellparam.malloc)
+ sh_free_string_vector(psh, &psh->shellparam.p);
+ sh_free_string_vector(psh, &psh->orgargv);
+ psh->argptr = NULL;
+ psh->minusc = NULL;
+
+ /* redir.c */
+ if (psh->redirlist)
+ {
+ struct redirtab *redir = psh->redirlist;
+ psh->redirlist = NULL;
+ do
+ {
+ struct redirtab *next = redir->next;
+ sh_free(psh, redir);
+ redir = next;
+ } while (redir);
+ }
+ psh->expfnames = NULL; /* stack alloc */
+
+ /* trap.c */
+ for (i = 0; i < K_ELEMENTS(psh->trap); i++)
+ if (!psh->trap[i])
+ { /* likely */ }
+ else
+ {
+ sh_free(psh, psh->trap[i]);
+ psh->trap[i] = NULL;
+ }
+
+ /* var.h */
+ if (psh->localvars)
+ {
+ struct localvar *lvar = psh->localvars;
+ psh->localvars = NULL;
+ do
+ {
+ struct localvar *next = lvar->next;
+ if (!(lvar->flags & VTEXTFIXED))
+ sh_free(psh, lvar->text);
+ sh_free(psh, lvar);
+ lvar = next;
+ } while (lvar);
+ }
+
+ for (i = 0; i < K_ELEMENTS(psh->vartab); i++)
+ {
+ struct var *var = psh->vartab[i];
+ if (!var)
+ { /* likely */ }
+ else
+ {
+ psh->vartab[i] = NULL;
+ do
+ {
+ struct var *next = var->next;
+ if (!(var->flags & (VTEXTFIXED | VSTACK)))
+ sh_free(psh, var->text);
+ if (!(var->flags & (VSTRFIXED | VSTRFIXED2)))
+ sh_free(psh, var);
+ var = next;
+ } while (var);
+ }
+ }
+
+ /*
+ * memalloc.c: Make sure we've gotten rid of all the stack memory.
+ */
+ if (psh->stackp != &psh->stackbase && psh->stackp)
+ {
+ struct stack_block *stackp = psh->stackp;
+ do
+ {
+ psh->stackp = stackp->prev;
+ sh_free(psh, stackp);
+ } while ((stackp = psh->stackp) != &psh->stackbase && stackp);
+ }
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR //bp msvcr100!_wassert
+ if (psh->pstack)
+ {
+ if (psh->pstacksize > 0)
+ pstackpop(psh, 0);
+ sh_free(psh, psh->pstack);
+ psh->pstack = NULL;
+ }
+ sh_free(psh, psh->freepstack);
+ psh->freepstack = NULL;
+#endif
+ psh->markp = NULL;
+
+ /*
+ * Finally get rid of tracefd and then free the shell:
+ */
+ shfile_uninit(&psh->fdtab, -1);
+
+ memset(psh, 0, sizeof(*psh));
+ sh_free(NULL, psh);
+}
+
+/**
+ * Clones a string vector like environ or argv.
+ *
+ * @returns 0 on success, -1 and errno on failure.
+ * @param psh The shell to associate the allocations with.
+ * @param dstp Where to store the clone.
+ * @param src The vector to be cloned.
+ */
+static int sh_clone_string_vector(shinstance *psh, char ***dstp, char **src)
+{
+ char **dst;
+ size_t items;
+
+ /* count first */
+ items = 0;
+ while (src[items])
+ items++;
+
+ /* alloc clone array. */
+ *dstp = dst = sh_malloc(psh, sizeof(*dst) * (items + 1));
+ if (!dst)
+ return -1;
+
+ /* copy the items */
+ dst[items] = NULL;
+ while (items-- > 0)
+ {
+ dst[items] = sh_strdup(psh, src[items]);
+ if (!dst[items])
+ {
+ /* allocation error, clean up. */
+ while (dst[++items])
+ sh_free(psh, dst[items]);
+ sh_free(psh, dst);
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Creates a shell instance, caller must link it.
+ *
+ * @param inherit The shell to inherit from, or NULL if root.
+ * @param argv The argument vector.
+ * @param envp The environment vector.
+ * @param parentfdtab File table to inherit from, NULL if root.
+ *
+ * @returns pointer to root shell on success, NULL on failure.
+ */
+static shinstance *sh_create_shell_common(char **argv, char **envp, shfdtab *parentfdtab)
+{
+ shinstance *psh;
+
+ /*
+ * The allocations.
+ */
+ psh = sh_calloc(NULL, sizeof(*psh), 1);
+ if (psh)
+ {
+ /* Init it enough for sh_destroy() to not get upset: */
+ /* ... */
+
+ /* Call the basic initializers. */
+ if ( !sh_clone_string_vector(psh, &psh->shenviron, envp)
+ && !sh_clone_string_vector(psh, &psh->orgargv, argv)
+ && !shfile_init(&psh->fdtab, parentfdtab))
+ {
+ unsigned i;
+
+ /*
+ * The special stuff.
+ */
+#ifdef _MSC_VER
+ psh->pgid = psh->pid = _getpid();
+#else
+ psh->pid = getpid();
+ psh->pgid = getpgid(0);
+#endif
+
+ /*sh_sigemptyset(&psh->sigrestartset);*/
+ for (i = 0; i < K_ELEMENTS(psh->sigactions); i++)
+ psh->sigactions[i].sh_handler = SH_SIG_UNK;
+#if defined(_MSC_VER)
+ sh_sigemptyset(&psh->sigmask);
+#else
+ sigprocmask(SIG_SETMASK, NULL, &psh->sigmask);
+#endif
+
+ /*
+ * State initialization.
+ */
+ /* cd.c */
+ psh->getpwd_first = 1;
+
+ /* exec */
+ psh->builtinloc = -1;
+
+ /* memalloc.c */
+ psh->stacknleft = MINSIZE;
+ psh->herefd = -1;
+ psh->stackp = &psh->stackbase;
+ psh->stacknxt = psh->stackbase.space;
+
+ /* input.c */
+ psh->plinno = 1;
+ psh->init_editline = 0;
+ psh->parsefile = &psh->basepf;
+
+ /* output.c */
+ psh->output.bufsize = OUTBUFSIZ;
+ psh->output.fd = 1;
+ psh->output.psh = psh;
+ psh->errout.bufsize = 100;
+ psh->errout.fd = 2;
+ psh->errout.psh = psh;
+ psh->memout.fd = MEM_OUT;
+ psh->memout.psh = psh;
+ psh->out1 = &psh->output;
+ psh->out2 = &psh->errout;
+
+ /* jobs.c */
+ psh->backgndpid = -1;
+#if JOBS
+ psh->curjob = -1;
+#else
+# error asdf
+#endif
+ psh->ttyfd = -1;
+
+ /* show.c */
+ psh->tracefd = -1;
+ return psh;
+ }
+
+ sh_destroy(psh);
+ }
+ return NULL;
+}
+
+/**
+ * Creates the root shell instance.
+ *
+ * @param argv The argument vector.
+ * @param envp The environment vector.
+ *
+ * @returns pointer to root shell on success, NULL on failure.
+ */
+shinstance *sh_create_root_shell(char **argv, char **envp)
+{
+ shinstance *psh;
+
+ sh_init_globals();
+
+ psh = sh_create_shell_common(argv, envp, NULL /*parentfdtab*/);
+ if (psh)
+ {
+ sh_int_link(psh);
+ return psh;
+ }
+ return NULL;
+}
+
+#ifndef SH_FORKED_MODE
+
+/**
+ * Does the inherting from the parent shell instance.
+ */
+static void sh_inherit_from_parent(shinstance *psh, shinstance *inherit)
+{
+ /*
+ * Make sure we can use TRACE/TRACE2 for logging here.
+ */
+#ifdef DEBUG
+ /* show.c */
+ psh->tracefd = inherit->tracefd;
+ /* options.c */
+ debug(psh) = debug(inherit);
+#endif
+
+ /*
+ * Do the rest of the inheriting.
+ */
+ psh->parent = inherit;
+ psh->pgid = inherit->pgid;
+
+ psh->sigmask = psh->sigmask;
+ /** @todo sigactions? */
+ /// @todo suppressint?
+
+ /* alises: */
+ subshellinitalias(psh, inherit);
+
+ /* cd.c */
+ psh->getpwd_first = inherit->getpwd_first;
+ if (inherit->curdir)
+ psh->curdir = savestr(psh, inherit->curdir);
+ if (inherit->prevdir)
+ psh->prevdir = savestr(psh, inherit->prevdir);
+
+ /* eval.h */
+ /* psh->commandname - see subshellinitoptions */
+ psh->exitstatus = inherit->exitstatus; /// @todo ??
+ psh->back_exitstatus = inherit->back_exitstatus; /// @todo ??
+ psh->funcnest = inherit->funcnest;
+ psh->evalskip = inherit->evalskip; /// @todo ??
+ psh->skipcount = inherit->skipcount; /// @todo ??
+
+ /* exec.c */
+ subshellinitexec(psh, inherit);
+
+ /* input.h/input.c - only for the parser and anyway forkchild calls closescript(). */
+
+ /* jobs.h - should backgndpid be -1 in subshells? */
+
+ /* jobs.c - */
+ psh->jobctl = inherit->jobctl; /// @todo ??
+ psh->initialpgrp = inherit->initialpgrp;
+ psh->ttyfd = inherit->ttyfd;
+ /** @todo copy jobtab so the 'jobs' command can be run in a subshell.
+ * Better, make it follow the parent chain and skip the copying. Will
+ * require some kind of job locking. */
+
+ /* mail.c - nothing (for now at least) */
+
+ /* main.h */
+ psh->rootpid = inherit->rootpid;
+ psh->psh_rootshell = inherit->psh_rootshell;
+
+ /* memalloc.h / memalloc.c - nothing. */
+
+ /* myhistedit.h */ /** @todo copy history? Do we need to care? */
+
+ /* output.h */ /** @todo not sure this is possible/relevant for subshells */
+ psh->output.fd = inherit->output.fd;
+ psh->errout.fd = inherit->errout.fd;
+ if (inherit->out1 == &inherit->memout)
+ psh->out1 = &psh->memout;
+ if (inherit->out2 == &inherit->memout)
+ psh->out2 = &psh->memout;
+
+ /* options.h */
+ subshellinitoptions(psh, inherit);
+
+ /* parse.h/parse.c */
+ psh->whichprompt = inherit->whichprompt;
+ /* tokpushback, doprompt and needprompt shouldn't really matter, parsecmd resets thems. */
+ /* The rest are internal to the parser, as I see them, and can be ignored. */
+
+ /* redir.c */
+ subshellinitredir(psh, inherit);
+
+ /* trap.h / trap.c */ /** @todo we don't carry pendingsigs to the subshell, right? */
+ subshellinittrap(psh, inherit);
+
+ /* var.h */
+ subshellinitvar(psh, inherit);
+}
+
+/**
+ * Creates a child shell instance.
+ *
+ * @param inherit The shell to inherit from.
+ *
+ * @returns pointer to root shell on success, NULL on failure.
+ */
+shinstance *sh_create_child_shell(shinstance *inherit)
+{
+ shinstance *psh = sh_create_shell_common(inherit->orgargv, inherit->shenviron, &inherit->fdtab);
+ if (psh)
+ {
+ /* Fake a pid for the child: */
+ static unsigned volatile s_cShells = 0;
+ int const iSubShell = ++s_cShells;
+ psh->pid = SHPID_MAKE(SHPID_GET_PID(inherit->pid), iSubShell);
+
+ sh_inherit_from_parent(psh, inherit);
+
+ /* link it */
+ sh_int_link(psh);
+ return psh;
+ }
+ return NULL;
+}
+
+#endif /* !SH_FORKED_MODE */
+
+/** getenv() */
+char *sh_getenv(shinstance *psh, const char *var)
+{
+ size_t len;
+ int i = 0;
+
+ if (!var)
+ return NULL;
+
+ len = strlen(var);
+ i = 0;
+ while (psh->shenviron[i])
+ {
+ const char *item = psh->shenviron[i];
+ if ( !strncmp(item, var, len)
+ && item[len] == '=')
+ return (char *)item + len + 1;
+ i++;
+ }
+
+ return NULL;
+}
+
+char **sh_environ(shinstance *psh)
+{
+ return psh->shenviron;
+}
+
+const char *sh_gethomedir(shinstance *psh, const char *user)
+{
+ const char *ret = NULL;
+
+#ifdef _MSC_VER
+ ret = sh_getenv(psh, "HOME");
+ if (!ret)
+ ret = sh_getenv(psh, "USERPROFILE");
+#else
+ struct passwd *pwd = getpwnam(user); /** @todo use getpwdnam_r */
+ (void)psh;
+ ret = pwd ? pwd->pw_dir : NULL;
+#endif
+
+ return ret;
+}
+
+/**
+ * Lazy initialization of a signal state, globally.
+ *
+ * @param psh The shell doing the lazy work.
+ * @param signo The signal (valid).
+ */
+static void sh_int_lazy_init_sigaction(shinstance *psh, int signo)
+{
+ if (psh->sigactions[signo].sh_handler == SH_SIG_UNK)
+ {
+ shmtxtmp tmp;
+ shmtx_enter(&g_sh_mtx, &tmp);
+
+ if (psh->sigactions[signo].sh_handler == SH_SIG_UNK)
+ {
+ shsigaction_t shold;
+ shinstance *cur;
+#ifndef _MSC_VER
+ struct sigaction old;
+ if (!sigaction(signo, NULL, &old))
+ {
+ /* convert */
+ shold.sh_flags = old.sa_flags;
+ shold.sh_mask = old.sa_mask;
+ if (old.sa_handler == SIG_DFL)
+ shold.sh_handler = SH_SIG_DFL;
+ else
+ {
+ kHlpAssert(old.sa_handler == SIG_IGN);
+ shold.sh_handler = SH_SIG_IGN;
+ }
+ }
+ else
+#endif
+ {
+ /* fake */
+#ifndef _MSC_VER
+ kHlpAssert(0);
+ old.sa_handler = SIG_DFL;
+ old.sa_flags = 0;
+ sigemptyset(&shold.sh_mask);
+ sigaddset(&shold.sh_mask, signo);
+#endif
+ shold.sh_flags = 0;
+ sh_sigemptyset(&shold.sh_mask);
+ sh_sigaddset(&shold.sh_mask, signo);
+ shold.sh_handler = SH_SIG_DFL;
+ }
+
+ /* update globals */
+#ifndef _MSC_VER
+ g_sig_state[signo].sa = old;
+#else
+ g_sig_state[signo].sa.sa_handler = SIG_DFL;
+ g_sig_state[signo].sa.sa_flags = 0;
+ g_sig_state[signo].sa.sa_mask = shold.sh_mask;
+#endif
+ TRACE2((psh, "sh_int_lazy_init_sigaction: signo=%d:%s sa_handler=%p sa_flags=%#x\n",
+ signo, sys_signame[signo], g_sig_state[signo].sa.sa_handler, g_sig_state[signo].sa.sa_flags));
+
+ /* update all shells */
+ for (cur = g_sh_head; cur; cur = cur->next)
+ {
+ kHlpAssert(cur->sigactions[signo].sh_handler == SH_SIG_UNK);
+ cur->sigactions[signo] = shold;
+ }
+ }
+
+ shmtx_leave(&g_sh_mtx, &tmp);
+ }
+}
+
+/**
+ * Perform the default signal action on the shell.
+ *
+ * @param psh The shell instance.
+ * @param signo The signal.
+ */
+static void sh_sig_do_default(shinstance *psh, int signo)
+{
+ /** @todo */
+}
+
+/**
+ * Deliver a signal to a shell.
+ *
+ * @param psh The shell instance.
+ * @param pshDst The shell instance to signal.
+ * @param signo The signal.
+ * @param locked Whether we're owning the lock or not.
+ */
+static void sh_sig_do_signal(shinstance *psh, shinstance *pshDst, int signo, int locked)
+{
+ shsig_t pfn = pshDst->sigactions[signo].sh_handler;
+ if (pfn == SH_SIG_UNK)
+ {
+ sh_int_lazy_init_sigaction(pshDst, signo);
+ pfn = pshDst->sigactions[signo].sh_handler;
+ }
+
+ if (pfn == SH_SIG_DFL)
+ sh_sig_do_default(pshDst, signo);
+ else if (pfn == SH_SIG_IGN)
+ /* ignore it */;
+ else
+ {
+ kHlpAssert(pfn != SH_SIG_ERR);
+ pfn(pshDst, signo);
+ }
+ (void)locked;
+}
+
+/**
+ * Handler for external signals.
+ *
+ * @param signo The signal.
+ */
+static void sh_sig_common_handler(int signo)
+{
+ shinstance *psh;
+
+/* fprintf(stderr, "sh_sig_common_handler: signo=%d:%s\n", signo, sys_signame[signo]); */
+
+#ifdef _MSC_VER
+ /* We're treating SIGBREAK as if it was SIGINT for now: */
+ if (signo == SIGBREAK)
+ signo = SIGINT;
+#endif
+
+ /*
+ * No need to take locks if there is only one shell.
+ * Since this will be the initial case, just avoid the deadlock
+ * hell for a litte while...
+ */
+ if (g_num_shells <= 1)
+ {
+ psh = g_sh_head;
+ if (psh)
+ sh_sig_do_signal(NULL, psh, signo, 0 /* no lock */);
+ }
+ else
+ {
+ shmtxtmp tmp;
+ shmtx_enter(&g_sh_mtx, &tmp);
+
+ /** @todo signal focus chain or something? Atm there will only be one shell,
+ * so it's not really important until we go threaded for real... */
+ psh = g_sh_tail;
+ while (psh != NULL)
+ {
+ sh_sig_do_signal(NULL, psh, signo, 1 /* locked */);
+ psh = psh->prev;
+ }
+
+ shmtx_leave(&g_sh_mtx, &tmp);
+ }
+}
+
+int sh_sigaction(shinstance *psh, int signo, const struct shsigaction *newp, struct shsigaction *oldp)
+{
+ if (newp)
+ TRACE2((psh, "sh_sigaction: signo=%d:%s newp=%p:{.sh_handler=%p, .sh_flags=%#x} oldp=%p\n",
+ signo, sys_signame[signo], newp, newp->sh_handler, newp->sh_flags, oldp));
+ else
+ TRACE2((psh, "sh_sigaction: signo=%d:%s newp=NULL oldp=%p\n", signo, sys_signame[signo], oldp));
+
+ /*
+ * Input validation.
+ */
+ if (signo >= NSIG || signo <= 0)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /*
+ * Make sure our data is correct.
+ */
+ sh_int_lazy_init_sigaction(psh, signo);
+
+ /*
+ * Get the old one if requested.
+ */
+ if (oldp)
+ *oldp = psh->sigactions[signo];
+
+ /*
+ * Set the new one if it has changed.
+ *
+ * This will be attempted coordinated with the other signal handlers so
+ * that we can arrive at a common denominator.
+ */
+ if ( newp
+ && memcmp(&psh->sigactions[signo], newp, sizeof(*newp)))
+ {
+ shmtxtmp tmp;
+ shmtx_enter(&g_sh_mtx, &tmp);
+
+ /* Undo the accounting for the current entry. */
+ if (psh->sigactions[signo].sh_handler == SH_SIG_IGN)
+ g_sig_state[signo].num_ignore--;
+ else if (psh->sigactions[signo].sh_handler != SH_SIG_DFL)
+ g_sig_state[signo].num_specific--;
+ if (psh->sigactions[signo].sh_flags & SA_RESTART)
+ g_sig_state[signo].num_restart--;
+
+ /* Set the new entry. */
+ psh->sigactions[signo] = *newp;
+
+ /* Add the bits for the new action entry. */
+ if (psh->sigactions[signo].sh_handler == SH_SIG_IGN)
+ g_sig_state[signo].num_ignore++;
+ else if (psh->sigactions[signo].sh_handler != SH_SIG_DFL)
+ g_sig_state[signo].num_specific++;
+ if (psh->sigactions[signo].sh_flags & SA_RESTART)
+ g_sig_state[signo].num_restart++;
+
+ /*
+ * Calc new common action.
+ *
+ * This is quit a bit ASSUMPTIVE about the limited use. We will not
+ * bother synching the mask, and we pretend to care about SA_RESTART.
+ * The only thing we really actually care about is the sh_handler.
+ *
+ * On second though, it's possible we should just tie this to the root
+ * shell since it only really applies to external signal ...
+ */
+ if ( g_sig_state[signo].num_specific
+ || g_sig_state[signo].num_ignore != g_num_shells)
+ g_sig_state[signo].sa.sa_handler = sh_sig_common_handler;
+ else if (g_sig_state[signo].num_ignore)
+ g_sig_state[signo].sa.sa_handler = SIG_IGN;
+ else
+ g_sig_state[signo].sa.sa_handler = SIG_DFL;
+ g_sig_state[signo].sa.sa_flags = psh->sigactions[signo].sh_flags & SA_RESTART;
+
+ TRACE2((psh, "sh_sigaction: setting signo=%d:%s to {.sa_handler=%p, .sa_flags=%#x}\n",
+ signo, sys_signame[signo], g_sig_state[signo].sa.sa_handler, g_sig_state[signo].sa.sa_flags));
+#ifdef _MSC_VER
+ /* Throw SIGBREAK in with SIGINT for now. */
+ if (signo == SIGINT)
+ signal(SIGBREAK, g_sig_state[signo].sa.sa_handler);
+
+ if (signal(signo, g_sig_state[signo].sa.sa_handler) == SIG_ERR)
+ {
+ TRACE2((psh, "sh_sigaction: SIG_ERR, errno=%d signo=%d\n", errno, signo));
+ if ( signo != SIGHUP /* whatever */
+ && signo != SIGQUIT
+ && signo != SIGPIPE
+ && signo != SIGTTIN
+ && signo != SIGTSTP
+ && signo != SIGTTOU
+ && signo != SIGCONT)
+ kHlpAssert(0);
+ }
+#else
+ if (sigaction(signo, &g_sig_state[signo].sa, NULL))
+ kHlpAssert(0);
+#endif
+
+ shmtx_leave(&g_sh_mtx, &tmp);
+ }
+
+ return 0;
+}
+
+shsig_t sh_signal(shinstance *psh, int signo, shsig_t handler)
+{
+ shsigaction_t sa;
+ shsig_t ret;
+
+ /*
+ * Implementation using sh_sigaction.
+ */
+ if (sh_sigaction(psh, signo, NULL, &sa))
+ return SH_SIG_ERR;
+
+ ret = sa.sh_handler;
+ sa.sh_flags &= SA_RESTART;
+ sa.sh_handler = handler;
+ sh_sigemptyset(&sa.sh_mask);
+ sh_sigaddset(&sa.sh_mask, signo); /* ?? */
+ if (sh_sigaction(psh, signo, &sa, NULL))
+ return SH_SIG_ERR;
+
+ return ret;
+}
+
+int sh_siginterrupt(shinstance *psh, int signo, int interrupt)
+{
+ shsigaction_t sa;
+ int oldflags = 0;
+
+ /*
+ * Implementation using sh_sigaction.
+ */
+ if (sh_sigaction(psh, signo, NULL, &sa))
+ return -1;
+ oldflags = sa.sh_flags;
+ if (interrupt)
+ sa.sh_flags &= ~SA_RESTART;
+ else
+ sa.sh_flags |= ~SA_RESTART;
+ if (!((oldflags ^ sa.sh_flags) & SA_RESTART))
+ return 0; /* unchanged. */
+
+ return sh_sigaction(psh, signo, &sa, NULL);
+}
+
+void sh_sigemptyset(shsigset_t *setp)
+{
+ memset(setp, 0, sizeof(*setp));
+}
+
+void sh_sigfillset(shsigset_t *setp)
+{
+ memset(setp, 0xff, sizeof(*setp));
+}
+
+void sh_sigaddset(shsigset_t *setp, int signo)
+{
+#ifdef _MSC_VER
+ *setp |= 1U << signo;
+#else
+ sigaddset(setp, signo);
+#endif
+}
+
+void sh_sigdelset(shsigset_t *setp, int signo)
+{
+#ifdef _MSC_VER
+ *setp &= ~(1U << signo);
+#else
+ sigdelset(setp, signo);
+#endif
+}
+
+int sh_sigismember(shsigset_t const *setp, int signo)
+{
+#ifdef _MSC_VER
+ return !!(*setp & (1U << signo));
+#else
+ return !!sigismember(setp, signo);
+#endif
+}
+
+int sh_sigprocmask(shinstance *psh, int operation, shsigset_t const *newp, shsigset_t *oldp)
+{
+ int rc;
+
+ if ( operation != SIG_BLOCK
+ && operation != SIG_UNBLOCK
+ && operation != SIG_SETMASK)
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+#if defined(SH_FORKED_MODE) && !defined(_MSC_VER)
+ rc = sigprocmask(operation, newp, oldp);
+ if (!rc && newp)
+ psh->sigmask = *newp;
+
+#else
+ if (oldp)
+ *oldp = psh->sigmask;
+ if (newp)
+ {
+ /* calc the new mask */
+ shsigset_t mask = psh->sigmask;
+ switch (operation)
+ {
+ case SIG_BLOCK:
+ for (rc = 0; rc < NSIG; rc++)
+ if (sh_sigismember(newp, rc))
+ sh_sigaddset(&mask, rc);
+ break;
+ case SIG_UNBLOCK:
+ for (rc = 0; rc < NSIG; rc++)
+ if (sh_sigismember(newp, rc))
+ sh_sigdelset(&mask, rc);
+ break;
+ case SIG_SETMASK:
+ mask = *newp;
+ break;
+ }
+
+# if defined(_MSC_VER)
+ rc = 0;
+# else
+ rc = sigprocmask(operation, &mask, NULL);
+ if (!rc)
+# endif
+ psh->sigmask = mask;
+ }
+
+#endif
+ return rc;
+}
+
+SH_NORETURN_1 void sh_abort(shinstance *psh)
+{
+ shsigset_t set;
+ TRACE2((psh, "sh_abort\n"));
+
+ /* block other async signals */
+ sh_sigfillset(&set);
+ sh_sigdelset(&set, SIGABRT);
+ sh_sigprocmask(psh, SIG_SETMASK, &set, NULL);
+
+ sh_sig_do_signal(psh, psh, SIGABRT, 0 /* no lock */);
+
+ /** @todo die in a nicer manner. */
+ *(char *)1 = 3;
+
+ TRACE2((psh, "sh_abort returns!\n"));
+ (void)psh;
+ abort();
+}
+
+void sh_raise_sigint(shinstance *psh)
+{
+ TRACE2((psh, "sh_raise(SIGINT)\n"));
+
+ sh_sig_do_signal(psh, psh, SIGINT, 0 /* no lock */);
+
+ TRACE2((psh, "sh_raise(SIGINT) returns\n"));
+}
+
+int sh_kill(shinstance *psh, shpid pid, int signo)
+{
+ shinstance *pshDst;
+ shmtxtmp tmp;
+ int rc;
+
+ /*
+ * Self or any of the subshells?
+ */
+ shmtx_enter(&g_sh_mtx, &tmp);
+
+ pshDst = g_sh_tail;
+ while (pshDst != NULL)
+ {
+ if (pshDst->pid == pid)
+ {
+ TRACE2((psh, "sh_kill(%" SHPID_PRI ", %d): pshDst=%p\n", pid, signo, pshDst));
+ sh_sig_do_signal(psh, pshDst, signo, 1 /* locked */);
+
+ shmtx_leave(&g_sh_mtx, &tmp);
+ return 0;
+ }
+ pshDst = pshDst->prev;
+ }
+
+ shmtx_leave(&g_sh_mtx, &tmp);
+
+ /*
+ * Some other process, call kill where possible
+ */
+#ifdef _MSC_VER
+ errno = ENOSYS;
+ rc = -1;
+#elif defined(SH_FORKED_MODE)
+/* fprintf(stderr, "kill(%d, %d)\n", pid, signo);*/
+ rc = kill(pid, signo);
+#else
+# error "PORT ME?"
+#endif
+
+ TRACE2((psh, "sh_kill(%d, %d) -> %d [%d]\n", pid, signo, rc, errno));
+ return rc;
+}
+
+int sh_killpg(shinstance *psh, shpid pgid, int signo)
+{
+ shinstance *pshDst;
+ shmtxtmp tmp;
+ int rc;
+
+ /*
+ * Self or any of the subshells?
+ */
+ shmtx_enter(&g_sh_mtx, &tmp);
+
+ pshDst = g_sh_tail;
+ while (pshDst != NULL)
+ {
+ if (pshDst->pgid == pgid)
+ {
+ TRACE2((psh, "sh_killpg(%" SHPID_PRI ", %d): pshDst=%p\n", pgid, signo, pshDst));
+ sh_sig_do_signal(psh, pshDst, signo, 1 /* locked */);
+
+ shmtx_leave(&g_sh_mtx, &tmp);
+ return 0;
+ }
+ pshDst = pshDst->prev;
+ }
+
+ shmtx_leave(&g_sh_mtx, &tmp);
+
+#ifdef _MSC_VER
+ errno = ENOSYS;
+ rc = -1;
+#elif defined(SH_FORKED_MODE)
+ //fprintf(stderr, "killpg(%d, %d)\n", pgid, signo);
+ rc = killpg(pgid, signo);
+#else
+# error "PORTME?"
+#endif
+
+ TRACE2((psh, "sh_killpg(%" SHPID_PRI ", %d) -> %d [%d]\n", pgid, signo, rc, errno));
+ (void)psh;
+ return rc;
+}
+
+clock_t sh_times(shinstance *psh, shtms *tmsp)
+{
+#ifdef _MSC_VER
+ errno = ENOSYS;
+ return (clock_t)-1;
+#elif defined(SH_FORKED_MODE)
+ (void)psh;
+ return times(tmsp);
+#else
+# error "PORTME"
+#endif
+}
+
+int sh_sysconf_clk_tck(void)
+{
+#ifdef _MSC_VER
+ return CLK_TCK;
+#else
+ return sysconf(_SC_CLK_TCK);
+#endif
+}
+
+#ifndef SH_FORKED_MODE
+
+/**
+ * Retains a reference to a subshell status structure.
+ */
+static unsigned shsubshellstatus_retain(shsubshellstatus *sts)
+{
+ unsigned refs = sh_atomic_dec(&sts->refs);
+ kHlpAssert(refs > 1);
+ kHlpAssert(refs < 16);
+ return refs;
+}
+
+/**
+ * Releases a reference to a subshell status structure.
+ */
+static unsigned shsubshellstatus_release(shinstance *psh, shsubshellstatus *sts)
+{
+ unsigned refs = sh_atomic_dec(&sts->refs);
+ kHlpAssert(refs < ~(unsigned)0/4);
+ if (refs == 0)
+ {
+ shmtxtmp tmp;
+ shmtx_enter(&g_sh_sts_mtx, &tmp);
+ sts->next = g_sh_sts_free;
+ g_sh_sts_free = sts;
+ shmtx_leave(&g_sh_sts_mtx, &tmp);
+ }
+ return refs;
+}
+
+/**
+ * Creates a subshell status structure.
+ */
+static shsubshellstatus *shsubshellstatus_create(shinstance *psh, int refs)
+{
+ shsubshellstatus *sts;
+
+ /* Check the free list: */
+ if (g_sh_sts_free)
+ {
+ shmtxtmp tmp;
+ shmtx_enter(&g_sh_sts_mtx, &tmp);
+ sts = g_sh_sts_free;
+ if (sts)
+ g_sh_sts_free = sts->next;
+ shmtx_leave(&g_sh_sts_mtx, &tmp);
+ }
+ else
+ sts = NULL;
+ if (sts)
+ {
+# if K_OS == K_OS_WINDOWS
+ BOOL rc = ResetEvent((HANDLE)sts->towaiton);
+ kHlpAssert(rc); K_NOREF(rc);
+# endif
+ }
+ else
+ {
+ /* Create a new one: */
+ sts = (shsubshellstatus *)sh_malloc(psh, sizeof(*sts));
+ if (!sts)
+ return NULL;
+# if K_OS == K_OS_WINDOWS
+ sts->towaiton = (void *)CreateEventW(NULL /*noinherit*/, TRUE /*fManualReset*/,
+ FALSE /*fInitialState*/, NULL /*pszName*/);
+ if (!sts->towaiton)
+ {
+ kHlpAssert(0);
+ sh_free(psh, sts);
+ return NULL;
+ }
+# endif
+ }
+
+ /* Initialize it: */
+ sts->refs = refs;
+ sts->status = 999999;
+ sts->done = 0;
+ sts->next = NULL;
+# if K_OS == K_OS_WINDOWS
+ sts->hThread = 0;
+# endif
+ return sts;
+}
+
+/**
+ * If we have a subshell status structure, signal and release it.
+ */
+static void shsubshellstatus_signal_and_release(shinstance *psh, int iExit)
+{
+ shsubshellstatus *sts = psh->subshellstatus;
+ if (sts)
+ {
+ BOOL rc;
+ HANDLE hThread;
+
+ sts->status = W_EXITCODE(iExit, 0);
+ sts->done = K_TRUE;
+ rc = SetEvent((HANDLE)sts->towaiton); kHlpAssert(rc); K_NOREF(rc);
+
+ hThread = (HANDLE)sts->hThread;
+ sts->hThread = 0;
+ rc = CloseHandle(hThread); kHlpAssert(rc);
+
+ shsubshellstatus_release(psh, sts);
+ psh->subshellstatus = NULL;
+ }
+}
+
+
+#endif /* !SH_FORKED_MODE */
+
+/**
+ * Adds a child to the shell
+ *
+ * @returns 0 on success, on failure -1 and errno set to ENOMEM.
+ *
+ * @param psh The shell instance.
+ * @param pid The child pid.
+ * @param hChild Windows child wait handle (process if sts is NULL).
+ * @param sts Subshell status structure, NULL if progress.
+ */
+int sh_add_child(shinstance *psh, shpid pid, void *hChild, struct shsubshellstatus *sts)
+{
+ /* get a free table entry. */
+ unsigned i = psh->num_children++;
+ if (!(i % 32))
+ {
+ void *ptr = sh_realloc(psh, psh->children, sizeof(*psh->children) * (i + 32));
+ if (!ptr)
+ {
+ psh->num_children--;
+ errno = ENOMEM;
+ return -1;
+ }
+ psh->children = ptr;
+ }
+
+ /* add it */
+ psh->children[i].pid = pid;
+#if K_OS == K_OS_WINDOWS
+ psh->children[i].hChild = hChild;
+#endif
+#ifndef SH_FORKED_MODE
+ psh->children[i].subshellstatus = sts;
+#endif
+ (void)hChild; (void)sts;
+ return 0;
+}
+
+#ifdef SH_FORKED_MODE
+
+pid_t sh_fork(shinstance *psh)
+{
+ pid_t pid;
+ TRACE2((psh, "sh_fork\n"));
+
+#if K_OS == K_OS_WINDOWS //&& defined(SH_FORKED_MODE)
+ pid = shfork_do(psh);
+
+#elif defined(SH_FORKED_MODE)
+# ifdef _MSC_VER
+ pid = -1;
+ errno = ENOSYS;
+# else
+ pid = fork();
+# endif
+
+#else
+
+#endif
+
+ /* child: update the pid and zap the children array */
+ if (!pid)
+ {
+# ifdef _MSC_VER
+ psh->pid = _getpid();
+# else
+ psh->pid = getpid();
+# endif
+ psh->num_children = 0;
+ }
+
+ TRACE2((psh, "sh_fork -> %d [%d]\n", pid, errno));
+ (void)psh;
+ return pid;
+}
+
+#else /* !SH_FORKED_MODE */
+
+# ifdef _MSC_VER
+/** Thread wrapper procedure. */
+static unsigned __stdcall sh_thread_wrapper(void *user)
+{
+ shinstance * volatile volpsh = (shinstance *)user;
+ shinstance *psh = (shinstance *)user;
+ struct jmploc exitjmp;
+ int iExit;
+
+ /* Update the TID and PID (racing sh_thread_start) */
+ DWORD tid = GetCurrentThreadId();
+ shpid pid = GetCurrentProcessId();
+
+ pid = SHPID_MAKE(pid, tid);
+ psh->pid = pid;
+ psh->tid = tid;
+
+ /* Set the TLS entry before we try TRACE or TRACE2. */
+ shthread_set_shell(psh);
+
+ TRACE2((psh, "sh_thread_wrapper: enter\n"));
+ if ((iExit = setjmp(exitjmp.loc)) == 0)
+ {
+ psh->exitjmp = &exitjmp;
+ iExit = psh->thread(psh, psh->threadarg);
+ TRACE2((psh, "sh_thread_wrapper: thread proc returns %d (%#x)\n", iExit, iExit));
+ }
+ else
+ {
+ psh = volpsh; /* paranoia */
+ psh->exitjmp = NULL;
+ TRACE2((psh, "sh_thread_wrapper: longjmp: iExit=%d (%#x)\n", iExit, iExit));
+ if (iExit == SH_EXIT_ZERO)
+ iExit = 0;
+ }
+
+ /* Signal parent. */
+ shsubshellstatus_signal_and_release(psh, iExit);
+
+ /* destroy the shell instance and exit the thread. */
+ TRACE2((psh, "sh_thread_wrapper: quits - iExit=%d\n", iExit));
+ sh_destroy(psh);
+ shthread_set_shell(NULL);
+ _endthreadex(iExit);
+ return iExit;
+}
+# else
+# error "PORTME"
+# endif
+
+/**
+ * Starts a sub-shell thread.
+ */
+shpid sh_thread_start(shinstance *pshparent, shinstance *pshchild, int (*thread)(shinstance *, void *), void *arg)
+{
+# ifdef _MSC_VER
+ shpid pid;
+
+ shsubshellstatus *sts = shsubshellstatus_create(pshparent, 2);
+ pshchild->subshellstatus = sts;
+ if (sts)
+ {
+ unsigned tid = 0;
+ uintptr_t hThread;
+
+ pshchild->thread = thread;
+ pshchild->threadarg = arg;
+
+ hThread = _beginthreadex(NULL /*security*/, 0 /*stack_size*/, sh_thread_wrapper, pshchild, 0 /*initflags*/, &tid);
+ sts->hThread = hThread;
+ if (hThread != -1)
+ {
+ pid = SHPID_MAKE(SHPID_GET_PID(pshparent->pid), tid);
+ pshchild->pid = pid;
+ pshchild->tid = tid;
+
+ if (sh_add_child(pshparent, pid, sts->towaiton, sts) == 0)
+ {
+ return pid;
+ }
+
+ shsubshellstatus_retain(sts);
+ pid = -ENOMEM;
+ }
+ else
+ pid = -errno;
+ shsubshellstatus_release(pshparent, sts);
+ shsubshellstatus_release(pshparent, sts);
+ }
+ else
+ pid = -ENOMEM;
+ return pid;
+
+# else
+# error "PORTME"
+# endif
+}
+
+#endif /* !SH_FORKED_MODE */
+
+/** waitpid() */
+shpid sh_waitpid(shinstance *psh, shpid pid, int *statusp, int flags)
+{
+ shpid pidret;
+#if K_OS == K_OS_WINDOWS //&& defined(SH_FORKED_MODE)
+ DWORD dwRet;
+ HANDLE hChild = INVALID_HANDLE_VALUE;
+ unsigned i;
+
+ *statusp = 0;
+ pidret = -1;
+ if (pid != -1)
+ {
+ /*
+ * A specific child, try look it up in the child process table
+ * and wait for it.
+ */
+ for (i = 0; i < psh->num_children; i++)
+ if (psh->children[i].pid == pid)
+ break;
+ if (i < psh->num_children)
+ {
+ dwRet = WaitForSingleObject(psh->children[i].hChild,
+ flags & WNOHANG ? 0 : INFINITE);
+ if (dwRet == WAIT_OBJECT_0)
+ hChild = psh->children[i].hChild;
+ else if (dwRet == WAIT_TIMEOUT)
+ {
+ i = ~0; /* don't try close anything */
+ pidret = 0;
+ }
+ else
+ errno = ECHILD;
+ }
+ else
+ errno = ECHILD;
+ }
+ else if (psh->num_children <= MAXIMUM_WAIT_OBJECTS)
+ {
+ HANDLE ahChildren[MAXIMUM_WAIT_OBJECTS];
+ for (i = 0; i < psh->num_children; i++)
+ ahChildren[i] = psh->children[i].hChild;
+ dwRet = WaitForMultipleObjects(psh->num_children, &ahChildren[0],
+ FALSE,
+ flags & WNOHANG ? 0 : INFINITE);
+ i = dwRet - WAIT_OBJECT_0;
+ if (i < psh->num_children)
+ {
+ hChild = psh->children[i].hChild;
+ }
+ else if (dwRet == WAIT_TIMEOUT)
+ {
+ i = ~0; /* don't try close anything */
+ pidret = 0;
+ }
+ else
+ {
+ i = ~0; /* don't try close anything */
+ errno = EINVAL;
+ }
+ }
+ else
+ {
+ fprintf(stderr, "panic! too many children!\n");
+ i = ~0;
+ *(char *)1 = '\0'; /** @todo implement this! */
+ }
+
+ /*
+ * Close the handle, and if we succeeded collect the exit code first.
+ */
+ if (i < psh->num_children)
+ {
+ BOOL rc;
+ if (hChild != INVALID_HANDLE_VALUE)
+ {
+ DWORD dwExitCode = 127;
+# ifndef SH_FORKED_MODE
+ if (psh->children[i].subshellstatus)
+ {
+ rc = psh->children[i].subshellstatus->done;
+ kHlpAssert(rc);
+ if (rc)
+ {
+ *statusp = psh->children[i].subshellstatus->status;
+ pidret = psh->children[i].pid;
+ }
+ }
+ else
+# endif
+ if (GetExitCodeProcess(hChild, &dwExitCode))
+ {
+ pidret = psh->children[i].pid;
+ if (dwExitCode && !W_EXITCODE(dwExitCode, 0))
+ dwExitCode |= 16;
+ *statusp = W_EXITCODE(dwExitCode, 0);
+ }
+ else
+ errno = EINVAL;
+ }
+
+ /* close and remove */
+# ifndef SH_FORKED_MODE
+ if (psh->children[i].subshellstatus)
+ {
+ shsubshellstatus_release(psh, psh->children[i].subshellstatus);
+ psh->children[i].subshellstatus = NULL;
+ }
+ else
+# endif
+ {
+ rc = CloseHandle(psh->children[i].hChild);
+ kHlpAssert(rc);
+ }
+
+ psh->num_children--;
+ if (i < psh->num_children)
+ psh->children[i] = psh->children[psh->num_children];
+ psh->children[psh->num_children].hChild = NULL;
+# ifndef SH_FORKED_MODE
+ psh->children[psh->num_children].subshellstatus = NULL;
+# endif
+ }
+
+#elif defined(SH_FORKED_MODE)
+ *statusp = 0;
+# ifdef _MSC_VER
+ pidret = -1;
+ errno = ENOSYS;
+# else
+ pidret = waitpid(pid, statusp, flags);
+# endif
+
+#else
+#endif
+
+ TRACE2((psh, "waitpid(%" SHPID_PRI ", %p, %#x) -> %" SHPID_PRI " [%d] *statusp=%#x (rc=%d)\n", pid, statusp, flags,
+ pidret, errno, *statusp, WEXITSTATUS(*statusp)));
+ (void)psh;
+ return pidret;
+}
+
+SH_NORETURN_1 void sh__exit(shinstance *psh, int iExit)
+{
+ TRACE2((psh, "sh__exit(%d)\n", iExit));
+
+#if defined(SH_FORKED_MODE)
+ _exit(iExit);
+ (void)psh;
+
+#else
+ psh->exitstatus = iExit;
+
+ /*
+ * If we're a thread, jump to the sh_thread_wrapper and make a clean exit.
+ */
+ if (psh->thread)
+ {
+ shsubshellstatus_signal_and_release(psh, iExit);
+ if (psh->exitjmp)
+ longjmp(psh->exitjmp->loc, !iExit ? SH_EXIT_ZERO : iExit);
+ else
+ {
+ static char const s_msg[] = "fatal error in sh__exit: exitjmp is NULL!\n";
+ shfile_write(&psh->fdtab, 2, s_msg, sizeof(s_msg) - 1);
+ _exit(iExit);
+ }
+ }
+
+ /*
+ * The main thread will typically have to stick around till all subshell
+ * threads have been stopped. We must tear down this shell instance as
+ * much as possible before doing this, though, as subshells could be
+ * waiting for pipes and such to be closed before they're willing to exit.
+ */
+ if (g_num_shells > 1)
+ {
+ TRACE2((psh, "sh__exit: %u shells around, must wait...\n", g_num_shells));
+ shfile_uninit(&psh->fdtab, psh->tracefd);
+ sh_int_unlink(psh);
+ /** @todo */
+ }
+
+ _exit(iExit);
+#endif
+}
+
+int sh_execve(shinstance *psh, const char *exe, const char * const *argv, const char * const *envp)
+{
+ int rc;
+
+ g_stat_execs++;
+
+#ifdef DEBUG
+ /* log it all */
+ TRACE2((psh, "sh_execve(%p:{%s}, %p, %p}\n", exe, exe, argv, envp));
+ for (rc = 0; argv[rc]; rc++)
+ TRACE2((psh, " argv[%d]=%p:{%s}\n", rc, argv[rc], argv[rc]));
+#endif
+
+ if (!envp)
+ envp = (const char * const *)sh_environ(psh);
+
+#if defined(SH_FORKED_MODE) && K_OS != K_OS_WINDOWS
+# ifdef _MSC_VER
+ errno = 0;
+ {
+ intptr_t rc2 = _spawnve(_P_WAIT, exe, (char **)argv, (char **)envp);
+ if (rc2 != -1)
+ {
+ TRACE2((psh, "sh_execve: child exited, rc=%d. (errno=%d)\n", rc, errno));
+ rc = (int)rc2;
+ if (!rc && rc2)
+ rc = 16;
+ exit(rc);
+ }
+ }
+ rc = -1;
+
+# else
+ rc = shfile_exec_unix(&psh->fdtab);
+ if (!rc)
+ rc = execve(exe, (char **)argv, (char **)envp);
+# endif
+
+#else
+# if K_OS == K_OS_WINDOWS
+ {
+ /*
+ * This ain't quite straight forward on Windows...
+ */
+ PROCESS_INFORMATION ProcInfo;
+ STARTUPINFO StrtInfo;
+ shfdexecwin fdinfo;
+ char *cwd = shfile_getcwd(&psh->fdtab, NULL, 0);
+ char *cmdline;
+ size_t cmdline_size;
+ char *envblock;
+ size_t env_size;
+ char *p;
+ int i;
+
+ /* Create the environment block. */
+ if (!envp)
+ envp = sh_environ(psh);
+ env_size = 2;
+ for (i = 0; envp[i]; i++)
+ env_size += strlen(envp[i]) + 1;
+ envblock = p = sh_malloc(psh, env_size);
+ for (i = 0; envp[i]; i++)
+ {
+ size_t len = strlen(envp[i]) + 1;
+ memcpy(p, envp[i], len);
+ p += len;
+ }
+ *p = '\0';
+
+ /* Figure the size of the command line. Double quotes makes this
+ tedious and we overestimate to simplify. */
+ cmdline_size = 2;
+ for (i = 0; argv[i]; i++)
+ {
+ const char *arg = argv[i];
+ cmdline_size += strlen(arg) + 3;
+ arg = strchr(arg, '"');
+ if (arg)
+ {
+ do
+ cmdline_size++;
+ while ((arg = strchr(arg + 1, '"')) != NULL);
+ arg = argv[i] - 1;
+ while ((arg = strchr(arg + 1, '\\')) != NULL);
+ cmdline_size++;
+ }
+ }
+
+ /* Create the command line. */
+ cmdline = p = sh_malloc(psh, cmdline_size);
+ for (i = 0; argv[i]; i++)
+ {
+ const char *arg = argv[i];
+ const char *cur = arg;
+ size_t len = strlen(arg);
+ int quoted = 0;
+ char ch;
+ while ((ch = *cur++) != '\0')
+ if (ch <= 0x20 || strchr("&><|%", ch) != NULL)
+ {
+ quoted = 1;
+ break;
+ }
+
+ if (i != 0)
+ *(p++) = ' ';
+ if (quoted)
+ *(p++) = '"';
+ if (memchr(arg, '"', len) == NULL)
+ {
+ memcpy(p, arg, len);
+ p += len;
+ }
+ else
+ { /* MS CRT style: double quotes must be escaped; backslashes
+ must be escaped if followed by double quotes. */
+ while ((ch = *arg++) != '\0')
+ if (ch != '\\' && ch != '"')
+ *p++ = ch;
+ else if (ch == '"')
+ {
+ *p++ = '\\';
+ *p++ = '"';
+ }
+ else
+ {
+ unsigned slashes = 1;
+ *p++ = '\\';
+ while (*arg == '\\')
+ {
+ *p++ = '\\';
+ slashes++;
+ arg++;
+ }
+ if (*arg == '"')
+ {
+ while (slashes-- > 0)
+ *p++ = '\\';
+ *p++ = '\\';
+ *p++ = '"';
+ arg++;
+ }
+ }
+ }
+ if (quoted)
+ *(p++) = '"';
+ }
+ p[0] = p[1] = '\0';
+
+ /* Init the info structure */
+ memset(&StrtInfo, '\0', sizeof(StrtInfo));
+ StrtInfo.cb = sizeof(StrtInfo);
+
+ /* File handles. */
+ fdinfo.strtinfo = &StrtInfo;
+ shfile_exec_win(&psh->fdtab, 1 /* prepare */, &fdinfo);
+ TRACE2((psh, "sh_execve: inherithandles=%d replacehandles={%d,%d,%d} handles={%p,%p,%p} suspended=%d Reserved2=%p LB %#x\n",
+ fdinfo.inherithandles, fdinfo.replacehandles[0], fdinfo.replacehandles[1], fdinfo.replacehandles[2],
+ fdinfo.handles[0], fdinfo.handles[1], fdinfo.handles[3], fdinfo.startsuspended,
+ StrtInfo.lpReserved2, StrtInfo.cbReserved2));
+ if (!fdinfo.inherithandles)
+ {
+ StrtInfo.dwFlags |= STARTF_USESTDHANDLES;
+ StrtInfo.hStdInput = INVALID_HANDLE_VALUE;
+ StrtInfo.hStdOutput = INVALID_HANDLE_VALUE;
+ StrtInfo.hStdError = INVALID_HANDLE_VALUE;
+ }
+ else
+ {
+ StrtInfo.dwFlags |= STARTF_USESTDHANDLES;
+ StrtInfo.hStdInput = (HANDLE)fdinfo.handles[0];
+ StrtInfo.hStdOutput = (HANDLE)fdinfo.handles[1];
+ StrtInfo.hStdError = (HANDLE)fdinfo.handles[2];
+ g_stat_execs_serialized++;
+ }
+
+ /* Get going... */
+ rc = CreateProcessA(exe,
+ cmdline,
+ NULL, /* pProcessAttributes */
+ NULL, /* pThreadAttributes */
+ fdinfo.inherithandles,
+ fdinfo.startsuspended ? CREATE_SUSPENDED : 0,
+ envblock,
+ cwd,
+ &StrtInfo,
+ &ProcInfo);
+ if (rc)
+ {
+ DWORD dwErr;
+ DWORD dwExitCode;
+
+ if (fdinfo.startsuspended)
+ {
+ char errmsg[512];
+ if (!fdinfo.inherithandles)
+ rc = nt_child_inject_standard_handles(ProcInfo.hProcess, fdinfo.replacehandles,
+ (HANDLE *)&fdinfo.handles[0], errmsg, sizeof(errmsg));
+ else
+ rc = 0;
+ if (!rc)
+ {
+# ifdef KASH_ASYNC_CLOSE_HANDLE
+ shfile_async_close_sync();
+# endif
+ rc = ResumeThread(ProcInfo.hThread);
+ if (!rc)
+ TRACE2((psh, "sh_execve: ResumeThread failed: %u -> errno=ENXIO\n", GetLastError()));
+ }
+ else
+ {
+ TRACE2((psh, "sh_execve: nt_child_inject_standard_handles failed: %d -> errno=ENXIO; %s\n", rc, errmsg));
+ rc = FALSE;
+ }
+ errno = ENXIO;
+ }
+
+ shfile_exec_win(&psh->fdtab, rc ? 0 /* done */ : -1 /* done but failed */, &fdinfo);
+
+ CloseHandle(ProcInfo.hThread);
+ ProcInfo.hThread = INVALID_HANDLE_VALUE;
+ if (rc)
+ {
+ /*
+ * Wait for it and forward the exit code.
+ */
+ dwErr = WaitForSingleObject(ProcInfo.hProcess, INFINITE);
+ kHlpAssert(dwErr == WAIT_OBJECT_0);
+
+ if (GetExitCodeProcess(ProcInfo.hProcess, &dwExitCode))
+ {
+# ifndef SH_FORKED_MODE
+ shsubshellstatus_signal_and_release(psh, (int)dwExitCode);
+# endif
+ CloseHandle(ProcInfo.hProcess);
+ ProcInfo.hProcess = INVALID_HANDLE_VALUE;
+ sh__exit(psh, dwExitCode);
+ }
+
+ /* this shouldn't happen... */
+ TRACE2((psh, "sh_execve: GetExitCodeProcess failed: %u\n", GetLastError()));
+ kHlpAssert(0);
+ errno = EINVAL;
+ }
+ TerminateProcess(ProcInfo.hProcess, 0x40000015);
+ CloseHandle(ProcInfo.hProcess);
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+
+ shfile_exec_win(&psh->fdtab, -1 /* done but failed */, &fdinfo);
+
+ switch (dwErr)
+ {
+ case ERROR_FILE_NOT_FOUND: errno = ENOENT; break;
+ case ERROR_PATH_NOT_FOUND: errno = ENOENT; break;
+ case ERROR_BAD_EXE_FORMAT: errno = ENOEXEC; break;
+ case ERROR_INVALID_EXE_SIGNATURE: errno = ENOEXEC; break;
+ default: errno = EINVAL; break;
+ }
+ TRACE2((psh, "sh_execve: dwErr=%d -> errno=%d\n", dwErr, errno));
+ }
+ }
+ rc = -1;
+
+# else
+ errno = ENOSYS;
+ rc = -1;
+# endif
+#endif
+
+ TRACE2((psh, "sh_execve -> %d [%d]\n", rc, errno));
+ (void)psh;
+ return (int)rc;
+}
+
+uid_t sh_getuid(shinstance *psh)
+{
+#ifdef _MSC_VER
+ uid_t uid = 0;
+#else
+ uid_t uid = getuid();
+#endif
+
+ TRACE2((psh, "sh_getuid() -> %d [%d]\n", uid, errno));
+ (void)psh;
+ return uid;
+}
+
+uid_t sh_geteuid(shinstance *psh)
+{
+#ifdef _MSC_VER
+ uid_t euid = 0;
+#else
+ uid_t euid = geteuid();
+#endif
+
+ TRACE2((psh, "sh_geteuid() -> %d [%d]\n", euid, errno));
+ (void)psh;
+ return euid;
+}
+
+gid_t sh_getgid(shinstance *psh)
+{
+#ifdef _MSC_VER
+ gid_t gid = 0;
+#else
+ gid_t gid = getgid();
+#endif
+
+ TRACE2((psh, "sh_getgid() -> %d [%d]\n", gid, errno));
+ (void)psh;
+ return gid;
+}
+
+gid_t sh_getegid(shinstance *psh)
+{
+#ifdef _MSC_VER
+ gid_t egid = 0;
+#else
+ gid_t egid = getegid();
+#endif
+
+ TRACE2((psh, "sh_getegid() -> %d [%d]\n", egid, errno));
+ (void)psh;
+ return egid;
+}
+
+shpid sh_getpid(shinstance *psh)
+{
+ return psh->pid;
+}
+
+shpid sh_getpgrp(shinstance *psh)
+{
+ shpid pgid = psh->pgid;
+#ifndef _MSC_VER
+ kHlpAssert(pgid == getpgrp());
+#endif
+
+ TRACE2((psh, "sh_getpgrp() -> %" SHPID_PRI " [%d]\n", pgid, errno));
+ return pgid;
+}
+
+/**
+ * @param pid Should always be zero, i.e. referring to the current shell
+ * process.
+ */
+shpid sh_getpgid(shinstance *psh, shpid pid)
+{
+ shpid pgid;
+ if (pid == 0 || psh->pid == pid)
+ {
+ pgid = psh->pgid;
+#ifndef _MSC_VER
+ kHlpAssert(pgid == getpgrp());
+#endif
+ }
+ else
+ {
+ kHlpAssert(0);
+ errno = ESRCH;
+ pgid = -1;
+ }
+
+ TRACE2((psh, "sh_getpgid(%" SHPID_PRI ") -> %" SHPID_PRI " [%d]\n", pid, pgid, errno));
+ return pgid;
+}
+
+/**
+ *
+ * @param pid The pid to modify. This is always 0, except when forkparent
+ * calls to group a newly created child. Though, we might
+ * almost safely ignore it in that case as the child will also
+ * perform the operation.
+ * @param pgid The process group to assign @a pid to.
+ */
+int sh_setpgid(shinstance *psh, shpid pid, shpid pgid)
+{
+#if defined(SH_FORKED_MODE) && !defined(_MSC_VER)
+ int rc = setpgid(pid, pgid);
+ TRACE2((psh, "sh_setpgid(%" SHPID_PRI ", %" SHPID_PRI ") -> %d [%d]\n", pid, pgid, rc, errno));
+ (void)psh;
+#else
+ int rc = 0;
+ if (pid == 0 || psh->pid == pid)
+ {
+ TRACE2((psh, "sh_setpgid(self,): %" SHPID_PRI " -> %" SHPID_PRI "\n", psh->pgid, pgid));
+ psh->pgid = pgid;
+ }
+ else
+ {
+ /** @todo fixme */
+ rc = -1;
+ errno = ENOSYS;
+ }
+#endif
+ return rc;
+}
+
+shpid sh_tcgetpgrp(shinstance *psh, int fd)
+{
+ shpid pgrp;
+
+#ifdef _MSC_VER
+ pgrp = -1;
+ errno = ENOSYS;
+#elif defined(SH_FORKED_MODE)
+ pgrp = tcgetpgrp(fd);
+#else
+# error "PORT ME"
+#endif
+
+ TRACE2((psh, "sh_tcgetpgrp(%d) -> %" SHPID_PRI " [%d]\n", fd, pgrp, errno));
+ (void)psh;
+ return pgrp;
+}
+
+int sh_tcsetpgrp(shinstance *psh, int fd, shpid pgrp)
+{
+ int rc;
+ TRACE2((psh, "sh_tcsetpgrp(%d, %" SHPID_PRI ")\n", fd, pgrp));
+
+#ifdef _MSC_VER
+ rc = -1;
+ errno = ENOSYS;
+#elif defined(SH_FORKED_MODE)
+ rc = tcsetpgrp(fd, pgrp);
+#else
+# error "PORT ME"
+#endif
+
+ TRACE2((psh, "sh_tcsetpgrp(%d, %" SHPID_PRI ") -> %d [%d]\n", fd, pgrp, rc, errno));
+ (void)psh;
+ return rc;
+}
+
+int sh_getrlimit(shinstance *psh, int resid, shrlimit *limp)
+{
+#ifdef _MSC_VER
+ int rc = -1;
+ errno = ENOSYS;
+#elif defined(SH_FORKED_MODE)
+ int rc = getrlimit(resid, limp);
+#else
+# error "PORT ME"
+ /* returned the stored limit */
+#endif
+
+ TRACE2((psh, "sh_getrlimit(%d, %p) -> %d [%d] {%ld,%ld}\n",
+ resid, limp, rc, errno, (long)limp->rlim_cur, (long)limp->rlim_max));
+ (void)psh;
+ return rc;
+}
+
+int sh_setrlimit(shinstance *psh, int resid, const shrlimit *limp)
+{
+#ifdef _MSC_VER
+ int rc = -1;
+ errno = ENOSYS;
+#elif defined(SH_FORKED_MODE)
+ int rc = setrlimit(resid, limp);
+#else
+# error "PORT ME"
+ /* if max(shell) < limp; then setrlimit; fi
+ if success; then store limit for later retrival and maxing. */
+
+#endif
+
+ TRACE2((psh, "sh_setrlimit(%d, %p:{%ld,%ld}) -> %d [%d]\n",
+ resid, limp, (long)limp->rlim_cur, (long)limp->rlim_max, rc, errno));
+ (void)psh;
+ return rc;
+}
+
+
+/* Wrapper for strerror that makes sure it doesn't return NULL and causes the
+ caller or fprintf routines to crash. */
+const char *sh_strerror(shinstance *psh, int error)
+{
+ char *err = strerror(error);
+ if (!err)
+ return "strerror return NULL!";
+ (void)psh;
+ return err;
+}
+
diff --git a/src/kash/shinstance.h b/src/kash/shinstance.h
new file mode 100644
index 0000000..f181f33
--- /dev/null
+++ b/src/kash/shinstance.h
@@ -0,0 +1,606 @@
+/* $Id: shinstance.h 3480 2020-09-21 11:20:56Z bird $ */
+/** @file
+ * The shell instance and it's methods.
+ */
+
+/*
+ * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef ___shinstance_h
+#define ___shinstance_h
+
+#include <stdio.h> /* BUFSIZ */
+#include <signal.h> /* NSIG */
+#ifndef _MSC_VER
+# include <termios.h>
+# include <sys/types.h>
+# include <sys/ioctl.h>
+# include <sys/resource.h>
+#endif
+#include <errno.h>
+#ifdef _MSC_VER
+# define EWOULDBLOCK 140
+#endif
+
+#include "shtypes.h"
+#include "shthread.h"
+#include "shfile.h"
+#include "shheap.h"
+#include "shell.h"
+#include "output.h"
+#include "options.h"
+
+#include "expand.h"
+#include "exec.h"
+#include "var.h"
+#include "show.h"
+
+#ifdef _MSC_VER
+# define strcasecmp stricmp
+# define strncasecmp strnicmp
+#endif
+
+#ifndef SH_FORKED_MODE
+extern shmtx g_sh_exec_inherit_mtx;
+#endif
+
+#ifndef SH_FORKED_MODE
+/**
+ * Subshell status.
+ */
+typedef struct shsubshellstatus
+{
+ unsigned volatile refs; /**< Reference counter. */
+ int volatile status; /**< The exit code. */
+ KBOOL volatile done; /**< Set if done (valid exit code). */
+ void *towaiton; /**< Event semaphore / whatever to wait on. */
+# if K_OS == K_OS_WINDOWS
+ uintptr_t volatile hThread; /**< The thread handle (child closes this). */
+# endif
+ struct shsubshellstatus *next; /**< Next free one on the free chain. */
+} shsubshellstatus;
+#else
+struct shsubshellstatus;
+#endif
+
+/**
+ * A child process.
+ */
+typedef struct shchild
+{
+ shpid pid; /**< The pid. */
+#if K_OS == K_OS_WINDOWS
+ void *hChild; /**< The handle to wait on. */
+#endif
+#ifndef SH_FORKED_MODE
+ shsubshellstatus *subshellstatus; /**< Pointer to the subshell status structure. NULL if child process. */
+#endif
+} shchild;
+
+/* memalloc.c */
+#define MINSIZE 504 /* minimum size of a block */
+struct stack_block {
+ struct stack_block *prev;
+ char space[MINSIZE];
+};
+
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+/** Parser stack allocator block.
+ * These are reference counted so they can be shared between the parent and
+ * child shells. They are also using as an alternative to copying function
+ * definitions, here the final goal is to automatically emit separate
+ * pstack_blocks for function while parsing to make it more flexible. */
+typedef struct pstack_block {
+ /** Pointer to the next unallocated byte (= stacknxt). */
+ char *nextbyte;
+ /** Number of bytes available in the current stack block (= stacknleft). */
+ size_t avail;
+ /* Number of chars left for string data (PSTPUTC, PSTUPUTC, et al) (= sstrnleft). */
+ size_t strleft;
+ /** Top of the allocation stack (nextbyte points within this). */
+ struct stack_block *top;
+ /** Size of the top stack element (user space only). */
+ size_t topsize;
+ /** @name statistics
+ * @{ */
+ size_t allocations;
+ size_t bytesalloced;
+ size_t nodesalloced;
+ size_t entriesalloced;
+ size_t strbytesalloced;
+ size_t blocks;
+ size_t fragmentation;
+ /** @} */
+ /** Reference counter. */
+ unsigned volatile refs;
+ /** Whether to make it current when is restored to the top of the stack. */
+ KBOOL done;
+ /** The first stack block. */
+ struct stack_block first;
+} pstack_block;
+#endif
+
+/* input.c */
+struct strpush {
+ struct strpush *prev; /* preceding string on stack */
+ char *prevstring;
+ int prevnleft;
+ int prevlleft;
+ struct alias *ap; /* if push was associated with an alias */
+};
+
+/*
+ * The parsefile structure pointed to by the global variable parsefile
+ * contains information about the current file being read.
+ */
+struct parsefile {
+ struct parsefile *prev; /* preceding file on stack */
+ int linno; /* current line */
+ int fd; /* file descriptor (or -1 if string) */
+ int nleft; /* number of chars left in this line */
+ int lleft; /* number of chars left in this buffer */
+ char *nextc; /* next char in buffer */
+ char *buf; /* input buffer */
+ struct strpush *strpush; /* for pushing strings at this level */
+ struct strpush basestrpush; /* so pushing one is fast */
+};
+
+/* exec.c */
+#define CMDTABLESIZE 31 /* should be prime */
+#define ARB 1 /* actual size determined at run time */
+
+struct tblentry {
+ struct tblentry *next; /* next entry in hash chain */
+ union param param; /* definition of builtin function */
+ short cmdtype; /* index identifying command */
+ char rehash; /* if set, cd done since entry created */
+ char cmdname[ARB]; /* name of command */
+};
+
+/* expand.c */
+/*
+ * Structure specifying which parts of the string should be searched
+ * for IFS characters.
+ */
+struct ifsregion {
+ struct ifsregion *next; /* next region in list */
+ int begoff; /* offset of start of region */
+ int endoff; /* offset of end of region */
+ int inquotes; /* search for nul bytes only */
+};
+
+/* redir.c */
+struct redirtab {
+ struct redirtab *next;
+ short renamed[10];
+};
+
+/**
+ * This is a replacement for temporary node field nfile.expfname.
+ * Uses stack allocator, created by expredir(), duplicated by
+ * subshellinitredir() and popped (but not freed) by expredircleanup().
+ */
+typedef struct redirexpfnames
+{
+ struct redirexpfnames *prev; /**< Previous record. */
+ unsigned depth; /**< Nesting depth. */
+ unsigned count; /**< Number of expanded filenames in the array. */
+ char *names[1]; /**< Variable size. */
+} redirexpfnames;
+
+
+/**
+ * A shell instance.
+ *
+ * This is the core structure of the shell, it contains all
+ * the data associated with a shell process except that it's
+ * running in a thread and not a separate process.
+ */
+struct shinstance
+{
+ struct shinstance *next; /**< The next shell instance. */
+ struct shinstance *prev; /**< The previous shell instance. */
+ struct shinstance *parent; /**< The parent shell instance. */
+ shpid pid; /**< The (fake) process id of this shell instance. */
+ shtid tid; /**< The thread identifier of the thread for this shell. */
+ shpid pgid; /**< Process group ID. */
+ shfdtab fdtab; /**< The file descriptor table. */
+ shsigaction_t sigactions[NSIG]; /**< The signal actions registered with this shell instance. */
+ shsigset_t sigmask; /**< Our signal mask. */
+ char **shenviron; /**< The environment vector. */
+ int linked; /**< Set if we're still linked. */
+ unsigned num_children; /**< Number of children in the array. */
+ shchild *children; /**< The child array. */
+#ifndef SH_FORKED_MODE
+ int (*thread)(struct shinstance *, void *); /**< The thread procedure. */
+ void *threadarg; /**< The thread argument. */
+ struct jmploc *exitjmp; /**< Long jump target in sh_thread_wrapper for use by sh__exit. */
+ shsubshellstatus *subshellstatus; /**< Pointer to the subshell status structure (NULL if root). */
+#endif
+
+ /* alias.c */
+#define ATABSIZE 39
+ struct alias *atab[ATABSIZE];
+ unsigned aliases; /**< Number of active aliases. */
+
+ /* cd.c */
+ char *curdir; /**< current working directory */
+ char *prevdir; /**< previous working directory */
+ char *cdcomppath; /**< (stalloc) */
+ int getpwd_first; /**< static in getpwd. (initialized to 1!) */
+
+ /* error.h */
+ struct jmploc *handler;
+ int exception;
+ int exerrno/* = 0 */; /**< Last exec error */
+ int volatile suppressint;
+ int volatile intpending;
+
+ /* error.c */
+ char errmsg_buf[16]; /**< static in errmsg. (bss) */
+
+ /* eval.h */
+ char *commandname; /**< currently executing command */
+ int exitstatus; /**< exit status of last command */
+ int back_exitstatus;/**< exit status of backquoted command */
+ struct strlist *cmdenviron; /**< environment for builtin command (varlist from evalcommand()) */
+ int funcnest; /**< depth of function calls */
+ int evalskip; /**< set if we are skipping commands */
+ int skipcount; /**< number of levels to skip */
+ int loopnest; /**< current loop nesting level */
+ int commandnamemalloc; /**< Set if commandname is malloc'ed (only subshells). */
+
+ /* expand.c */
+ char *expdest; /**< output of current string (stack) */
+ struct nodelist *argbackq; /**< list of back quote expressions */
+ struct ifsregion ifsfirst; /**< first struct in list of ifs regions */
+ struct ifsregion *ifslastp; /**< last struct in list */
+ struct arglist exparg; /**< holds expanded arg list (stack) */
+ char *expdir; /**< Used by expandmeta. */
+
+ /* exec.h */
+ const char *pathopt; /**< set by padvance */
+
+ /* exec.c */
+ struct tblentry *cmdtable[CMDTABLESIZE];
+ int builtinloc/* = -1*/; /**< index in path of %builtin, or -1 */
+
+ /* input.h */
+ int plinno/* = 1 */;/**< input line number */
+ int parsenleft; /**< number of characters left in input buffer */
+ char *parsenextc; /**< next character in input buffer */
+ int init_editline/* = 0 */; /**< 0 == not setup, 1 == OK, -1 == failed */
+
+ /* input.c */
+ int parselleft; /**< copy of parsefile->lleft */
+ struct parsefile basepf; /**< top level input file */
+ char basebuf[BUFSIZ];/**< buffer for top level input file */
+ struct parsefile *parsefile/* = &basepf*/; /**< current input file */
+#ifndef SMALL
+ EditLine *el; /**< cookie for editline package */
+#endif
+
+ /* jobs.h */
+ shpid backgndpid/* = -1 */; /**< pid of last background process */
+ int job_warning; /**< user was warned about stopped jobs */
+
+ /* jobs.c */
+ struct job *jobtab; /**< array of jobs */
+ int njobs; /**< size of array */
+ int jobs_invalid; /**< set in child */
+ shpid initialpgrp; /**< pgrp of shell on invocation */
+ int curjob/* = -1*/;/**< current job */
+ int ttyfd/* = -1*/;
+ int jobctl; /**< job control enabled / disabled */
+ char *cmdnextc;
+ int cmdnleft;
+
+
+ /* mail.c */
+#define MAXMBOXES 10
+ int nmboxes; /**< number of mailboxes */
+ time_t mailtime[MAXMBOXES]; /**< times of mailboxes */
+
+ /* main.h */
+ shpid rootpid; /**< pid of main shell. */
+ int rootshell; /**< true if we aren't a child of the main shell. */
+ struct shinstance *psh_rootshell; /**< The root shell pointer. (!rootshell) */
+
+ /* memalloc.h */
+ char *stacknxt/* = stackbase.space*/;
+ int stacknleft/* = MINSIZE*/;
+ int sstrnleft;
+ int herefd/* = -1 */;
+
+ /* memalloc.c */
+ struct stack_block stackbase;
+ struct stack_block *stackp/* = &stackbase*/;
+ struct stackmark *markp;
+
+#ifdef KASH_SEPARATE_PARSER_ALLOCATOR
+ pstack_block *curpstack; /**< The pstack entry we're currently allocating from (NULL when not in parse.c). */
+ pstack_block **pstack; /**< Stack of parsed stuff. */
+ unsigned pstacksize; /**< Number of entries in pstack. */
+ unsigned pstackalloced; /**< The allocated size of pstack. */
+ pstack_block *freepstack; /**< One cached pstack entry (lots of parsecmd calls). */
+#endif
+
+ /* myhistedit.h */
+ int displayhist;
+#ifndef SMALL
+ History *hist;
+ EditLine *el;
+#endif
+
+ /* output.h */
+ struct output output;
+ struct output errout;
+ struct output memout;
+ struct output *out1;
+ struct output *out2;
+
+ /* output.c */
+#define OUTBUFSIZ BUFSIZ
+#define MEM_OUT -3 /**< output to dynamically allocated memory */
+
+ /* options.h */
+ struct optent optlist[NOPTS];
+ char *minusc; /**< argument to -c option */
+ char *arg0; /**< $0 */
+ struct shparam shellparam; /**< $@ */
+ char **argptr; /**< argument list for builtin commands */
+ char *optionarg; /**< set by nextopt */
+ char *optptr; /**< used by nextopt */
+ char **orgargv; /**< The original argument vector (for cleanup). */
+ int arg0malloc; /**< Indicates whether arg0 was allocated or is part of orgargv. */
+
+ /* parse.h */
+ int tokpushback;
+ int whichprompt; /**< 1 == PS1, 2 == PS2 */
+
+ /* parser.c */
+ int noalias/* = 0*/;/**< when set, don't handle aliases */
+ struct heredoc *heredoclist; /**< list of here documents to read */
+ int parsebackquote; /**< nonzero if we are inside backquotes */
+ int doprompt; /**< if set, prompt the user */
+ int needprompt; /**< true if interactive and at start of line */
+ int lasttoken; /**< last token read */
+ char *wordtext; /**< text of last word returned by readtoken */
+ int checkkwd; /**< 1 == check for kwds, 2 == also eat newlines */
+ struct nodelist *backquotelist;
+ union node *redirnode;
+ struct heredoc *heredoc;
+ int quoteflag; /**< set if (part of) last token was quoted */
+ int startlinno; /**< line # where last token started */
+
+ /* redir.c */
+ struct redirtab *redirlist;
+ int fd0_redirected/* = 0*/;
+ redirexpfnames *expfnames; /**< Expanded filenames for current redirection setup. */
+
+ /* show.c */
+ char tracebuf[1024];
+ size_t tracepos;
+ int tracefd;
+
+ /* trap.h */
+ int pendingsigs; /**< indicates some signal received */
+
+ /* trap.c */
+ char gotsig[NSIG]; /**< indicates specified signal received */
+ char *trap[NSIG+1]; /**< trap handler commands */
+ char sigmode[NSIG]; /**< current value of signal */
+
+ /* var.h */
+ struct localvar *localvars;
+ struct var vatty;
+ struct var vifs;
+ struct var vmail;
+ struct var vmpath;
+ struct var vpath;
+#ifdef _MSC_VER
+ struct var vpath2;
+#endif
+ struct var vps1;
+ struct var vps2;
+ struct var vps4;
+#ifndef SMALL
+ struct var vterm;
+ struct var vhistsize;
+#endif
+ struct var voptind;
+#ifdef PC_OS2_LIBPATHS
+ struct var libpath_vars[4];
+#endif
+#ifdef SMALL
+# define VTABSIZE 39
+#else
+# define VTABSIZE 517
+#endif
+ struct var *vartab[VTABSIZE];
+
+ /* builtins.h */
+
+ /* bltin/test.c */
+ char **t_wp;
+ struct t_op const *t_wp_op;
+};
+
+extern void sh_init_globals(void);
+extern shinstance *sh_create_root_shell(char **, char **);
+extern shinstance *sh_create_child_shell(shinstance *);
+
+/* environment & pwd.h */
+char *sh_getenv(shinstance *, const char *);
+char **sh_environ(shinstance *);
+const char *sh_gethomedir(shinstance *, const char *);
+
+/* signals */
+#define SH_SIG_UNK ((shsig_t)(intptr_t)-199)
+#define SH_SIG_DFL ((shsig_t)(intptr_t)SIG_DFL)
+#define SH_SIG_IGN ((shsig_t)(intptr_t)SIG_IGN)
+#define SH_SIG_ERR ((shsig_t)(intptr_t)SIG_ERR)
+#ifdef _MSC_VER
+# define SA_RESTART 0x02
+# define SIG_BLOCK 1
+# define SIG_UNBLOCK 2
+# define SIG_SETMASK 3
+
+# define SIGHUP 1 /* _SIGHUP_IGNORE */
+/*# define SIGINT 2 */
+# define SIGQUIT 3 /* _SIGQUIT_IGNORE */
+/*# define SIGILL 4 */
+/*# define SIGFPE 8 */
+/*# define SIGSEGV 11 */
+# define SIGPIPE 13 /* _SIGPIPE_IGNORE */
+/*# define SIGTERM 15 */
+# define SIGTTIN 16 /* _SIGIOINT_IGNORE */
+# define SIGTSTP 17 /* _SIGSTOP_IGNORE */
+# define SIGTTOU 18
+# define SIGCONT 20
+/*# define SIGBREAK 21 */
+/*# define SIGABRT 22 */
+const char *strsignal(int iSig);
+#endif /* _MSC_VER */
+#ifndef HAVE_SYS_SIGNAME
+extern const char * const sys_signame[NSIG];
+#endif
+
+int sh_sigaction(shinstance *, int, const struct shsigaction *, struct shsigaction *);
+shsig_t sh_signal(shinstance *, int, shsig_t);
+int sh_siginterrupt(shinstance *, int, int);
+void sh_sigemptyset(shsigset_t *);
+void sh_sigfillset(shsigset_t *);
+void sh_sigaddset(shsigset_t *, int);
+void sh_sigdelset(shsigset_t *, int);
+int sh_sigismember(shsigset_t const *, int);
+int sh_sigprocmask(shinstance *, int, shsigset_t const *, shsigset_t *);
+SH_NORETURN_1 void sh_abort(shinstance *) SH_NORETURN_2;
+void sh_raise_sigint(shinstance *);
+int sh_kill(shinstance *, shpid, int);
+int sh_killpg(shinstance *, shpid, int);
+
+/* times */
+#include <time.h>
+#ifdef _MSC_VER
+ typedef struct shtms
+ {
+ clock_t tms_utime;
+ clock_t tms_stime;
+ clock_t tms_cutime;
+ clock_t tms_cstime;
+ } shtms;
+#else
+# include <sys/times.h>
+ typedef struct tms shtms;
+#endif
+clock_t sh_times(shinstance *, shtms *);
+int sh_sysconf_clk_tck(void);
+
+/* wait / process */
+int sh_add_child(shinstance *psh, shpid pid, void *hChild, struct shsubshellstatus *sts);
+#ifdef _MSC_VER
+# include <process.h>
+# define WNOHANG 1 /* Don't hang in wait. */
+# define WUNTRACED 2 /* Tell about stopped, untraced children. */
+# define WCONTINUED 4 /* Report a job control continued process. */
+# define _W_INT(w) (*(int *)&(w)) /* Convert union wait to int. */
+# define WCOREFLAG 0200
+# define _WSTATUS(x) (_W_INT(x) & 0177)
+# define _WSTOPPED 0177 /* _WSTATUS if process is stopped */
+# define WIFSTOPPED(x) (_WSTATUS(x) == _WSTOPPED)
+# define WSTOPSIG(x) (_W_INT(x) >> 8)
+# define WIFSIGNALED(x) (_WSTATUS(x) != 0 && !WIFSTOPPED(x) && !WIFCONTINUED(x)) /* bird: made GLIBC tests happy. */
+# define WTERMSIG(x) (_WSTATUS(x))
+# define WIFEXITED(x) (_WSTATUS(x) == 0)
+# define WEXITSTATUS(x) (_W_INT(x) >> 8)
+# define WIFCONTINUED(x) (x == 0x13) /* 0x13 == SIGCONT */
+# define WCOREDUMP(x) (_W_INT(x) & WCOREFLAG)
+# define W_EXITCODE(ret, sig) ((ret) << 8 | (sig))
+# define W_STOPCODE(sig) ((sig) << 8 | _WSTOPPED)
+#else
+# include <sys/wait.h>
+# ifdef __HAIKU__
+# define WCOREDUMP(x) WIFCORED(x)
+# endif
+#endif
+#ifdef SH_FORKED_MODE
+shpid sh_fork(shinstance *);
+#else
+shpid sh_thread_start(shinstance *pshparent, shinstance *pshchild, int (*thread)(shinstance *, void *), void *arg);
+#endif
+shpid sh_waitpid(shinstance *, shpid, int *, int);
+SH_NORETURN_1 void sh__exit(shinstance *, int) SH_NORETURN_2;
+int sh_execve(shinstance *, const char *, const char * const*, const char * const *);
+uid_t sh_getuid(shinstance *);
+uid_t sh_geteuid(shinstance *);
+gid_t sh_getgid(shinstance *);
+gid_t sh_getegid(shinstance *);
+shpid sh_getpid(shinstance *);
+shpid sh_getpgrp(shinstance *);
+shpid sh_getpgid(shinstance *, shpid);
+int sh_setpgid(shinstance *, shpid, shpid);
+
+/* tc* */
+shpid sh_tcgetpgrp(shinstance *, int);
+int sh_tcsetpgrp(shinstance *, int, shpid);
+
+/* sys/resource.h */
+#ifdef _MSC_VER
+ typedef int64_t shrlim_t;
+ typedef struct shrlimit
+ {
+ shrlim_t rlim_cur;
+ shrlim_t rlim_max;
+ } shrlimit;
+# 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_MEMLOCK 6
+# define RLIMIT_NPROC 7
+# define RLIMIT_NOFILE 8
+# define RLIMIT_SBSIZE 9
+# define RLIMIT_VMEM 10
+# define RLIM_NLIMITS 11
+# define RLIM_INFINITY (0x7fffffffffffffffLL)
+#else
+ typedef rlim_t shrlim_t;
+ typedef struct rlimit shrlimit;
+#endif
+int sh_getrlimit(shinstance *, int, shrlimit *);
+int sh_setrlimit(shinstance *, int, const shrlimit *);
+
+/* string.h */
+const char *sh_strerror(shinstance *, int);
+
+#ifdef DEBUG
+# define TRACE2(param) trace param
+# define TRACE2V(param) tracev param
+#else
+# define TRACE2(param) do { } while (0)
+# define TRACE2V(param) do { } while (0)
+#endif
+
+#endif
diff --git a/src/kash/show.c b/src/kash/show.c
new file mode 100644
index 0000000..e516f30
--- /dev/null
+++ b/src/kash/show.c
@@ -0,0 +1,534 @@
+/* $NetBSD: show.c,v 1.26 2003/11/14 10:46:13 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)show.c 8.3 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: show.c,v 1.26 2003/11/14 10:46:13 dsl Exp $");
+#endif /* not lint */
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+
+#include "shell.h"
+#include "parser.h"
+#include "nodes.h"
+#include "mystring.h"
+#include "show.h"
+#include "options.h"
+#include "shinstance.h"
+
+
+#ifdef DEBUG
+static void shtree(union node *, int, char *, FILE*);
+static void shcmd(union node *, FILE *);
+static void sharg(union node *, FILE *);
+static void indent(int, char *, FILE *);
+static void trstring(shinstance *, char *);
+
+
+void
+showtree(shinstance *psh, union node *n)
+{
+ trputs(psh, "showtree called\n");
+ shtree(n, 1, NULL, stdout);
+}
+
+
+static void
+shtree(union node *n, int ind, char *pfx, FILE *fp)
+{
+ struct nodelist *lp;
+ const char *s;
+
+ if (n == NULL)
+ return;
+
+ indent(ind, pfx, fp);
+ switch(n->type) {
+ case NSEMI:
+ s = "; ";
+ goto binop;
+ case NAND:
+ s = " && ";
+ goto binop;
+ case NOR:
+ s = " || ";
+binop:
+ shtree(n->nbinary.ch1, ind, NULL, fp);
+ /* if (ind < 0) */
+ fputs(s, fp);
+ shtree(n->nbinary.ch2, ind, NULL, fp);
+ break;
+ case NCMD:
+ shcmd(n, fp);
+ if (ind >= 0)
+ putc('\n', fp);
+ break;
+ case NPIPE:
+ for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+ shcmd(lp->n, fp);
+ if (lp->next)
+ fputs(" | ", fp);
+ }
+ if (n->npipe.backgnd)
+ fputs(" &", fp);
+ if (ind >= 0)
+ putc('\n', fp);
+ break;
+ default:
+ fprintf(fp, "<node type %d>", n->type);
+ if (ind >= 0)
+ putc('\n', fp);
+ break;
+ }
+}
+
+
+
+static void
+shcmd(union node *cmd, FILE *fp)
+{
+ union node *np;
+ int first;
+ const char *s;
+ int dftfd;
+
+ first = 1;
+ for (np = cmd->ncmd.args ; np ; np = np->narg.next) {
+ if (! first)
+ putchar(' ');
+ sharg(np, fp);
+ first = 0;
+ }
+ for (np = cmd->ncmd.redirect ; np ; np = np->nfile.next) {
+ if (! first)
+ putchar(' ');
+ switch (np->nfile.type) {
+ case NTO: s = ">"; dftfd = 1; break;
+ case NCLOBBER: s = ">|"; dftfd = 1; break;
+ case NAPPEND: s = ">>"; dftfd = 1; break;
+ case NTOFD: s = ">&"; dftfd = 1; break;
+ case NFROM: s = "<"; dftfd = 0; break;
+ case NFROMFD: s = "<&"; dftfd = 0; break;
+ case NFROMTO: s = "<>"; dftfd = 0; break;
+ default: s = "*error*"; dftfd = 0; break;
+ }
+ if (np->nfile.fd != dftfd)
+ fprintf(fp, "%d", np->nfile.fd);
+ fputs(s, fp);
+ if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) {
+ fprintf(fp, "%d", np->ndup.dupfd);
+ } else {
+ sharg(np->nfile.fname, fp);
+ }
+ first = 0;
+ }
+}
+
+
+
+static void
+sharg(union node *arg, FILE *fp)
+{
+ char *p;
+ struct nodelist *bqlist;
+ int subtype;
+
+ if (arg->type != NARG) {
+ printf("<node type %d>\n", arg->type);
+ abort();
+ }
+ bqlist = arg->narg.backquote;
+ for (p = arg->narg.text ; *p ; p++) {
+ switch (*p) {
+ case CTLESC:
+ putc(*++p, fp);
+ break;
+ case CTLVAR:
+ putc('$', fp);
+ putc('{', fp);
+ subtype = *++p;
+ if (subtype == VSLENGTH)
+ putc('#', fp);
+
+ while (*p != '=')
+ putc(*p++, fp);
+
+ if (subtype & VSNUL)
+ putc(':', fp);
+
+ switch (subtype & VSTYPE) {
+ case VSNORMAL:
+ putc('}', fp);
+ break;
+ case VSMINUS:
+ putc('-', fp);
+ break;
+ case VSPLUS:
+ putc('+', fp);
+ break;
+ case VSQUESTION:
+ putc('?', fp);
+ break;
+ case VSASSIGN:
+ putc('=', fp);
+ break;
+ case VSTRIMLEFT:
+ putc('#', fp);
+ break;
+ case VSTRIMLEFTMAX:
+ putc('#', fp);
+ putc('#', fp);
+ break;
+ case VSTRIMRIGHT:
+ putc('%', fp);
+ break;
+ case VSTRIMRIGHTMAX:
+ putc('%', fp);
+ putc('%', fp);
+ break;
+ case VSLENGTH:
+ break;
+ default:
+ printf("<subtype %d>", subtype);
+ }
+ break;
+ case CTLENDVAR:
+ putc('}', fp);
+ break;
+ case CTLBACKQ:
+ case CTLBACKQ|CTLQUOTE:
+ putc('$', fp);
+ putc('(', fp);
+ shtree(bqlist->n, -1, NULL, fp);
+ putc(')', fp);
+ break;
+ default:
+ putc(*p, fp);
+ break;
+ }
+ }
+}
+
+
+static void
+indent(int amount, char *pfx, FILE *fp)
+{
+ int i;
+
+ for (i = 0 ; i < amount ; i++) {
+ if (pfx && i == amount - 1)
+ fputs(pfx, fp);
+ putc('\t', fp);
+ }
+}
+#endif
+
+
+
+#ifdef DEBUG
+/*
+ * Debugging stuff.
+ */
+
+/** @def TRY_GET_PSH_OR_RETURN
+ * Make sure @a psh is valid, trying to fetch it from TLS
+ * if it's NULL and returning (void) if that fails. */
+# define TRY_GET_PSH_OR_RETURN(psh) \
+ if (!(psh)) { \
+ (psh) = shthread_get_shell(); \
+ if (!(psh)) \
+ return; \
+ } else do { } while (0)
+
+/** @def RETURN_IF_NOT_TRACING
+ * Return if we're not tracing. */
+# define RETURN_IF_NOT_TRACING(psh) \
+ if (debug(psh) != 1 || psh->tracefd == -1) \
+ return; \
+ else do {} while (0)
+
+/* Flushes the tracebuf. */
+static void
+trace_flush(shinstance *psh)
+{
+ size_t pos = psh->tracepos;
+
+ if (pos > sizeof(psh->tracebuf)) {
+ char *end;
+ kHlpAssert(0);
+ end = memchr(psh->tracebuf, '\0', sizeof(psh->tracebuf));
+ pos = end ? end - &psh->tracebuf[0] : 0;
+ }
+
+ if (pos) {
+ int s = errno;
+ char prefix[40];
+ size_t len;
+
+#ifdef SH_FORKED_MODE
+ len = sprintf(prefix, "[%" SHPID_PRI "] ", sh_getpid(psh));
+#else
+ shpid pid = sh_getpid(psh);
+ len = sprintf(prefix, "[%d/%d] ", SHPID_GET_PID(pid), SHPID_GET_TID(pid));
+#endif
+ shfile_write(&psh->fdtab, psh->tracefd, prefix, len);
+ shfile_write(&psh->fdtab, psh->tracefd, psh->tracebuf, pos);
+
+ psh->tracepos = 0;
+ psh->tracebuf[0] = '\0';
+
+ errno = s;
+ }
+}
+
+/* Adds a char to the trace buffer. */
+static void
+trace_char(shinstance *psh, int c)
+{
+ size_t pos = psh->tracepos;
+ if (pos >= sizeof(psh->tracebuf) - 1) {
+ trace_flush(psh);
+ pos = psh->tracepos;
+ }
+ psh->tracebuf[pos] = c;
+ psh->tracepos = pos + 1;
+ if (c == '\n')
+ trace_flush(psh);
+ else
+ psh->tracebuf[pos + 1] = '\0';
+}
+
+/* Add a string to the trace buffer. */
+static void
+trace_string(shinstance *psh, const char *str)
+{
+ /* push it out line by line. */
+ while (*str) {
+ /* find line/string length. */
+ size_t pos;
+ size_t len;
+ const char *end = str;
+ int flush_it = 0;
+ while (*end) {
+ if (*end++ == '\n') {
+ flush_it = 1;
+ break;
+ }
+ }
+ len = end - str;
+
+ /* copy to the buffer */
+ pos = psh->tracepos;
+ if (pos + len <= sizeof(psh->tracebuf)) {
+ memcpy(&psh->tracebuf[pos], str, len);
+ psh->tracepos = pos + len;
+ if (flush_it)
+ trace_flush(psh);
+ } else {
+ /* it's too big for some reason... */
+ int s = errno;
+ trace_flush(psh);
+ shfile_write(&psh->fdtab, psh->tracefd, str, len);
+ if (!flush_it)
+ shfile_write(&psh->fdtab, psh->tracefd, "[too long]\n", sizeof( "[too long]\n") - 1);
+ errno = s;
+ }
+
+ /* advance */
+ str = end;
+ }
+}
+
+void
+trputc(shinstance *psh, int c)
+{
+ TRY_GET_PSH_OR_RETURN(psh);
+ RETURN_IF_NOT_TRACING(psh);
+
+ trace_char(psh, c);
+}
+
+void
+trace(shinstance *psh, const char *fmt, ...)
+{
+ va_list va;
+ char buf[2048];
+
+ TRY_GET_PSH_OR_RETURN(psh);
+ RETURN_IF_NOT_TRACING(psh);
+
+ va_start(va, fmt);
+# ifdef _MSC_VER
+ _vsnprintf(buf, sizeof(buf), fmt, va);
+# else
+ vsnprintf(buf, sizeof(buf), fmt, va);
+# endif
+ va_end(va);
+ trace_string(psh, buf);
+}
+
+void
+tracev(shinstance *psh, const char *fmt, va_list va)
+{
+ char buf[2048];
+
+ TRY_GET_PSH_OR_RETURN(psh);
+ RETURN_IF_NOT_TRACING(psh);
+
+# ifdef _MSC_VER
+ _vsnprintf(buf, sizeof(buf), fmt, va);
+# else
+ vsnprintf(buf, sizeof(buf), fmt, va);
+# endif
+ trace_string(psh, buf);
+}
+
+void
+trputs(shinstance *psh, const char *s)
+{
+ TRY_GET_PSH_OR_RETURN(psh);
+ RETURN_IF_NOT_TRACING(psh);
+
+ trace_string(psh, s);
+ trace_char(psh, '\n');
+}
+
+
+static void
+trstring(shinstance *psh, char *s)
+{
+ char *p;
+ char c;
+
+ TRY_GET_PSH_OR_RETURN(psh);
+ RETURN_IF_NOT_TRACING(psh);
+
+ trace_char(psh, '"');
+ for (p = s ; *p ; p++) {
+ switch (*p) {
+ case '\n': c = 'n'; goto backslash;
+ case '\t': c = 't'; goto backslash;
+ case '\r': c = 'r'; goto backslash;
+ case '"': c = '"'; goto backslash;
+ case '\\': c = '\\'; goto backslash;
+ case CTLESC: c = 'e'; goto backslash;
+ case CTLVAR: c = 'v'; goto backslash;
+ case CTLVAR+CTLQUOTE: c = 'V'; goto backslash;
+ case CTLBACKQ: c = 'q'; goto backslash;
+ case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash;
+backslash: trace_char(psh, '\\');
+ trace_char(psh, c);
+ break;
+ default:
+ if (*p >= ' ' && *p <= '~')
+ trace_char(psh, *p);
+ else {
+ trace_char(psh, '\\');
+ trace_char(psh, *p >> 6 & 03);
+ trace_char(psh, *p >> 3 & 07);
+ trace_char(psh, *p & 07);
+ }
+ break;
+ }
+ }
+ trace_char(psh, '"');
+}
+
+void
+trargs(shinstance *psh, char **ap)
+{
+ TRY_GET_PSH_OR_RETURN(psh);
+ RETURN_IF_NOT_TRACING(psh);
+
+ while (*ap) {
+ trstring(psh, *ap++);
+ if (*ap)
+ trace_char(psh, ' ');
+ else
+ trace_char(psh, '\n');
+ }
+}
+
+void
+opentrace(shinstance *psh)
+{
+ static const char s[] = "./trace";
+
+ TRY_GET_PSH_OR_RETURN(psh);
+ if (debug(psh) != 1) {
+ /* disabled */
+ if (psh->tracefd != -1) {
+ trace_flush(psh);
+ shfile_close(&psh->fdtab, psh->tracefd);
+ psh->tracefd = -1;
+ }
+ return;
+ }
+ /* else: (re-)enabled */
+
+ if (psh->tracefd != -1)
+ return;
+
+ psh->tracefd = shfile_open(&psh->fdtab, s, O_APPEND | O_RDWR | O_CREAT, 0600);
+ if (psh->tracefd != -1) {
+ /* relocate it */
+ int want_fd = 199;
+ while (want_fd > 10)
+ {
+ int fd2 = shfile_fcntl(&psh->fdtab, psh->tracefd, F_DUPFD, want_fd);
+ if (fd2 != -1) {
+ shfile_close(&psh->fdtab, psh->tracefd);
+ psh->tracefd = fd2;
+ break;
+ }
+ want_fd = ((want_fd + 1) / 2) - 1;
+ }
+ shfile_cloexec(&psh->fdtab, psh->tracefd, 1 /* close it */);
+ shfile_set_trace(&psh->fdtab, psh->tracefd);
+ }
+ if (psh->tracefd == -1) {
+ fprintf(stderr, "Can't open %s\n", s);
+ debug(psh) = 0;
+ return;
+ }
+ trace_string(psh, "Tracing started.\n");
+}
+
+#endif /* DEBUG */
+
diff --git a/src/kash/show.h b/src/kash/show.h
new file mode 100644
index 0000000..7e4c3d4
--- /dev/null
+++ b/src/kash/show.h
@@ -0,0 +1,50 @@
+/* $NetBSD: show.h,v 1.7 2003/08/07 09:05:38 agc Exp $ */
+
+/*-
+ * Copyright (c) 1995
+ * 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.
+ *
+ * @(#)show.h 1.1 (Berkeley) 5/4/95
+ */
+
+#ifndef ___show_h
+#define ___show_h
+
+#include <stdarg.h>
+
+union node;
+void showtree(struct shinstance *, union node *);
+#ifdef DEBUG
+void trace(struct shinstance *, const char *, ...);
+void tracev(struct shinstance *, const char *, va_list);
+void trargs(struct shinstance *, char **);
+void trputc(struct shinstance *, int);
+void trputs(struct shinstance *, const char *);
+void opentrace(struct shinstance *);
+#endif
+
+#endif
diff --git a/src/kash/shthread.c b/src/kash/shthread.c
new file mode 100644
index 0000000..3a86801
--- /dev/null
+++ b/src/kash/shthread.c
@@ -0,0 +1,151 @@
+/* $Id: shthread.c 3505 2021-12-15 22:53:57Z bird $ */
+/** @file
+ *
+ * Shell Thread Management.
+ *
+ * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "shthread.h"
+#include "shinstance.h"
+
+#if K_OS == K_OS_WINDOWS
+# include <Windows.h>
+#elif K_OS == K_OS_OS2
+# include <InnoTekLIBC/FastInfoBlocks.h>
+# include <InnoTekLIBC/thread.h>
+#else
+# include <pthread.h>
+#endif
+
+
+/*******************************************************************************
+* Global Variables *
+*******************************************************************************/
+#if K_OS == K_OS_WINDOWS
+static DWORD sh_tls = TLS_OUT_OF_INDEXES;
+#elif K_OS == K_OS_OS2
+static int sh_tls = -1;
+#else
+static int sh_tls_inited = 0;
+static pthread_key_t sh_tls;
+#endif
+
+
+/**
+ * Stores the shell instance pointer in a TLS entry.
+ *
+ * This will allocate the TLS entry on the first call. We assume
+ * there will no be races at that time.
+ *
+ * @param psh The shell instance.
+ */
+void shthread_set_shell(struct shinstance *psh)
+{
+#if K_OS == K_OS_WINDOWS
+ if (sh_tls == TLS_OUT_OF_INDEXES)
+ {
+ sh_tls = TlsAlloc();
+ kHlpAssert(sh_tls != TLS_OUT_OF_INDEXES);
+ }
+ if (!TlsSetValue(sh_tls, psh))
+ kHlpAssert(0);
+
+#elif K_OS == K_OS_OS2
+ if (sh_tls == -1)
+ {
+ sh_tls = __libc_TLSAlloc();
+ kHlpAssert(sh_tls != -1);
+ }
+ if (__libc_TLSSet(sh_tls, psh) == -1)
+ kHlpAssert(0);
+#else
+ if (!sh_tls_inited)
+ {
+ if (pthread_key_create(&sh_tls, NULL) != 0)
+ kHlpAssert(0);
+ sh_tls_inited = 1;
+ }
+ if (pthread_setspecific(sh_tls, psh) != 0)
+ kHlpAssert(0);
+#endif
+}
+
+/**
+ * Get the shell instance pointer from TLS.
+ *
+ * @returns The shell instance.
+ */
+struct shinstance *shthread_get_shell(void)
+{
+ shinstance *psh;
+#if K_OS == K_OS_WINDOWS
+ psh = (shinstance *)TlsGetValue(sh_tls);
+#elif K_OS == K_OS_OS2
+ psh = (shinstance *)__libc_TLSGet(sh_tls);
+#else
+ psh = (shinstance *)pthread_getspecific(sh_tls);
+#endif
+ return psh;
+}
+
+
+/**
+ * Sets the name of the current thread if supported by the OS.
+ */
+void shthread_set_name(const char *name)
+{
+#if K_OS == K_OS_WINDOWS
+ typedef BOOL (WINAPI * PFNSETTHREADDESCRIPTION)(HANDLE, WCHAR *);
+ static KBOOL volatile s_initialized = K_FALSE;
+ static PFNSETTHREADDESCRIPTION volatile s_pfnSetThreadDescription = NULL;
+ PFNSETTHREADDESCRIPTION pfnSetThreadDescription = s_pfnSetThreadDescription;
+ WCHAR wszName[32];
+ size_t i;
+
+ /* Get the function pointer, return if not available. */
+ if (pfnSetThreadDescription)
+ { }
+ else if (s_initialized)
+ return;
+ else
+ {
+ pfnSetThreadDescription = (PFNSETTHREADDESCRIPTION)GetProcAddress(GetModuleHandleW(L"KERNEL32.DLL"),
+ "SetThreadDescription");
+ s_pfnSetThreadDescription = pfnSetThreadDescription;
+ s_initialized = K_TRUE;
+ if (!pfnSetThreadDescription)
+ return;
+ }
+
+ /* Convert the name to UTF-16 and call the API. */
+ i = strlen(name);
+ kHlpAssertStmt(i < K_ELEMENTS(wszName), i = K_ELEMENTS(wszName));
+ wszName[i] = '\0';
+ while (i-- > 0)
+ wszName[i] = name[i];
+
+ pfnSetThreadDescription(GetCurrentThread(), wszName);
+#else
+ K_NOREF(name);
+#endif
+}
+
diff --git a/src/kash/shthread.h b/src/kash/shthread.h
new file mode 100644
index 0000000..046cee3
--- /dev/null
+++ b/src/kash/shthread.h
@@ -0,0 +1,89 @@
+/* $Id: shthread.h 3515 2021-12-16 12:54:03Z bird $ */
+/** @file
+ *
+ * Shell thread methods.
+ *
+ * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef ___shthread_h___
+#define ___shthread_h___
+
+#include "shtypes.h"
+
+typedef union shmtx
+{
+ char b[64];
+ KU64 au64[64/sizeof(KU64)];
+ void *aptrs[64/sizeof(void *)];
+} shmtx;
+
+/** Magic mutex value (final u64).
+ * This is used to detect whether the mutex has been initialized or not,
+ * allowing shmtx_delete to be called more than once without doing harm.
+ * @internal */
+#define SHMTX_MAGIC KU64_C(0x8888000019641018) /**< Charles Stross */
+/** Index into shmtx::au64 of the SHMTX_MAGIC value.
+ * @internal */
+#define SHMTX_MAGIC_IDX (sizeof(shmtx) / sizeof(KU64) - 1)
+
+typedef struct shmtxtmp { int i; } shmtxtmp;
+
+typedef uintptr_t shtid;
+
+void shthread_set_shell(struct shinstance *);
+struct shinstance *shthread_get_shell(void);
+void shthread_set_name(const char *name);
+
+int shmtx_init(shmtx *pmtx);
+void shmtx_delete(shmtx *pmtx);
+void shmtx_enter(shmtx *pmtx, shmtxtmp *ptmp);
+void shmtx_leave(shmtx *pmtx, shmtxtmp *ptmp);
+
+
+K_INLINE unsigned sh_atomic_inc(KU32 volatile *valuep)
+{
+#ifdef _MSC_VER
+ return _InterlockedIncrement((long *)valuep);
+#elif defined(__GNUC__) && (K_ARCH == K_ARCH_AMD64 || K_ARCH == K_ARCH_X86_32)
+ unsigned uRet;
+ __asm__ __volatile__("lock; xaddl %1, %0" : "=m" (*valuep), "=r" (uRet) : "m" (*valuep), "1" (1) : "memory", "cc");
+ return uRet + 1;
+#else
+ return __sync_add_and_fetch(valuep, 1);
+#endif
+}
+
+K_INLINE unsigned sh_atomic_dec(unsigned volatile *valuep)
+{
+#ifdef _MSC_VER
+ return _InterlockedDecrement((long *)valuep);
+#elif defined(__GNUC__) && (K_ARCH == K_ARCH_AMD64 || K_ARCH == K_ARCH_X86_32)
+ unsigned uRet;
+ __asm__ __volatile__("lock; xaddl %1, %0" : "=m" (*valuep), "=r" (uRet) : "m" (*valuep), "1" (-1) : "memory", "cc");
+ return uRet - 1;
+#else
+ return __sync_sub_and_fetch(valuep, 1);
+#endif
+}
+
+#endif
+
diff --git a/src/kash/shtypes.h b/src/kash/shtypes.h
new file mode 100644
index 0000000..f5c8ff0
--- /dev/null
+++ b/src/kash/shtypes.h
@@ -0,0 +1,150 @@
+/* $Id: shtypes.h 3477 2020-09-17 21:52:16Z bird $ */
+/** @file
+ * Wrapper for missing types and such.
+ */
+
+/*
+ * Copyright (c) 2007-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef ___shtypes_h___
+#define ___shtypes_h___
+
+#include "k/kTypes.h" /* Use these, not the ones below. */
+#include "k/kHlpAssert.h"
+
+#include <sys/types.h>
+#include <stdlib.h>
+#ifdef __HAIKU__
+# include <posix/signal.h> /* silly */
+#elif !defined(_MSC_VER)
+# include <sys/signal.h>
+#endif
+
+#ifdef _MSC_VER
+typedef signed char int8_t;
+typedef unsigned char uint8_t;
+typedef short int16_t;
+typedef unsigned short uint16_t;
+typedef int int32_t;
+typedef unsigned int uint32_t;
+typedef _int64 int64_t;
+typedef unsigned _int64 uint64_t;
+# if _MSC_VER >= 1400
+# include <io.h> /* intptr_t and uintptr_t */
+# else
+typedef KIPTR intptr_t;
+typedef KUPTR uintptr_t;
+# endif
+
+#define INT16_C(c) (c)
+#define INT32_C(c) (c)
+#define INT64_C(c) (c ## LL)
+
+#define UINT8_C(c) (c)
+#define UINT16_C(c) (c)
+#define UINT32_C(c) (c ## U)
+#define UINT64_C(c) (c ## ULL)
+
+#define INTMAX_C(c) (c ## LL)
+#define UINTMAX_C(c) (c ## ULL)
+
+#undef INT8_MIN
+#define INT8_MIN (-0x7f-1)
+#undef INT16_MIN
+#define INT16_MIN (-0x7fff-1)
+#undef INT32_MIN
+#define INT32_MIN (-0x7fffffff-1)
+#undef INT64_MIN
+#define INT64_MIN (-0x7fffffffffffffffLL-1)
+
+#undef INT8_MAX
+#define INT8_MAX 0x7f
+#undef INT16_MAX
+#define INT16_MAX 0x7fff
+#undef INT32_MAX
+#define INT32_MAX 0x7fffffff
+#undef INT64_MAX
+#define INT64_MAX 0x7fffffffffffffffLL
+
+#undef UINT8_MAX
+#define UINT8_MAX 0xff
+#undef UINT16_MAX
+#define UINT16_MAX 0xffff
+#undef UINT32_MAX
+#define UINT32_MAX 0xffffffffU
+#undef UINT64_MAX
+#define UINT64_MAX 0xffffffffffffffffULL
+
+typedef int pid_t;
+typedef unsigned short uid_t;
+typedef unsigned short gid_t;
+typedef int mode_t;
+typedef intptr_t ssize_t;
+
+#else
+# include <stdint.h>
+#endif
+
+struct shinstance;
+typedef struct shinstance shinstance;
+
+#ifdef _MSC_VER
+typedef uint32_t shsigset_t;
+#else
+typedef sigset_t shsigset_t;
+#endif
+
+typedef void (*shsig_t)(shinstance *, int);
+typedef struct shsigaction
+{
+ shsig_t sh_handler;
+ shsigset_t sh_mask;
+ int sh_flags;
+} shsigaction_t;
+
+/* SH_NORETURN_1 must be both on prototypes and definitions, while
+ SH_NORETURN_2 should at least be on the prototype. */
+#ifdef _MSC_VER
+# define SH_NORETURN_1 __declspec(noreturn)
+# define SH_NORETURN_2
+#else
+# define SH_NORETURN_1
+# define SH_NORETURN_2 __attribute__((__noreturn__))
+#endif
+
+/** @name Extra wide pid_t so we can safely add a sub-pid to the top.
+ * @{ */
+#ifndef SH_FORKED_MODE
+typedef KI64 shpid;
+# define SHPID_MAKE(pid, tid) ((shpid)(KU32)(pid) | (shpid)(KU32)(tid) << 32)
+# define SHPID_GET_PID(shpid) ((pid_t)(KU32)(shpid))
+# define SHPID_GET_TID(shpid) ((pid_t)((shpid) >> 32))
+# define SHPID_PRI KI64_PRI
+#else
+typedef pid_t shpid;
+# define SHPID_GET_PID(shpid) (shpid)
+# define SHPID_PRI KI32_PRI
+#endif
+/** @} */
+
+#endif
+
diff --git a/src/kash/strlcpy.c b/src/kash/strlcpy.c
new file mode 100644
index 0000000..7a1d0df
--- /dev/null
+++ b/src/kash/strlcpy.c
@@ -0,0 +1,72 @@
+/* $OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $ */
+
+/*
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ * 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. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
+ */
+
+#if 0
+#if defined(LIBC_SCCS) && !defined(lint)
+static char *rcsid = "$OpenBSD: strlcpy.c,v 1.4 1999/05/01 18:56:41 millert Exp $");
+#include <sys/cdefs.h>
+//__FBSDID("$FreeBSD: src/lib/libc/string/strlcpy.c,v 1.7 2003/05/01 19:03:14 nectar Exp $");
+#endif /* LIBC_SCCS and not lint */
+#endif
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Copy src to string dst of size siz. At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t strlcpy(dst, src, siz)
+ char *dst;
+ const char *src;
+ size_t siz;
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0 && --n != 0) {
+ do {
+ if ((*d++ = *s++) == 0)
+ break;
+ } while (--n != 0);
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0)
+ *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return(s - src - 1); /* count does not include NUL */
+}
diff --git a/src/kash/strsignal.c b/src/kash/strsignal.c
new file mode 100644
index 0000000..6bcd9db
--- /dev/null
+++ b/src/kash/strsignal.c
@@ -0,0 +1,14 @@
+/*
+ * Fake strsignal (for Windows/MSC).
+ */
+
+#include "shinstance.h" /* for MSC */
+#include <string.h>
+
+const char *strsignal(int iSig)
+{
+ if (iSig < NSIG)
+ return sys_signame[iSig];
+ return NULL;
+}
+
diff --git a/src/kash/syntax.c b/src/kash/syntax.c
new file mode 100644
index 0000000..d1753ef
--- /dev/null
+++ b/src/kash/syntax.c
@@ -0,0 +1,209 @@
+/* $NetBSD: syntax.c,v 1.1 2004/01/17 17:38:12 dsl Exp $ */
+
+#include "shell.h"
+#include "syntax.h"
+#include "parser.h"
+#include "shinstance.h"
+
+#ifdef _MSC_VER /* doesn't implement the fancy initializers I think... */
+
+char basesyntax[257] = {CSHEOF};
+char dqsyntax[257] = {CSHEOF};
+char sqsyntax[257] = {CSHEOF};
+char arisyntax[257] = {CSHEOF};
+char is_type[257] = {0};
+
+void init_syntax(void)
+{
+ char *tab;
+ int i;
+
+#define ndx(ch) (ch + 1 - CHAR_MIN)
+#define set(ch, val) tab[ndx(ch)] = val
+#define set_range(s, e, val) for (i = ndx(s); i <= ndx(e); i++) tab[i] = val
+
+ /*basesyntax*/
+ tab = basesyntax;
+ set_range(CTL_FIRST, CTL_LAST, CCTL);
+ set('\n', CNL);
+ set('\\', CBACK);
+ set('\'', CSQUOTE);
+ set('"', CDQUOTE);
+ set('`', CBQUOTE);
+ set('$', CVAR);
+ set('}', CENDVAR);
+ set('<', CSPCL);
+ set('>', CSPCL);
+ set('(', CSPCL);
+ set(')', CSPCL);
+ set(';', CSPCL);
+ set('&', CSPCL);
+ set('|', CSPCL);
+ set(' ', CSPCL);
+ set('\t', CSPCL);
+
+ tab = dqsyntax;
+ set_range(CTL_FIRST, CTL_LAST, CCTL);
+ set('\n', CNL);
+ set('\\', CBACK);
+ set('"', CDQUOTE);
+ set('`', CBQUOTE);
+ set('$', CVAR);
+ set('}', CENDVAR);
+ /* ':/' for tilde expansion, '-' for [a\-x] pattern ranges */
+ set('!', CCTL);
+ set('*', CCTL);
+ set('?', CCTL);
+ set('[', CCTL);
+ set('=', CCTL);
+ set('~', CCTL);
+ set(':', CCTL);
+ set('/', CCTL);
+ set('-', CCTL);
+
+ tab = sqsyntax;
+ set_range(CTL_FIRST, CTL_LAST, CCTL);
+ set('\n', CNL);
+ set('\'', CSQUOTE);
+ /* ':/' for tilde expansion, '-' for [a\-x] pattern ranges */
+ set('!', CCTL);
+ set('*', CCTL) ;
+ set('?', CCTL);
+ set('[', CCTL);
+ set('=', CCTL);
+ set('~', CCTL);
+ set(':', CCTL);
+ set('/', CCTL);
+ set('-', CCTL);
+
+ tab = arisyntax;
+ set_range(CTL_FIRST, CTL_LAST, CCTL);
+ set('\n', CNL);
+ set('\\', CBACK);
+ set('`', CBQUOTE);
+ set('\'', CSQUOTE);
+ set('"', CDQUOTE);
+ set('$', CVAR);
+ set('}', CENDVAR);
+ set('(', CLP);
+ set(')', CRP);
+
+ tab = is_type;
+ set_range('0', '9', ISDIGIT);
+ set_range('a', 'z', ISLOWER);
+ set_range('A', 'Z', ISUPPER);
+ set('_', ISUNDER);
+ set('#', ISSPECL);
+ set('?', ISSPECL);
+ set('$', ISSPECL);
+ set('!', ISSPECL);
+ set('-', ISSPECL);
+ set('*', ISSPECL);
+ set('@', ISSPECL);
+}
+
+#else /* !_MSC_VER */
+
+#if CWORD != 0
+#error initialisation assumes 'CWORD' is zero
+#endif
+
+#define ndx(ch) (ch + 1 - CHAR_MIN)
+#define set(ch, val) [ndx(ch)] = val,
+#define set_range(s, e, val) [ndx(s) ... ndx(e)] = val,
+
+/* Shut up the pedantic warnings about set_range() */
+#ifdef __GNUC__
+# if __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2)
+# pragma GCC diagnostic ignored "-Wpedantic"
+# endif
+#endif
+
+/* syntax table used when not in quotes */
+const char basesyntax[257] = { CSHEOF,
+ set_range(CTL_FIRST, CTL_LAST, CCTL)
+ set('\n', CNL)
+ set('\\', CBACK)
+ set('\'', CSQUOTE)
+ set('"', CDQUOTE)
+ set('`', CBQUOTE)
+ set('$', CVAR)
+ set('}', CENDVAR)
+ set('<', CSPCL)
+ set('>', CSPCL)
+ set('(', CSPCL)
+ set(')', CSPCL)
+ set(';', CSPCL)
+ set('&', CSPCL)
+ set('|', CSPCL)
+ set(' ', CSPCL)
+ set('\t', CSPCL)
+};
+
+/* syntax table used when in double quotes */
+const char dqsyntax[257] = { CSHEOF,
+ set_range(CTL_FIRST, CTL_LAST, CCTL)
+ set('\n', CNL)
+ set('\\', CBACK)
+ set('"', CDQUOTE)
+ set('`', CBQUOTE)
+ set('$', CVAR)
+ set('}', CENDVAR)
+ /* ':/' for tilde expansion, '-' for [a\-x] pattern ranges */
+ set('!', CCTL)
+ set('*', CCTL)
+ set('?', CCTL)
+ set('[', CCTL)
+ set('=', CCTL)
+ set('~', CCTL)
+ set(':', CCTL)
+ set('/', CCTL)
+ set('-', CCTL)
+};
+
+/* syntax table used when in single quotes */
+const char sqsyntax[257] = { CSHEOF,
+ set_range(CTL_FIRST, CTL_LAST, CCTL)
+ set('\n', CNL)
+ set('\'', CSQUOTE)
+ /* ':/' for tilde expansion, '-' for [a\-x] pattern ranges */
+ set('!', CCTL)
+ set('*', CCTL)
+ set('?', CCTL)
+ set('[', CCTL)
+ set('=', CCTL)
+ set('~', CCTL)
+ set(':', CCTL)
+ set('/', CCTL)
+ set('-', CCTL)
+};
+
+/* syntax table used when in arithmetic */
+const char arisyntax[257] = { CSHEOF,
+ set_range(CTL_FIRST, CTL_LAST, CCTL)
+ set('\n', CNL)
+ set('\\', CBACK)
+ set('`', CBQUOTE)
+ set('\'', CSQUOTE)
+ set('"', CDQUOTE)
+ set('$', CVAR)
+ set('}', CENDVAR)
+ set('(', CLP)
+ set(')', CRP)
+};
+
+/* character classification table */
+const char is_type[257] = { 0,
+ set_range('0', '9', ISDIGIT)
+ set_range('a', 'z', ISLOWER)
+ set_range('A', 'Z', ISUPPER)
+ set('_', ISUNDER)
+ set('#', ISSPECL)
+ set('?', ISSPECL)
+ set('$', ISSPECL)
+ set('!', ISSPECL)
+ set('-', ISSPECL)
+ set('*', ISSPECL)
+ set('@', ISSPECL)
+};
+#endif /* !_MSC_VER */
diff --git a/src/kash/syntax.h b/src/kash/syntax.h
new file mode 100644
index 0000000..2c6aab8
--- /dev/null
+++ b/src/kash/syntax.h
@@ -0,0 +1,91 @@
+/* $NetBSD: syntax.h,v 1.2 2004/01/17 17:38:12 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#include <ctype.h>
+#include <limits.h>
+
+/* Syntax classes */
+#define CWORD 0 /* character is nothing special */
+#define CNL 1 /* newline character */
+#define CBACK 2 /* a backslash character */
+#define CSQUOTE 3 /* single quote */
+#define CDQUOTE 4 /* double quote */
+#define CBQUOTE 5 /* backwards single quote */
+#define CVAR 6 /* a dollar sign */
+#define CENDVAR 7 /* a '}' character */
+#define CLP 8 /* a left paren in arithmetic */
+#define CRP 9 /* a right paren in arithmetic */
+#define CSHEOF 10 /* end of file */
+#define CCTL 11 /* like CWORD, except it must be escaped */
+#define CSPCL 12 /* these terminate a word */
+
+/* Syntax classes for is_ functions */
+#define ISDIGIT 01 /* a digit */
+#define ISUPPER 02 /* an upper case letter */
+#define ISLOWER 04 /* a lower case letter */
+#define ISUNDER 010 /* an underscore */
+#define ISSPECL 020 /* the name of a special parameter */
+
+#define PEOF (CHAR_MIN - 1)
+#define SYNBASE (-PEOF)
+/* XXX UPEOF is CHAR_MAX, so is a valid 'char' value... */
+#define UPEOF ((char)PEOF)
+
+
+#define BASESYNTAX (basesyntax + SYNBASE)
+#define DQSYNTAX (dqsyntax + SYNBASE)
+#define SQSYNTAX (sqsyntax + SYNBASE)
+#define ARISYNTAX (arisyntax + SYNBASE)
+
+/* These defines assume that the digits are contiguous */
+#define is_digit(c) ((unsigned)((c) - '0') <= 9)
+#define is_alpha(c) (((char)(c)) != UPEOF && ((c) < CTL_FIRST || (c) > CTL_LAST) && isalpha((unsigned char)(c)))
+#define is_name(c) (((char)(c)) != UPEOF && ((c) < CTL_FIRST || (c) > CTL_LAST) && ((c) == '_' || isalpha((unsigned char)(c))))
+#define is_in_name(c) (((char)(c)) != UPEOF && ((c) < CTL_FIRST || (c) > CTL_LAST) && ((c) == '_' || isalnum((unsigned char)(c))))
+#define is_special(c) ((is_type+SYNBASE)[c] & (ISSPECL|ISDIGIT))
+#define digit_val(c) ((c) - '0')
+
+#ifdef _MSC_VER
+extern char basesyntax[];
+extern char dqsyntax[];
+extern char sqsyntax[];
+extern char arisyntax[];
+extern char is_type[];
+#else
+extern const char basesyntax[];
+extern const char dqsyntax[];
+extern const char sqsyntax[];
+extern const char arisyntax[];
+extern const char is_type[];
+#endif
diff --git a/src/kash/tests/Makefile.kmk b/src/kash/tests/Makefile.kmk
new file mode 100644
index 0000000..40e081d
--- /dev/null
+++ b/src/kash/tests/Makefile.kmk
@@ -0,0 +1,75 @@
+# $Id: Makefile.kmk 3433 2020-09-02 17:25:31Z bird $
+## @file
+# Sub-makefile for kash tests.
+#
+
+#
+# Copyright (c) 2005-2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+#
+# This file is part of kBuild.
+#
+# kBuild is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# kBuild is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with kBuild. If not, see <http://www.gnu.org/licenses/>
+#
+#
+
+SUB_DEPTH = ../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+#
+# The program.
+#
+
+TESTING += kash_tests
+
+KASH_TEST_BIN = $(if $(kash_1_TARGET),$(kash_1_TARGET),$(PATH_INS)/$(TEMPLATE_BIN_INST)kmk_ash$(SUFF_EXE))
+KASH_TEST_DIR := $(PATH_SUB_CURRENT)
+KASH_TESTCASES := $(addprefix $(KASH_TEST_DIR)/,\
+ trap-exit-1 \
+ trap-int-1 \
+ trap-term-1 \
+ tick-1 \
+ redirect-1 \
+ redirect-2 \
+ redirect-3 \
+ pipe-1 \
+ pipe-2 \
+ )
+
+# exec-1 - lost
+
+
+kash_tests::
+ $(ECHO) "kash tests..."
+ @export KASH_TEST_DIR=$(KASH_TEST_DIR); \
+ KASH_FAILURE=0; \
+ $(foreach test,$(KASH_TESTCASES)\
+ ,echo " * $(KASH_TEST_BIN) $(test)"; \
+ if ! $(KASH_TEST_BIN) $(test); then \
+ echo " => FAILURE!"; \
+ KASH_FAILURE=`$(EXPR_EXT) $${KASH_FAILURE} + 1`; \
+ fi; \
+ ) \
+ if test $$KASH_FAILURE -eq 0; then \
+ echo 'kash tests: All tests succeeded.'; \
+ else \
+ echo "kash tests: $$KASH_FAILURE tests failed"'!!'; \
+ echo ""; \
+ exit 1; \
+ fi
+
+
+
+include $(FILE_KBUILD_SUB_FOOTER)
+
+
diff --git a/src/kash/tests/common-include.sh b/src/kash/tests/common-include.sh
new file mode 100755
index 0000000..199f42f
--- /dev/null
+++ b/src/kash/tests/common-include.sh
@@ -0,0 +1,14 @@
+# File to be sourced. Contains pointers to a bunch of shell utilities.
+
+CMD_PREFIX=kmk_
+CMD_CAT=${CMD_PREFIX}cat
+CMD_CP=${CMD_PREFIX}cp
+CMD_EXPR=${CMD_PREFIX}expr
+CMD_INSTALL=${CMD_PREFIX}install
+CMD_LN=${CMD_PREFIX}ln
+CMD_MV=${CMD_PREFIX}mv
+CMD_RM=${CMD_PREFIX}rm
+CMD_SED=${CMD_PREFIX}sed
+CMD_SLEEP=${CMD_PREFIX}sleep
+CMD_TIME=${CMD_PREFIX}time
+
diff --git a/src/kash/tests/netbsd/exit1 b/src/kash/tests/netbsd/exit1
new file mode 100755
index 0000000..5139fe3
--- /dev/null
+++ b/src/kash/tests/netbsd/exit1
@@ -0,0 +1,7 @@
+#!/bin/sh
+x=`( trap 'echo exiting' EXIT; /usr/bin/true )`
+if [ -z "$x" ]
+then
+ echo failed
+ exit 1
+fi
diff --git a/src/kash/tests/netbsd/var1 b/src/kash/tests/netbsd/var1
new file mode 100755
index 0000000..68267af
--- /dev/null
+++ b/src/kash/tests/netbsd/var1
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+line='/foo/bar/*/baz'
+if [ "/foo/bar/" != ${line%%\**}"" ]
+then
+ echo broken
+ exit 1
+fi
diff --git a/src/kash/tests/netbsd/waitjob b/src/kash/tests/netbsd/waitjob
new file mode 100755
index 0000000..e2cc333
--- /dev/null
+++ b/src/kash/tests/netbsd/waitjob
@@ -0,0 +1,8 @@
+#!/bin/sh
+sleep 3 &
+sleep 1 &
+
+wait %1
+[ $? = 0 ] || echo fail1
+wait %2
+[ $? = 0 ] || echo fail2
diff --git a/src/kash/tests/pipe-1 b/src/kash/tests/pipe-1
new file mode 100644
index 0000000..fbd6833
--- /dev/null
+++ b/src/kash/tests/pipe-1
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# Pipes input from an builtin command thru an external one.
+
+. ${KASH_TEST_DIR}/common-include.sh
+
+TMPFILE="/tmp/pipe-1.$$.tmp"
+
+echo piped | $CMD_SED -e 's/piped/1/' > $TMPFILE
+VAR=`$CMD_CAT $TMPFILE`
+$CMD_RM -f $TMPFILE
+if test "$VAR" != "1"; then
+ echo "pipe-1: FAILURE - VAR=$VAR"
+ exit 1
+fi
+echo "pipe-1: SUCCESS"
+exit 0
+
diff --git a/src/kash/tests/pipe-2 b/src/kash/tests/pipe-2
new file mode 100644
index 0000000..6ba2637
--- /dev/null
+++ b/src/kash/tests/pipe-2
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+# Pipes input from an builtin command thru an external one.
+
+. ${KASH_TEST_DIR}/common-include.sh
+
+TMPFILE1="/tmp/pipe-2a.$$.tmp"
+TMPFILE2="/tmp/pipe-2b.$$.tmp"
+echo piped > $TMPFILE1
+$CMD_CAT $TMPFILE1 \
+ | $CMD_SED -e 's/piped/abc/' \
+ | $CMD_SED -e 's/abc/def/' \
+ | $CMD_SED -e 's/def/ghi/' \
+ | $CMD_SED -e 's/ghi/jkl/' \
+ | $CMD_SED -e 's/jkl/mno/' \
+ | $CMD_SED -e 's/mno/pqr/' \
+ | $CMD_SED -e 's/pqr/stu/' \
+ | $CMD_SED -e 's/stu/vwx/' \
+ | $CMD_SED -e 's/vwx/yz_/' \
+ > $TMPFILE2
+
+VAR=`$CMD_CAT $TMPFILE2`
+$CMD_RM -f -- $TMPFILE1 $TMPFILE2
+if test "$VAR" != "yz_"; then
+ echo "pipe-2: FAILURE - VAR=$VAR"
+ exit 1
+fi
+echo "pipe-2: SUCCESS"
+exit 0
+
diff --git a/src/kash/tests/redirect-1 b/src/kash/tests/redirect-1
new file mode 100755
index 0000000..350e56a
--- /dev/null
+++ b/src/kash/tests/redirect-1
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# Redirect output from a builtin command.
+
+. ${KASH_TEST_DIR}/common-include.sh
+
+TMPFILE="/tmp/redirect-1.$$.tmp"
+
+echo 1 > $TMPFILE
+VAR=`$CMD_CAT $TMPFILE`
+$CMD_RM -f $TMPFILE
+if test "$VAR" != "1"; then
+ echo "redirect-1: FAILURE - VAR=$VAR"
+ exit 1
+fi
+echo "redirect-1: SUCCESS"
+exit 0
+
diff --git a/src/kash/tests/redirect-2 b/src/kash/tests/redirect-2
new file mode 100755
index 0000000..09bd905
--- /dev/null
+++ b/src/kash/tests/redirect-2
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+# Redirect output from builtin commands in a subshell.
+
+. ${KASH_TEST_DIR}/common-include.sh
+
+TMPFILE="/tmp/redirect-2.$$.tmp"
+
+(echo -n 1 ; echo -n 2 ; echo -n 3 ) > $TMPFILE
+VAR=`$CMD_CAT $TMPFILE`
+$CMD_RM -f $TMPFILE
+if test "$VAR" != "123"; then
+ echo "redirect-2: FAILURE - VAR=$VAR"
+ exit 1
+fi
+echo "redirect-2: SUCCESS"
+exit 0
+
+
diff --git a/src/kash/tests/redirect-3 b/src/kash/tests/redirect-3
new file mode 100755
index 0000000..3cb30e4
--- /dev/null
+++ b/src/kash/tests/redirect-3
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+# Redirect input to an external command.
+
+. ${KASH_TEST_DIR}/common-include.sh
+
+TMPFILE="/tmp/redirect-3.$$.tmp"
+
+echo 1 > $TMPFILE
+echo 2 >> $TMPFILE
+echo 3 >> $TMPFILE
+VAR=`$CMD_SED -e '/2/!d' < $TMPFILE`
+$CMD_RM -f $TMPFILE
+if test "$VAR" != "2"; then
+ echo "redirect-3: FAILURE - VAR=$VAR."
+ exit 1
+fi
+echo "redirect-3: SUCCESS"
+exit 0
+
diff --git a/src/kash/tests/tick-1 b/src/kash/tests/tick-1
new file mode 100755
index 0000000..1a64f14
--- /dev/null
+++ b/src/kash/tests/tick-1
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+VAR=`echo "echoed string"`
+if test "$VAR" != "echoed string"; then
+ echo "tick-1: failure: VAR=$VAR"
+ exit 1
+fi
+echo 'tick-1: SUCCESS'
+exit 0
+
diff --git a/src/kash/tests/trap-exit-1 b/src/kash/tests/trap-exit-1
new file mode 100755
index 0000000..50b805c
--- /dev/null
+++ b/src/kash/tests/trap-exit-1
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+trap 'echo "trap-exit-1: overriding exit 1"; exit 0' EXIT
+exit 1
+exit 2
+
+
diff --git a/src/kash/tests/trap-int-1 b/src/kash/tests/trap-int-1
new file mode 100755
index 0000000..79e2dbb
--- /dev/null
+++ b/src/kash/tests/trap-int-1
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+trap 'echo "trap-int-1: caught SIGINT"; exit 0' INT
+kill -INT $$
+exit 2
+
diff --git a/src/kash/tests/trap-term-1 b/src/kash/tests/trap-term-1
new file mode 100755
index 0000000..e9e94d5
--- /dev/null
+++ b/src/kash/tests/trap-term-1
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+trap 'echo "trap-term-1: caught SIGTERM"; exit 0' TERM
+kill -TERM $$
+exit 2
+
diff --git a/src/kash/trap.c b/src/kash/trap.c
new file mode 100644
index 0000000..df2d4a3
--- /dev/null
+++ b/src/kash/trap.c
@@ -0,0 +1,471 @@
+/* $NetBSD: trap.c,v 1.31 2005/01/11 19:38:57 christos Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)trap.c 8.5 (Berkeley) 6/5/95";
+#else
+__RCSID("$NetBSD: trap.c,v 1.31 2005/01/11 19:38:57 christos Exp $");
+#endif /* not lint */
+#endif
+
+#include <stdlib.h>
+
+#include "shell.h"
+#include "main.h"
+#include "nodes.h" /* for other headers */
+#include "eval.h"
+#include "jobs.h"
+#include "show.h"
+#include "options.h"
+#include "syntax.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "trap.h"
+#include "mystring.h"
+#include "var.h"
+#include "shinstance.h"
+
+
+/*
+ * Sigmode records the current value of the signal handlers for the various
+ * modes. A value of zero means that the current handler is not known.
+ * S_HARD_IGN indicates that the signal was ignored on entry to the shell,
+ */
+
+#define S_DFL 1 /* default signal handling (SIG_DFL) */
+#define S_CATCH 2 /* signal is caught */
+#define S_IGN 3 /* signal is ignored (SIG_IGN) */
+#define S_HARD_IGN 4 /* signal is ignored permenantly */
+#define S_RESET 5 /* temporary - to reset a hard ignored sig */
+
+
+//char *trap[NSIG+1]; /* trap handler commands */
+//MKINIT char sigmode[NSIG]; /* current value of signal */
+//char gotsig[NSIG]; /* indicates specified signal received */
+//int pendingsigs; /* indicates some signal received */
+
+static int getsigaction(shinstance *, int, shsig_t *);
+
+#ifndef SH_FORKED_MODE
+void
+subshellinittrap(shinstance *psh, shinstance *inherit)
+{
+ /* The forkchild calls clear_traps(), we have to emulate that here. */
+ unsigned i;
+ memcpy(psh->sigmode, inherit->sigmode, sizeof(psh->sigmode));
+ for (i = 0; i < K_ELEMENTS(inherit->trap); i++) {
+ const char *src = inherit->trap[i];
+ if (!src) {
+ } else if (i > 0) {
+ setsignal(psh, i);
+ }
+ }
+}
+#endif
+
+
+/*
+ * return the signal number described by `p' (as a number or a name)
+ * or -1 if it isn't one
+ */
+
+static int
+signame_to_signum(shinstance *psh, const char *p)
+{
+ int i;
+
+ if (is_number(p))
+ return number(psh, p);
+
+ if (strcasecmp(p, "exit") == 0 )
+ return 0;
+
+ if (strncasecmp(p, "sig", 3) == 0)
+ p += 3;
+
+ for (i = 0; i < NSIG; ++i)
+ if (strcasecmp(p, sys_signame[i]) == 0)
+ return i;
+ return -1;
+}
+
+/*
+ * Print a list of valid signal names
+ */
+static void
+printsignals(shinstance *psh)
+{
+ int n;
+
+ out1str(psh, "EXIT ");
+
+ for (n = 1; n < NSIG; n++) {
+ out1fmt(psh, "%s", sys_signame[n]);
+ if ((n == NSIG/2) || n == (NSIG - 1))
+ out1str(psh, "\n");
+ else
+ out1c(psh, ' ');
+ }
+}
+
+/*
+ * The trap builtin.
+ */
+
+int
+trapcmd(shinstance *psh, int argc, char **argv)
+{
+ char *action;
+ char **ap;
+ int signo;
+
+ if (argc <= 1) {
+ for (signo = 0 ; signo <= NSIG ; signo++)
+ if (psh->trap[signo] != NULL) {
+ out1fmt(psh, "trap -- ");
+ print_quoted(psh, psh->trap[signo]);
+ out1fmt(psh, " %s\n",
+ (signo) ? sys_signame[signo] : "EXIT");
+ }
+ return 0;
+ }
+ ap = argv + 1;
+
+ action = NULL;
+
+ if (strcmp(*ap, "--") == 0)
+ if (*++ap == NULL)
+ return 0;
+
+ if (signame_to_signum(psh, *ap) == -1) {
+ if ((*ap)[0] == '-') {
+ if ((*ap)[1] == '\0')
+ ap++;
+ else if ((*ap)[1] == 'l' && (*ap)[2] == '\0') {
+ printsignals(psh);
+ return 0;
+ }
+ else
+ error(psh, "bad option %s\n", *ap);
+ }
+ else
+ action = *ap++;
+ }
+
+ while (*ap) {
+ if (is_number(*ap))
+ signo = number(psh, *ap);
+ else
+ signo = signame_to_signum(psh, *ap);
+
+ if (signo < 0 || signo > NSIG)
+ error(psh, "%s: bad trap", *ap);
+
+ INTOFF;
+ if (action)
+ action = savestr(psh, action);
+
+ if (psh->trap[signo])
+ ckfree(psh, psh->trap[signo]);
+
+ psh->trap[signo] = action;
+
+ if (signo != 0)
+ setsignal(psh, signo);
+ INTON;
+ ap++;
+ }
+ return 0;
+}
+
+
+
+/*
+ * Clear traps on a fork or vfork.
+ * Takes one arg vfork, to tell it to not be destructive of
+ * the parents variables.
+ */
+
+void
+clear_traps(shinstance *psh)
+{
+ char **tp;
+
+ for (tp = psh->trap ; tp <= &psh->trap[NSIG] ; tp++) {
+ if (*tp && **tp) { /* trap not NULL or SIG_IGN */
+ INTOFF;
+ ckfree(psh, *tp);
+ *tp = NULL;
+ if (tp != &psh->trap[0])
+ setsignal(psh, (int)(tp - psh->trap));
+ INTON;
+ }
+ }
+}
+
+
+
+/*
+ * Set the signal handler for the specified signal. The routine figures
+ * out what it should be set to.
+ */
+
+void
+setsignal(shinstance *psh, int signo)
+{
+ int action;
+ shsig_t sigact = SH_SIG_DFL;
+ char *t, tsig;
+
+ if ((t = psh->trap[signo]) == NULL)
+ action = S_DFL;
+ else if (*t != '\0')
+ action = S_CATCH;
+ else
+ action = S_IGN;
+ if (psh->rootshell && action == S_DFL) {
+ switch (signo) {
+ case SIGINT:
+ if (iflag(psh) || psh->minusc || sflag(psh) == 0)
+ action = S_CATCH;
+ break;
+ case SIGQUIT:
+#ifdef DEBUG
+ if (debug(psh))
+ break;
+#endif
+ /* FALLTHROUGH */
+ case SIGTERM:
+ if (iflag(psh))
+ action = S_IGN;
+ break;
+#if JOBS
+ case SIGTSTP:
+ case SIGTTOU:
+ if (mflag(psh))
+ action = S_IGN;
+ break;
+#endif
+ }
+ }
+
+ t = &psh->sigmode[signo - 1];
+ tsig = *t;
+ if (tsig == 0) {
+ /*
+ * current setting unknown
+ */
+ if (!getsigaction(psh, signo, &sigact)) {
+ /*
+ * Pretend it worked; maybe we should give a warning
+ * here, but other shells don't. We don't alter
+ * sigmode, so that we retry every time.
+ */
+ return;
+ }
+ if (sigact == SH_SIG_IGN) {
+ if (mflag(psh) && (signo == SIGTSTP ||
+ signo == SIGTTIN || signo == SIGTTOU)) {
+ tsig = S_IGN; /* don't hard ignore these */
+ } else
+ tsig = S_HARD_IGN;
+ } else {
+ tsig = S_RESET; /* force to be set */
+ }
+ }
+ if (tsig == S_HARD_IGN || tsig == action)
+ return;
+ switch (action) {
+ case S_DFL: sigact = SH_SIG_DFL; break;
+ case S_CATCH: sigact = onsig; break;
+ case S_IGN: sigact = SH_SIG_IGN; break;
+ }
+ *t = action;
+ sh_siginterrupt(psh, signo, 1);
+ sh_signal(psh, signo, sigact);
+}
+
+/*
+ * Return the current setting for sig w/o changing it.
+ */
+static int
+getsigaction(shinstance *psh, int signo, shsig_t *sigact)
+{
+ struct shsigaction sa;
+
+ if (sh_sigaction(psh, signo, NULL, &sa) == -1)
+ return 0;
+ *sigact = (shsig_t)sa.sh_handler;
+ return 1;
+}
+
+/*
+ * Ignore a signal.
+ */
+
+void
+ignoresig(shinstance *psh, int signo)
+{
+ if (psh->sigmode[signo - 1] != S_IGN && psh->sigmode[signo - 1] != S_HARD_IGN) {
+ sh_signal(psh, signo, SH_SIG_IGN);
+ }
+ psh->sigmode[signo - 1] = S_HARD_IGN;
+}
+
+
+#ifdef mkinit
+INCLUDE <signal.h>
+INCLUDE "trap.h"
+
+SHELLPROC {
+ char *sm;
+
+ clear_traps(psh);
+ for (sm = psh->sigmode ; sm < psh->sigmode + NSIG ; sm++) {
+ if (*sm == S_IGN)
+ *sm = S_HARD_IGN;
+ }
+}
+#endif
+
+
+
+/*
+ * Signal handler.
+ */
+
+void
+onsig(shinstance *psh, int signo)
+{
+ sh_signal(psh, signo, onsig);
+ if (signo == SIGINT && psh->trap[SIGINT] == NULL) {
+ onint(psh);
+ return;
+ }
+ psh->gotsig[signo - 1] = 1;
+ psh->pendingsigs++;
+}
+
+
+
+/*
+ * Called to execute a trap. Perhaps we should avoid entering new trap
+ * handlers while we are executing a trap handler.
+ */
+
+void
+dotrap(shinstance *psh)
+{
+ int i;
+ int savestatus;
+
+ for (;;) {
+ for (i = 1 ; ; i++) {
+ if (psh->gotsig[i - 1])
+ break;
+ if (i >= NSIG)
+ goto done;
+ }
+ psh->gotsig[i - 1] = 0;
+ savestatus=psh->exitstatus;
+ evalstring(psh, psh->trap[i], 0);
+ psh->exitstatus=savestatus;
+ }
+done:
+ psh->pendingsigs = 0;
+}
+
+
+
+/*
+ * Controls whether the shell is interactive or not.
+ */
+
+
+void
+setinteractive(shinstance *psh, int on)
+{
+ static int is_interactive;
+
+ if (on == is_interactive)
+ return;
+ setsignal(psh, SIGINT);
+ setsignal(psh, SIGQUIT);
+ setsignal(psh, SIGTERM);
+ is_interactive = on;
+}
+
+
+
+/*
+ * Called to exit the shell.
+ */
+
+int
+exitshell2(shinstance *psh, int status)
+{
+ struct jmploc loc1, loc2;
+ char *p;
+
+ TRACE((psh, "pid %" SHPID_PRI ", exitshell(%d)\n", sh_getpid(psh), status));
+ if (setjmp(loc1.loc)) {
+ goto l1;
+ }
+ if (setjmp(loc2.loc)) {
+ goto l2;
+ }
+ psh->handler = &loc1;
+ if ((p = psh->trap[0]) != NULL && *p != '\0') {
+ psh->trap[0] = NULL;
+ evalstring(psh, p, 0);
+ }
+l1:
+ psh->handler = &loc2; /* probably unnecessary */
+ output_flushall(psh);
+#if JOBS
+ setjobctl(psh, 0);
+#endif
+l2:
+ return status;
+}
+
+SH_NORETURN_1 void
+exitshell(shinstance *psh, int status)
+{
+ sh__exit(psh, exitshell2(psh, status));
+ /* NOTREACHED */
+}
+
diff --git a/src/kash/trap.h b/src/kash/trap.h
new file mode 100644
index 0000000..7a1b220
--- /dev/null
+++ b/src/kash/trap.h
@@ -0,0 +1,51 @@
+/* $NetBSD: trap.h,v 1.17 2003/08/07 09:05:39 agc Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)trap.h 8.3 (Berkeley) 6/5/95
+ */
+
+/*extern int pendingsigs;*/
+
+#ifndef SH_FORKED_MODE
+void subshellinittrap(shinstance *, shinstance *);
+#endif
+int trapcmd(struct shinstance *, int, char **);
+void clear_traps(struct shinstance *);
+void setsignal(struct shinstance *, int);
+void ignoresig(struct shinstance *, int);
+void onsig(struct shinstance *, int);
+void dotrap(struct shinstance *);
+void setinteractive(struct shinstance *, int);
+int exitshell2(struct shinstance *, int);
+SH_NORETURN_1 void exitshell(struct shinstance *, int) SH_NORETURN_2;
+
diff --git a/src/kash/tstDump.c b/src/kash/tstDump.c
new file mode 100644
index 0000000..2ef959e
--- /dev/null
+++ b/src/kash/tstDump.c
@@ -0,0 +1,75 @@
+/* $Id: tstDump.c 2413 2010-09-11 17:43:04Z bird $ */
+/** @file
+ * tstDump - dump inherited file handle information on Windows.
+ */
+
+/*
+ * Copyright (c) 2010 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+/*******************************************************************************
+* Header Files *
+*******************************************************************************/
+#include <Windows.h>
+#include <stdio.h>
+
+
+int main()
+{
+ STARTUPINFO Info;
+ GetStartupInfo(&Info);
+
+ printf("tst: hStdInput =%p / %p\n", Info.hStdInput, GetStdHandle(STD_INPUT_HANDLE));
+ printf("tst: hStdOutput=%p / %p\n", Info.hStdOutput, GetStdHandle(STD_OUTPUT_HANDLE));
+ printf("tst: hStdError =%p / %p\n", Info.hStdError, GetStdHandle(STD_ERROR_HANDLE));
+ printf("tst: cbReserved2=%d (%#x) lpReserved2=%p\n",
+ Info.cbReserved2, Info.cbReserved2, Info.lpReserved2);
+
+ if (Info.cbReserved2 > sizeof(int) && Info.lpReserved2)
+ {
+ int count = *(int *)Info.lpReserved2;
+ unsigned char *paf = (unsigned char *)Info.lpReserved2 + sizeof(int);
+ HANDLE *pah = (HANDLE *)(paf + count);
+ int i;
+
+ printf("tst: count=%d\n", count);
+ for (i = 0; i < count; i++)
+ {
+ if (paf[i] == 0 && pah[i] == INVALID_HANDLE_VALUE)
+ continue;
+
+ printf("tst: #%02d: native=%#p flags=%#x", i, pah[i], paf[i]);
+ if (paf[i] & 0x01) printf(" FOPEN");
+ if (paf[i] & 0x02) printf(" FEOFLAG");
+ if (paf[i] & 0x02) printf(" FEOFLAG");
+ if (paf[i] & 0x04) printf(" FCRLF");
+ if (paf[i] & 0x08) printf(" FPIPE");
+ if (paf[i] & 0x10) printf(" FNOINHERIT");
+ if (paf[i] & 0x20) printf(" FAPPEND");
+ if (paf[i] & 0x40) printf(" FDEV");
+ if (paf[i] & 0x80) printf(" FTEXT");
+ printf("\n");
+ }
+ }
+
+ return 0;
+}
+
diff --git a/src/kash/var.c b/src/kash/var.c
new file mode 100644
index 0000000..22acb28
--- /dev/null
+++ b/src/kash/var.c
@@ -0,0 +1,1020 @@
+/* $NetBSD: var.c,v 1.36 2004/10/06 10:23:43 enami Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: var.c,v 1.36 2004/10/06 10:23:43 enami Exp $");
+#endif /* not lint */
+#endif
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef PC_OS2_LIBPATHS
+#define INCL_BASE
+#include <os2.h>
+
+#ifndef LIBPATHSTRICT
+#define LIBPATHSTRICT 3
+#endif
+
+extern APIRET
+#ifdef APIENTRY
+ APIENTRY
+#endif
+ DosQueryHeaderInfo(HMODULE hmod, ULONG ulIndex, PVOID pvBuffer, ULONG cbBuffer, ULONG ulSubFunction);
+#define QHINF_EXEINFO 1 /* NE exeinfo. */
+#define QHINF_READRSRCTBL 2 /* Reads from the resource table. */
+#define QHINF_READFILE 3 /* Reads from the executable file. */
+#define QHINF_LIBPATHLENGTH 4 /* Gets the libpath length. */
+#define QHINF_LIBPATH 5 /* Gets the entire libpath. */
+#define QHINF_FIXENTRY 6 /* NE only */
+#define QHINF_STE 7 /* NE only */
+#define QHINF_MAPSEL 8 /* NE only */
+
+#endif
+
+
+
+/*
+ * Shell variables.
+ */
+
+#include "shell.h"
+#include "output.h"
+#include "expand.h"
+#include "nodes.h" /* for other headers */
+#include "eval.h" /* defines cmdenviron */
+#include "exec.h"
+#include "syntax.h"
+#include "options.h"
+#include "mail.h"
+#include "var.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "parser.h"
+#include "show.h"
+#ifndef SMALL
+# include "myhistedit.h"
+#endif
+#include "shinstance.h"
+
+//#ifdef SMALL
+//#define VTABSIZE 39
+//#else
+//#define VTABSIZE 517
+//#endif
+
+
+struct varinit {
+ unsigned var_off;
+ int flags;
+ const char *text;
+ void (*func)(shinstance *, const char *);
+};
+
+
+//#if ATTY
+//struct var vatty;
+//#endif
+//#ifndef SMALL
+//struct var vhistsize;
+//struct var vterm;
+//#endif
+//struct var vifs;
+//struct var vmail;
+//struct var vmpath;
+//struct var vpath;
+//#ifdef _MSC_VER
+//struct var vpath2;
+//#endif
+//struct var vps1;
+//struct var vps2;
+//struct var vps4;
+//struct var vvers; - unused
+//struct var voptind;
+
+#ifdef PC_OS2_LIBPATHS
+//static struct var libpath_vars[4];
+static const char * const libpath_envs[4] = {"LIBPATH=", "BEGINLIBPATH=", "ENDLIBPATH=", "LIBPATHSTRICT="};
+#endif
+
+const struct varinit varinit[] = {
+#if ATTY
+ { offsetof(shinstance, vatty), VSTRFIXED|VSTRFIXED2|VTEXTFIXED|VUNSET, "ATTY=",
+ NULL },
+#endif
+#ifndef SMALL
+ { offsetof(shinstance, vhistsize), VSTRFIXED|VSTRFIXED2|VTEXTFIXED|VUNSET, "HISTSIZE=",
+ sethistsize },
+#endif
+ { offsetof(shinstance, vifs), VSTRFIXED|VSTRFIXED2|VTEXTFIXED, "IFS= \t\n",
+ NULL },
+ { offsetof(shinstance, vmail), VSTRFIXED|VSTRFIXED2|VTEXTFIXED|VUNSET, "MAIL=",
+ NULL },
+ { offsetof(shinstance, vmpath), VSTRFIXED|VSTRFIXED2|VTEXTFIXED|VUNSET, "MAILPATH=",
+ NULL },
+ { offsetof(shinstance, vpath), VSTRFIXED|VSTRFIXED2|VTEXTFIXED, "PATH=" _PATH_DEFPATH,
+ changepath },
+ /*
+ * vps1 depends on uid
+ */
+ { offsetof(shinstance, vps2), VSTRFIXED|VSTRFIXED2|VTEXTFIXED, "PS2=> ",
+ NULL },
+ { offsetof(shinstance, vps4), VSTRFIXED|VSTRFIXED2|VTEXTFIXED, "PS4=+ ",
+ NULL },
+#ifndef SMALL
+ { offsetof(shinstance, vterm), VSTRFIXED|VSTRFIXED2|VTEXTFIXED|VUNSET, "TERM=",
+ setterm },
+#endif
+ { offsetof(shinstance, voptind), VSTRFIXED|VSTRFIXED2|VTEXTFIXED|VNOFUNC, "OPTIND=1",
+ getoptsreset },
+ { 0, 0, NULL,
+ NULL }
+};
+
+//struct var *vartab[VTABSIZE];
+
+STATIC int strequal(const char *, const char *);
+STATIC struct var *find_var(shinstance *, const char *, struct var ***, int *);
+
+/*
+ * Initialize the varable symbol tables and import the environment
+ */
+
+#ifdef mkinit
+INCLUDE "var.h"
+
+INIT {
+ char **envp;
+
+ initvar(psh);
+ for (envp = sh_environ(psh) ; *envp ; envp++) {
+ if (strchr(*envp, '=')) {
+ setvareq(psh, *envp, VEXPORT|VTEXTFIXED);
+ }
+ }
+}
+#endif
+
+
+/*
+ * This routine initializes the builtin variables. It is called when the
+ * shell is initialized and again when a shell procedure is spawned.
+ */
+
+void
+initvar(shinstance *psh)
+{
+ const struct varinit *ip;
+ struct var *vp;
+ struct var **vpp;
+#ifdef PC_OS2_LIBPATHS
+ char *psz = ckmalloc(psh, 2048);
+ int rc;
+ int i;
+
+ for (i = 0; i < 4; i++)
+ {
+ psh->libpath_vars[i].flags = VSTRFIXED | VSTRFIXED2 | VOS2LIBPATH;
+ psh->libpath_vars[i].func = NULL;
+
+ if (i > 0)
+ {
+ psz[0] = psz[1] = psz[2] = psz[3] = '\0';
+ rc = DosQueryExtLIBPATH(psz, i);
+ }
+ else
+ {
+ rc = DosQueryHeaderInfo(NULLHANDLE, 0, psz, 2048, QHINF_LIBPATH);
+ psh->libpath_vars[i].flags |= VREADONLY;
+ }
+ if (!rc && *psz)
+ {
+ int cch1 = strlen(libpath_envs[i]);
+ int cch2 = strlen(psz) + 1;
+ psh->libpath_vars[i].text = ckmalloc(psh, cch1 + cch2);
+ memcpy(psh->libpath_vars[i].text, libpath_envs[i], cch1);
+ memcpy(psh->libpath_vars[i].text + cch1, psz, cch2);
+ }
+ else
+ {
+ psh->libpath_vars[i].flags |= VUNSET | VTEXTFIXED;
+ psh->libpath_vars[i].text = (char *)libpath_envs[i];
+ }
+ if (find_var(psh, psh->libpath_vars[i].text, &vpp, &psh->libpath_vars[i].name_len) != NULL)
+ continue;
+ psh->libpath_vars[i].next = *vpp;
+ *vpp = &psh->libpath_vars[i];
+ }
+ ckfree(psh, psz);
+#endif
+
+ for (ip = varinit; ip->text; ip++) {
+ vp = (struct var *)((char *)psh + ip->var_off);
+ if (find_var(psh, ip->text, &vpp, &vp->name_len) != NULL)
+ continue;
+ vp->next = *vpp;
+ *vpp = vp;
+ vp->text = sh_strdup(psh, ip->text);
+ vp->flags = ip->flags;
+ vp->func = ip->func;
+ }
+ /*
+ * PS1 depends on uid
+ */
+ if (find_var(psh, "PS1", &vpp, &psh->vps1.name_len) == NULL) {
+ psh->vps1.next = *vpp;
+ *vpp = &psh->vps1;
+#ifdef KBUILD_VERSION_MAJOR
+ psh->vps1.text = sh_strdup(psh, sh_geteuid(psh) ? "PS1=kash$ " : "PS1=kash# ");
+#else
+ psh->vps1.text = sh_strdup(psh, sh_geteuid(psh) ? "PS1=$ " : "PS1=# ");
+#endif
+ psh->vps1.flags = VSTRFIXED|VSTRFIXED2|VTEXTFIXED; /** @todo text isn't fixed here... */
+ }
+}
+
+
+#ifndef SH_FORKED_MODE
+/*
+ * This routine is called to copy variable state from parent to child shell.
+ */
+void
+subshellinitvar(shinstance *psh, shinstance *inherit)
+{
+ unsigned i;
+ for (i = 0; i < K_ELEMENTS(inherit->vartab); i++) {
+ struct var const *vsrc = inherit->vartab[i];
+ if (vsrc) {
+ struct var **ppdst = &psh->vartab[i];
+ do
+ {
+ struct var *dst;
+ if (!(vsrc->flags & VSTRFIXED2)) {
+ dst = (struct var *)ckmalloc(psh, sizeof(*dst));
+ *dst = *vsrc;
+ dst->flags &= ~VSTRFIXED;
+ } else {
+ /* VSTRFIXED2 is used when the structure is a fixed allocation in
+ the shinstance structure, so scan those to find which it is: */
+ size_t left = ((struct var *)&inherit->vartab[0] - &inherit->vatty);
+ struct var const *fixedsrc = &inherit->vatty;
+ dst = &psh->vatty;
+ while (left-- > 0)
+ if (vsrc != fixedsrc) {
+ fixedsrc++;
+ dst++;
+ } else
+ break;
+ kHlpAssert(left < 256 /*whatever, just no rollover*/);
+ *dst = *vsrc;
+ }
+
+ if (!(vsrc->flags & VTEXTFIXED)) {
+ dst->text = savestr(psh, vsrc->text);
+ dst->flags &= ~VSTACK;
+ }
+
+ *ppdst = dst;
+ ppdst = &dst->next;
+
+ vsrc = vsrc->next;
+ } while (vsrc);
+ *ppdst = NULL;
+ }
+ }
+
+ /** @todo We don't always need to copy local variables. */
+ if (inherit->localvars) {
+ struct localvar const *vsrc = inherit->localvars;
+ struct localvar **ppdst = &psh->localvars;
+ do
+ {
+ struct localvar *dst = ckmalloc(psh, sizeof(*dst));
+
+ dst->flags = vsrc->flags & ~(VSTACK | VTEXTFIXED | (vsrc->flags & VSTRFIXED2 ? 0 : VSTRFIXED));
+ if (vsrc->text)
+ dst->text = savestr(psh, vsrc->text);
+ else
+ {
+ dst->text = NULL;
+ dst->flags |= vsrc->flags & VTEXTFIXED;
+ }
+ dst->vp = find_var(psh, vsrc->vp->text, NULL, NULL);
+ kHlpAssert(dst->vp);
+
+ *ppdst = dst;
+ ppdst = &dst->next;
+
+ vsrc = vsrc->next;
+ } while (vsrc);
+ *ppdst = NULL;
+ }
+}
+#endif /* !SH_FORKED_MODE */
+
+/*
+ * Safe version of setvar, returns 1 on success 0 on failure.
+ */
+
+int
+setvarsafe(shinstance *psh, const char *name, const char *val, int flags)
+{
+ struct jmploc jmploc;
+ struct jmploc *volatile savehandler = psh->handler;
+ int err = 0;
+#ifdef __GNUC__
+ (void) &err;
+#endif
+
+ if (setjmp(jmploc.loc))
+ err = 1;
+ else {
+ psh->handler = &jmploc;
+ setvar(psh, name, val, flags);
+ }
+ psh->handler = savehandler;
+ return err;
+}
+
+/*
+ * Set the value of a variable. The flags argument is ored with the
+ * flags of the variable. If val is NULL, the variable is unset.
+ */
+
+void
+setvar(shinstance *psh, const char *name, const char *val, int flags)
+{
+ const char *p;
+ const char *q;
+ char *d;
+ size_t len;
+ int namelen;
+ char *nameeq;
+ int isbad;
+
+ isbad = 0;
+ p = name;
+ if (! is_name(*p))
+ isbad = 1;
+ p++;
+ for (;;) {
+ if (! is_in_name(*p)) {
+ if (*p == '\0' || *p == '=')
+ break;
+ isbad = 1;
+ }
+ p++;
+ }
+ namelen = (int)(p - name);
+ if (isbad)
+ error(psh, "%.*s: bad variable name", namelen, name);
+ len = namelen + 2; /* 2 is space for '=' and '\0' */
+ if (val == NULL) {
+ flags |= VUNSET;
+ } else {
+ len += strlen(val);
+ }
+ d = nameeq = ckmalloc(psh, len);
+ q = name;
+ while (--namelen >= 0)
+ *d++ = *q++;
+ *d++ = '=';
+ *d = '\0';
+ if (val)
+ scopy(val, d);
+ setvareq(psh, nameeq, flags);
+}
+
+
+
+/*
+ * Same as setvar except that the variable and value are passed in
+ * the first argument as name=value. Since the first argument will
+ * be actually stored in the table, it should not be a string that
+ * will go away.
+ */
+
+void
+setvareq(shinstance *psh, char *s, int flags)
+{
+ struct var *vp, **vpp;
+ int nlen;
+
+#if defined(_MSC_VER) || defined(_WIN32)
+ /* On Windows PATH is often spelled 'Path', correct this here. */
+ if ( s[0] == 'P'
+ && s[1] == 'a'
+ && s[2] == 't'
+ && s[3] == 'h'
+ && (s[4] == '\0' || s[4] == '=') ) {
+ s[1] = 'A';
+ s[2] = 'T';
+ s[3] = 'H';
+ }
+#endif
+
+ if (aflag(psh))
+ flags |= VEXPORT;
+ vp = find_var(psh, s, &vpp, &nlen);
+ if (vp != NULL) {
+ if (vp->flags & VREADONLY)
+ error(psh, "%.*s: is read only", vp->name_len, s);
+ if (flags & VNOSET)
+ return;
+ INTOFF;
+
+ if (vp->func && (flags & VNOFUNC) == 0)
+ (*vp->func)(psh, s + vp->name_len + 1);
+
+ if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
+ ckfree(psh, vp->text);
+
+ vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET);
+ vp->flags |= flags & ~VNOFUNC;
+ vp->text = s;
+#ifdef PC_OS2_LIBPATHS
+ if ((vp->flags & VOS2LIBPATH) && (vp->flags & VEXPORT))
+ vp->flags &= ~VEXPORT;
+#endif
+
+ /*
+ * We could roll this to a function, to handle it as
+ * a regular variable function callback, but why bother?
+ */
+ if (vp == &psh->vmpath || (vp == &psh->vmail && ! mpathset(psh)))
+ chkmail(psh, 1);
+ INTON;
+ return;
+ }
+ /* not found */
+ if (flags & VNOSET)
+ return;
+
+ vp = ckmalloc(psh, sizeof (*vp));
+ vp->flags = flags & ~VNOFUNC;
+ vp->text = s;
+ vp->name_len = nlen;
+ vp->next = *vpp;
+ vp->func = NULL;
+ *vpp = vp;
+}
+
+
+
+/*
+ * Process a linked list of variable assignments.
+ */
+
+void
+listsetvar(shinstance *psh, struct strlist *list, int flags)
+{
+ struct strlist *lp;
+
+ INTOFF;
+ for (lp = list ; lp ; lp = lp->next) {
+ setvareq(psh, savestr(psh, lp->text), flags);
+ }
+ INTON;
+}
+
+void
+listmklocal(shinstance *psh, struct strlist *list, int flags)
+{
+ struct strlist *lp;
+
+ for (lp = list ; lp ; lp = lp->next)
+ mklocal(psh, lp->text, flags);
+}
+
+
+/*
+ * Find the value of a variable. Returns NULL if not set.
+ */
+
+char *
+lookupvar(shinstance *psh, const char *name)
+{
+ struct var *v;
+
+ v = find_var(psh, name, NULL, NULL);
+ if (v == NULL || v->flags & VUNSET)
+ return NULL;
+ return v->text + v->name_len + 1;
+}
+
+
+
+/*
+ * Search the environment of a builtin command. If the second argument
+ * is nonzero, return the value of a variable even if it hasn't been
+ * exported.
+ */
+
+char *
+bltinlookup(shinstance *psh, const char *name, int doall)
+{
+ struct strlist *sp;
+ struct var *v;
+
+ for (sp = psh->cmdenviron ; sp ; sp = sp->next) {
+ if (strequal(sp->text, name))
+ return strchr(sp->text, '=') + 1;
+ }
+
+ v = find_var(psh, name, NULL, NULL);
+
+ if (v == NULL || v->flags & VUNSET || (!doall && !(v->flags & VEXPORT)))
+ return NULL;
+ return v->text + v->name_len + 1;
+}
+
+
+
+/*
+ * Generate a list of exported variables. This routine is used to construct
+ * the third argument to execve when executing a program.
+ */
+
+char **
+environment(shinstance *psh)
+{
+ int nenv;
+ struct var **vpp;
+ struct var *vp;
+ char **env;
+ char **ep;
+
+ nenv = 0;
+ for (vpp = psh->vartab ; vpp < psh->vartab + VTABSIZE ; vpp++) {
+ for (vp = *vpp ; vp ; vp = vp->next)
+ if (vp->flags & VEXPORT)
+ nenv++;
+ }
+ ep = env = stalloc(psh, (nenv + 1) * sizeof *env);
+ for (vpp = psh->vartab ; vpp < psh->vartab + VTABSIZE ; vpp++) {
+ for (vp = *vpp ; vp ; vp = vp->next)
+ if (vp->flags & VEXPORT)
+ *ep++ = vp->text;
+ }
+ *ep = NULL;
+
+#ifdef PC_OS2_LIBPATHS
+ /*
+ * Set the libpaths now as this is exec() time.
+ */
+ for (nenv = 0; nenv < 3; nenv++)
+ DosSetExtLIBPATH(strchr(psh->libpath_vars[nenv].text, '=') + 1, nenv);
+#endif
+
+ return env;
+}
+
+
+/*
+ * Called when a shell procedure is invoked to clear out nonexported
+ * variables. It is also necessary to reallocate variables of with
+ * VSTACK set since these are currently allocated on the stack.
+ */
+
+#ifdef mkinit
+void shprocvar(shinstance *psh);
+
+SHELLPROC {
+ shprocvar(psh);
+}
+#endif
+
+void
+shprocvar(shinstance *psh)
+{
+ struct var **vpp;
+ struct var *vp, **prev;
+
+ for (vpp = psh->vartab ; vpp < psh->vartab + VTABSIZE ; vpp++) {
+ for (prev = vpp ; (vp = *prev) != NULL ; ) {
+ if ((vp->flags & VEXPORT) == 0) {
+ *prev = vp->next;
+ if ((vp->flags & VTEXTFIXED) == 0)
+ ckfree(psh, vp->text);
+ if ((vp->flags & (VSTRFIXED | VSTRFIXED2)) == 0)
+ ckfree(psh, vp);
+ } else {
+ if (vp->flags & VSTACK) {
+ vp->text = savestr(psh, vp->text);
+ vp->flags &=~ VSTACK;
+ }
+ prev = &vp->next;
+ }
+ }
+ }
+ initvar(psh);
+}
+
+
+
+/*
+ * Command to list all variables which are set. Currently this command
+ * is invoked from the set command when the set command is called without
+ * any variables.
+ */
+
+void
+print_quoted(shinstance *psh, const char *p)
+{
+ const char *q;
+
+ if (strcspn(p, "|&;<>()$`\\\"' \t\n*?[]#~=%") == strlen(p)) {
+ out1fmt(psh, "%s", p);
+ return;
+ }
+ while (*p) {
+ if (*p == '\'') {
+ out1fmt(psh, "\\'");
+ p++;
+ continue;
+ }
+ q = strchr(p, '\'');
+ if (!q) {
+ out1fmt(psh, "'%s'", p );
+ return;
+ }
+ out1fmt(psh, "'%.*s'", (int)(q - p), p );
+ p = q;
+ }
+}
+
+static int
+sort_var(const void *v_v1, const void *v_v2)
+{
+ const struct var * const *v1 = v_v1;
+ const struct var * const *v2 = v_v2;
+
+ /* XXX Will anyone notice we include the '=' of the shorter name? */
+ return strcoll((*v1)->text, (*v2)->text);
+}
+
+/*
+ * POSIX requires that 'set' (but not export or readonly) output the
+ * variables in lexicographic order - by the locale's collating order (sigh).
+ * Maybe we could keep them in an ordered balanced binary tree
+ * instead of hashed lists.
+ * For now just roll 'em through qsort for printing...
+ */
+
+int
+showvars(shinstance *psh, const char *name, int flag, int show_value)
+{
+ struct var **vpp;
+ struct var *vp;
+ const char *p;
+
+ static struct var **list; /* static in case we are interrupted */
+ static int list_len;
+ int count = 0;
+
+ if (!list) {
+ list_len = 32;
+ list = ckmalloc(psh, list_len * sizeof(*list));
+ }
+
+ for (vpp = psh->vartab ; vpp < psh->vartab + VTABSIZE ; vpp++) {
+ for (vp = *vpp ; vp ; vp = vp->next) {
+ if (flag && !(vp->flags & flag))
+ continue;
+ if (vp->flags & VUNSET && !(show_value & 2))
+ continue;
+ if (count >= list_len) {
+ list = ckrealloc(psh, list,
+ (list_len << 1) * sizeof(*list));
+ list_len <<= 1;
+ }
+ list[count++] = vp;
+ }
+ }
+
+ qsort(list, count, sizeof(*list), sort_var);
+
+ for (vpp = list; count--; vpp++) {
+ vp = *vpp;
+ if (name)
+ out1fmt(psh, "%s ", name);
+ for (p = vp->text ; *p != '=' ; p++)
+ out1c(psh, *p);
+ if (!(vp->flags & VUNSET) && show_value) {
+ out1fmt(psh, "=");
+ print_quoted(psh, ++p);
+ }
+ out1c(psh, '\n');
+ }
+ return 0;
+}
+
+
+
+/*
+ * The export and readonly commands.
+ */
+
+int
+exportcmd(shinstance *psh, int argc, char **argv)
+{
+ struct var *vp;
+ char *name;
+ const char *p;
+ int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT;
+ int pflag;
+
+ pflag = nextopt(psh, "p") == 'p' ? 3 : 0;
+ if (argc <= 1 || pflag) {
+ showvars(psh, pflag ? argv[0] : 0, flag, pflag );
+ return 0;
+ }
+
+ while ((name = *psh->argptr++) != NULL) {
+ if ((p = strchr(name, '=')) != NULL) {
+ p++;
+ } else {
+ vp = find_var(psh, name, NULL, NULL);
+ if (vp != NULL) {
+ vp->flags |= flag;
+ continue;
+ }
+ }
+ setvar(psh, name, p, flag);
+ }
+ return 0;
+}
+
+
+/*
+ * The "local" command.
+ */
+
+int
+localcmd(shinstance *psh, int argc, char **argv)
+{
+ char *name;
+
+ if (! in_function(psh))
+ error(psh, "Not in a function");
+ while ((name = *psh->argptr++) != NULL) {
+ mklocal(psh, name, 0);
+ }
+ return 0;
+}
+
+
+/*
+ * Make a variable a local variable. When a variable is made local, it's
+ * value and flags are saved in a localvar structure. The saved values
+ * will be restored when the shell function returns. We handle the name
+ * "-" as a special case.
+ */
+
+void
+mklocal(shinstance *psh, const char *name, int flags)
+{
+ struct localvar *lvp;
+ struct var **vpp;
+ struct var *vp;
+
+ INTOFF;
+ lvp = ckmalloc(psh, sizeof (struct localvar));
+ if (name[0] == '-' && name[1] == '\0') {
+ char *p;
+ p = ckmalloc(psh, sizeof_optlist);
+ lvp->text = memcpy(p, psh->optlist, sizeof_optlist);
+ vp = NULL;
+ } else {
+ vp = find_var(psh, name, &vpp, NULL);
+ if (vp == NULL) {
+ if (strchr(name, '='))
+ setvareq(psh, savestr(psh, name), VSTRFIXED|flags);
+ else
+ setvar(psh, name, NULL, VSTRFIXED|flags);
+ vp = *vpp; /* the new variable */
+ lvp->text = NULL;
+ lvp->flags = VUNSET;
+ } else {
+ lvp->text = vp->text;
+ lvp->flags = vp->flags;
+ vp->flags |= VSTRFIXED|VTEXTFIXED;
+ if (name[vp->name_len] == '=')
+ setvareq(psh, savestr(psh, name), flags);
+ }
+ }
+ lvp->vp = vp;
+ lvp->next = psh->localvars;
+ psh->localvars = lvp;
+ INTON;
+}
+
+
+/*
+ * Called after a function returns.
+ */
+
+void
+poplocalvars(shinstance *psh)
+{
+ struct localvar *lvp;
+ struct var *vp;
+
+ while ((lvp = psh->localvars) != NULL) {
+ psh->localvars = lvp->next;
+ vp = lvp->vp;
+ TRACE((psh, "poplocalvar %s", vp ? vp->text : "-"));
+ if (vp == NULL) { /* $- saved */
+ memcpy(psh->optlist, lvp->text, sizeof_optlist);
+ ckfree(psh, lvp->text);
+ } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
+ (void)unsetvar(psh, vp->text, 0);
+ } else {
+ if (vp->func && (vp->flags & VNOFUNC) == 0)
+ (*vp->func)(psh, lvp->text + vp->name_len + 1);
+ if ((vp->flags & VTEXTFIXED) == 0)
+ ckfree(psh, vp->text);
+ vp->flags = lvp->flags;
+ vp->text = lvp->text;
+ }
+ ckfree(psh, lvp);
+ }
+}
+
+
+int
+setvarcmd(shinstance *psh, int argc, char **argv)
+{
+ if (argc <= 2)
+ return unsetcmd(psh, argc, argv);
+ else if (argc == 3)
+ setvar(psh, argv[1], argv[2], 0);
+ else
+ error(psh, "List assignment not implemented");
+ return 0;
+}
+
+
+/*
+ * The unset builtin command. We unset the function before we unset the
+ * variable to allow a function to be unset when there is a readonly variable
+ * with the same name.
+ */
+
+int
+unsetcmd(shinstance *psh, int argc, char **argv)
+{
+ char **ap;
+ int i;
+ int flg_func = 0;
+ int flg_var = 0;
+ int ret = 0;
+
+ while ((i = nextopt(psh, "evf")) != '\0') {
+ if (i == 'f')
+ flg_func = 1;
+ else
+ flg_var = i;
+ }
+ if (flg_func == 0 && flg_var == 0)
+ flg_var = 1;
+
+ for (ap = psh->argptr; *ap ; ap++) {
+ if (flg_func)
+ ret |= unsetfunc(psh, *ap);
+ if (flg_var)
+ ret |= unsetvar(psh, *ap, flg_var == 'e');
+ }
+ return ret;
+}
+
+
+/*
+ * Unset the specified variable.
+ */
+
+int
+unsetvar(shinstance *psh, const char *s, int unexport)
+{
+ struct var **vpp;
+ struct var *vp;
+
+ vp = find_var(psh, s, &vpp, NULL);
+ if (vp == NULL)
+ return 1;
+
+ if (vp->flags & VREADONLY)
+ return (1);
+
+ INTOFF;
+ if (unexport) {
+ vp->flags &= ~VEXPORT;
+ } else {
+ if (vp->text[vp->name_len + 1] != '\0')
+ setvar(psh, s, nullstr, 0);
+ vp->flags &= ~VEXPORT;
+ vp->flags |= VUNSET;
+ if ((vp->flags & (VSTRFIXED | VSTRFIXED2)) == 0) {
+ if ((vp->flags & VTEXTFIXED) == 0)
+ ckfree(psh, vp->text);
+ *vpp = vp->next;
+ ckfree(psh, vp);
+ }
+ }
+ INTON;
+ return 0;
+}
+
+
+/*
+ * Returns true if the two strings specify the same varable. The first
+ * variable name is terminated by '='; the second may be terminated by
+ * either '=' or '\0'.
+ */
+
+STATIC int
+strequal(const char *p, const char *q)
+{
+ while (*p == *q++) {
+ if (*p++ == '=')
+ return 1;
+ }
+ if (*p == '=' && *(q - 1) == '\0')
+ return 1;
+ return 0;
+}
+
+/*
+ * Search for a variable.
+ * 'name' may be terminated by '=' or a NUL.
+ * vppp is set to the pointer to vp, or the list head if vp isn't found
+ * lenp is set to the number of charactets in 'name'
+ */
+
+STATIC struct var *
+find_var(shinstance *psh, const char *name, struct var ***vppp, int *lenp)
+{
+ unsigned int hashval;
+ int len;
+ struct var *vp, **vpp;
+ const char *p = name;
+
+ hashval = 0;
+ while (*p && *p != '=')
+ hashval = 2 * hashval + (unsigned char)*p++;
+ len = (int)(p - name);
+
+ if (lenp)
+ *lenp = len;
+ vpp = &psh->vartab[hashval % VTABSIZE];
+ if (vppp)
+ *vppp = vpp;
+
+ for (vp = *vpp ; vp ; vpp = &vp->next, vp = *vpp) {
+ if (vp->name_len != len)
+ continue;
+ if (memcmp(vp->text, name, len) != 0)
+ continue;
+ if (vppp)
+ *vppp = vpp;
+ return vp;
+ }
+ return NULL;
+}
diff --git a/src/kash/var.h b/src/kash/var.h
new file mode 100644
index 0000000..b524398
--- /dev/null
+++ b/src/kash/var.h
@@ -0,0 +1,153 @@
+/* $NetBSD: var.h,v 1.23 2004/10/02 12:16:53 dsl Exp $ */
+
+/*-
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * 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.
+ *
+ * @(#)var.h 8.2 (Berkeley) 5/4/95
+ */
+
+#ifndef ___var_h___
+#define ___var_h___
+
+/*
+ * Shell variables.
+ */
+
+/* flags */
+#define VEXPORT 0x01 /* variable is exported */
+#define VREADONLY 0x02 /* variable cannot be modified */
+#define VSTRFIXED 0x04 /* variable struct is statically allocated */
+#define VTEXTFIXED 0x08 /* text is statically allocated */
+#define VSTACK 0x10 /* text is allocated on the stack */
+#define VUNSET 0x20 /* the variable is not set */
+#define VNOFUNC 0x40 /* don't call the callback function */
+#define VNOSET 0x80 /* do not set variable - just readonly test */
+#define VSTRFIXED2 0x4000 /* variable struct is in the shinstance, cannot be freed. (VSTRFIXED is mixed up in local vars) */
+#ifdef PC_OS2_LIBPATHS
+#define VOS2LIBPATH 0x8000 /* OS/2 LIBPATH related variable. */
+#endif
+
+
+struct var {
+ struct var *next; /* next entry in hash list */
+ int flags; /* flags are defined above */
+ char *text; /* name=value */
+ int name_len; /* length of name */
+ void (*func)(struct shinstance *, const char *);
+ /* function to be called when */
+ /* the variable gets set/unset */
+};
+
+
+struct localvar {
+ struct localvar *next; /* next local variable in list */
+ struct var *vp; /* the variable that was made local */
+ int flags; /* saved flags */
+ char *text; /* saved text */
+};
+
+/*
+struct localvar *localvars;
+
+#if ATTY
+extern struct var vatty;
+#endif
+extern struct var vifs;
+extern struct var vmail;
+extern struct var vmpath;
+extern struct var vpath;
+#ifdef _MSC_VER
+extern struct var vpath2;
+#endif
+extern struct var vps1;
+extern struct var vps2;
+extern struct var vps4;
+#ifndef SMALL
+extern struct var vterm;
+extern struct var vtermcap;
+extern struct var vhistsize;
+#endif
+*/
+
+/*
+ * The following macros access the values of the above variables.
+ * They have to skip over the name. They return the null string
+ * for unset variables.
+ */
+
+#define ifsval(psh) ((psh)->vifs.text + 4)
+#define ifsset(psh) (((psh)->vifs.flags & VUNSET) == 0)
+#define mailval(psh) ((psh)->vmail.text + 5)
+#define mpathval(psh) ((psh)->vmpath.text + 9)
+#ifdef _MSC_VER
+# define pathval(psh) ((psh)->vpath.text[5] || !(psh)->vpath2.text ? &(psh)->vpath.text[5] : &(psh)->vpath2.text[5])
+#else
+# define pathval(psh) ((psh)->vpath.text + 5)
+#endif
+#define ps1val(psh) ((psh)->vps1.text + 4)
+#define ps2val(psh) ((psh)->vps2.text + 4)
+#define ps4val(psh) ((psh)->vps4.text + 4)
+#define optindval(psh) ((psh)->voptind.text + 7)
+#ifndef SMALL
+#define histsizeval(psh) ((psh)->vhistsize.text + 9)
+#define termval(psh) ((psh)->vterm.text + 5)
+#endif
+
+#if ATTY
+#define attyset(psh) (((psh)->vatty.flags & VUNSET) == 0)
+#endif
+#define mpathset(psh) (((psh)->vmpath.flags & VUNSET) == 0)
+
+void initvar(struct shinstance *);
+#ifndef SH_FORKED_MODE
+void subshellinitvar(shinstance *, shinstance *);
+#endif
+void setvar(struct shinstance *, const char *, const char *, int);
+void setvareq(struct shinstance *, char *, int);
+struct strlist;
+void listsetvar(struct shinstance *, struct strlist *, int);
+char *lookupvar(struct shinstance *, const char *);
+char *bltinlookup(struct shinstance *, const char *, int);
+char **environment(struct shinstance *);
+void shprocvar(struct shinstance *);
+int showvars(struct shinstance *, const char *, int, int);
+int exportcmd(struct shinstance *, int, char **);
+int localcmd(struct shinstance *, int, char **);
+void mklocal(struct shinstance *, const char *, int);
+void listmklocal(struct shinstance *, struct strlist *, int);
+void poplocalvars(struct shinstance *);
+int setvarcmd(struct shinstance *, int, char **);
+int unsetcmd(struct shinstance *, int, char **);
+int unsetvar(struct shinstance *, const char *, int);
+int setvarsafe(struct shinstance *, const char *, const char *, int);
+void print_quoted(struct shinstance *, const char *);
+
+#endif