From 29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:21:29 +0200 Subject: Adding upstream version 1:0.1.9998svn3589+dfsg. Signed-off-by: Daniel Baumann --- src/kash/Makefile.kmk | 272 ++++ src/kash/TOUR | 357 +++++ src/kash/alias.c | 311 +++++ src/kash/alias.h | 53 + src/kash/arith.y | 209 +++ src/kash/arith_lex.l | 172 +++ src/kash/bld_signames.c | 172 +++ src/kash/bltin/Makefile.kup | 0 src/kash/bltin/echo.1 | 109 ++ src/kash/bltin/echo.c | 120 ++ src/kash/bltin/kill.c | 236 ++++ src/kash/bltin/printf.c | 659 ++++++++++ src/kash/bltin/test.c | 483 +++++++ src/kash/builtins.def | 92 ++ src/kash/cd.c | 445 +++++++ src/kash/cd.h | 43 + src/kash/error.c | 394 ++++++ src/kash/error.h | 133 ++ src/kash/eval.c | 1485 +++++++++++++++++++++ src/kash/eval.h | 64 + src/kash/exec.c | 1352 +++++++++++++++++++ src/kash/exec.h | 96 ++ src/kash/expand.c | 1617 +++++++++++++++++++++++ src/kash/expand.h | 78 ++ src/kash/funcs/cmv | 50 + src/kash/funcs/dirs | 74 ++ src/kash/funcs/kill | 50 + src/kash/funcs/login | 39 + src/kash/funcs/newgrp | 38 + src/kash/funcs/popd | 74 ++ src/kash/funcs/pushd | 74 ++ src/kash/funcs/suspend | 42 + src/kash/generated/arith.c | 748 +++++++++++ src/kash/generated/arith.h | 25 + src/kash/generated/arith_lex.c | 1731 +++++++++++++++++++++++++ src/kash/generated/builtins.c | 63 + src/kash/generated/builtins.h | 57 + src/kash/generated/init.c | 292 +++++ src/kash/generated/nodes.c | 366 ++++++ src/kash/generated/nodes.h | 207 +++ src/kash/generated/token.h | 112 ++ src/kash/histedit.c | 546 ++++++++ src/kash/init.h | 39 + src/kash/input.c | 574 ++++++++ src/kash/input.h | 62 + src/kash/jobs.c | 1521 ++++++++++++++++++++++ src/kash/jobs.h | 114 ++ src/kash/machdep.h | 56 + src/kash/mail.c | 119 ++ src/kash/mail.h | 37 + src/kash/main.c | 494 +++++++ src/kash/main.h | 44 + src/kash/memalloc.c | 725 +++++++++++ src/kash/memalloc.h | 147 +++ src/kash/miscbltin.c | 443 +++++++ src/kash/miscbltin.h | 31 + src/kash/mkbuiltins | 136 ++ src/kash/mkinit.sh | 199 +++ src/kash/mknodes.sh | 232 ++++ src/kash/mktokens | 98 ++ src/kash/myhistedit.h | 49 + src/kash/mystring.c | 132 ++ src/kash/mystring.h | 55 + src/kash/nodes.c.pat | 185 +++ src/kash/nodetypes | 141 ++ src/kash/options.c | 635 +++++++++ src/kash/options.h | 145 +++ src/kash/output.c | 502 +++++++ src/kash/output.h | 92 ++ src/kash/parser.c | 1966 ++++++++++++++++++++++++++++ src/kash/parser.h | 88 ++ src/kash/redir.c | 484 +++++++ src/kash/redir.h | 54 + src/kash/setmode.c | 472 +++++++ src/kash/sh.1 | 1949 ++++++++++++++++++++++++++++ src/kash/shell.h | 102 ++ src/kash/shfile.c | 2656 ++++++++++++++++++++++++++++++++++++++ src/kash/shfile.h | 229 ++++ src/kash/shfork-win.c | 290 +++++ src/kash/shforkA-win.asm | 338 +++++ src/kash/shheap.c | 596 +++++++++ src/kash/shheap.h | 45 + src/kash/shinstance.c | 2389 ++++++++++++++++++++++++++++++++++ src/kash/shinstance.h | 606 +++++++++ src/kash/show.c | 534 ++++++++ src/kash/show.h | 50 + src/kash/shthread.c | 151 +++ src/kash/shthread.h | 89 ++ src/kash/shtypes.h | 150 +++ src/kash/strlcpy.c | 72 ++ src/kash/strsignal.c | 14 + src/kash/syntax.c | 209 +++ src/kash/syntax.h | 91 ++ src/kash/tests/Makefile.kmk | 75 ++ src/kash/tests/common-include.sh | 14 + src/kash/tests/netbsd/exit1 | 7 + src/kash/tests/netbsd/var1 | 8 + src/kash/tests/netbsd/waitjob | 8 + src/kash/tests/pipe-1 | 18 + src/kash/tests/pipe-2 | 30 + src/kash/tests/redirect-1 | 18 + src/kash/tests/redirect-2 | 19 + src/kash/tests/redirect-3 | 20 + src/kash/tests/tick-1 | 10 + src/kash/tests/trap-exit-1 | 7 + src/kash/tests/trap-int-1 | 6 + src/kash/tests/trap-term-1 | 6 + src/kash/trap.c | 471 +++++++ src/kash/trap.h | 51 + src/kash/tstDump.c | 75 ++ src/kash/var.c | 1020 +++++++++++++++ src/kash/var.h | 153 +++ 112 files changed, 36187 insertions(+) create mode 100644 src/kash/Makefile.kmk create mode 100644 src/kash/TOUR create mode 100644 src/kash/alias.c create mode 100644 src/kash/alias.h create mode 100644 src/kash/arith.y create mode 100644 src/kash/arith_lex.l create mode 100644 src/kash/bld_signames.c create mode 100644 src/kash/bltin/Makefile.kup create mode 100644 src/kash/bltin/echo.1 create mode 100644 src/kash/bltin/echo.c create mode 100644 src/kash/bltin/kill.c create mode 100644 src/kash/bltin/printf.c create mode 100644 src/kash/bltin/test.c create mode 100644 src/kash/builtins.def create mode 100644 src/kash/cd.c create mode 100644 src/kash/cd.h create mode 100644 src/kash/error.c create mode 100644 src/kash/error.h create mode 100644 src/kash/eval.c create mode 100644 src/kash/eval.h create mode 100644 src/kash/exec.c create mode 100644 src/kash/exec.h create mode 100644 src/kash/expand.c create mode 100644 src/kash/expand.h create mode 100644 src/kash/funcs/cmv create mode 100644 src/kash/funcs/dirs create mode 100644 src/kash/funcs/kill create mode 100644 src/kash/funcs/login create mode 100644 src/kash/funcs/newgrp create mode 100644 src/kash/funcs/popd create mode 100644 src/kash/funcs/pushd create mode 100644 src/kash/funcs/suspend create mode 100644 src/kash/generated/arith.c create mode 100644 src/kash/generated/arith.h create mode 100644 src/kash/generated/arith_lex.c create mode 100644 src/kash/generated/builtins.c create mode 100644 src/kash/generated/builtins.h create mode 100644 src/kash/generated/init.c create mode 100644 src/kash/generated/nodes.c create mode 100644 src/kash/generated/nodes.h create mode 100644 src/kash/generated/token.h create mode 100644 src/kash/histedit.c create mode 100644 src/kash/init.h create mode 100644 src/kash/input.c create mode 100644 src/kash/input.h create mode 100644 src/kash/jobs.c create mode 100644 src/kash/jobs.h create mode 100644 src/kash/machdep.h create mode 100644 src/kash/mail.c create mode 100644 src/kash/mail.h create mode 100644 src/kash/main.c create mode 100644 src/kash/main.h create mode 100644 src/kash/memalloc.c create mode 100644 src/kash/memalloc.h create mode 100644 src/kash/miscbltin.c create mode 100644 src/kash/miscbltin.h create mode 100755 src/kash/mkbuiltins create mode 100755 src/kash/mkinit.sh create mode 100755 src/kash/mknodes.sh create mode 100755 src/kash/mktokens create mode 100644 src/kash/myhistedit.h create mode 100644 src/kash/mystring.c create mode 100644 src/kash/mystring.h create mode 100644 src/kash/nodes.c.pat create mode 100644 src/kash/nodetypes create mode 100644 src/kash/options.c create mode 100644 src/kash/options.h create mode 100644 src/kash/output.c create mode 100644 src/kash/output.h create mode 100644 src/kash/parser.c create mode 100644 src/kash/parser.h create mode 100644 src/kash/redir.c create mode 100644 src/kash/redir.h create mode 100644 src/kash/setmode.c create mode 100644 src/kash/sh.1 create mode 100644 src/kash/shell.h create mode 100644 src/kash/shfile.c create mode 100644 src/kash/shfile.h create mode 100644 src/kash/shfork-win.c create mode 100644 src/kash/shforkA-win.asm create mode 100644 src/kash/shheap.c create mode 100644 src/kash/shheap.h create mode 100644 src/kash/shinstance.c create mode 100644 src/kash/shinstance.h create mode 100644 src/kash/show.c create mode 100644 src/kash/show.h create mode 100644 src/kash/shthread.c create mode 100644 src/kash/shthread.h create mode 100644 src/kash/shtypes.h create mode 100644 src/kash/strlcpy.c create mode 100644 src/kash/strsignal.c create mode 100644 src/kash/syntax.c create mode 100644 src/kash/syntax.h create mode 100644 src/kash/tests/Makefile.kmk create mode 100755 src/kash/tests/common-include.sh create mode 100755 src/kash/tests/netbsd/exit1 create mode 100755 src/kash/tests/netbsd/var1 create mode 100755 src/kash/tests/netbsd/waitjob create mode 100644 src/kash/tests/pipe-1 create mode 100644 src/kash/tests/pipe-2 create mode 100755 src/kash/tests/redirect-1 create mode 100755 src/kash/tests/redirect-2 create mode 100755 src/kash/tests/redirect-3 create mode 100755 src/kash/tests/tick-1 create mode 100755 src/kash/tests/trap-exit-1 create mode 100755 src/kash/tests/trap-int-1 create mode 100755 src/kash/tests/trap-term-1 create mode 100644 src/kash/trap.c create mode 100644 src/kash/trap.h create mode 100644 src/kash/tstDump.c create mode 100644 src/kash/var.c create mode 100644 src/kash/var.h (limited to 'src/kash') 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 +# +# 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 +# +# + +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 +#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 +#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 +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 +#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 +#include + +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 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 +#include +#include +#include +#include +#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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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[] = ""; + 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 +#include +#include +#include +#include +#include +#include + +#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 ::= +*/ + +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 +#include +#include +#include + +/* + * 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 +#include +#include +#include + +#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 + +/* + * 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 +#else +# include /** @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 +#include +#include +#include +#ifdef HAVE_SYSCTL_H +# ifdef __OpenBSD__ /* joyful crap */ +# include +# undef psh +# endif +# include +#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 ' act as if + 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 +#include +#include +#include +#include + +/* + * 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 +#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 +#include +#include +#include + +/* + * 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 +#include + +#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 +#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 +#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 +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 +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . 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 +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 +#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 +#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 +#include "input.h" +#include "error.h" +#include +#include "options.h" +#include "output.h" +#include "memalloc.h" +#include "redir.h" +#include +#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 +/* + * 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 +#include + +/* + * 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 /* defines BUFSIZ */ +#include +#include +#include + +/* + * 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 +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 +#include +#include +#include + +#include "shell.h" +#if JOBS && !defined(_MSC_VER) +# include +#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 + +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 +#include + +#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 +#include +#include +#include + + +#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 +#include + +#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 +#include +#include +#include + +#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 + +/*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 +#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 +#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 +/* + * 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< + +#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 */ + const char letter; /* set [+/-] and $- */ + const char opt_set; /* mutually exclusive option set */ + char val; /* value of 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 ) /* 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 + +#include /* defines BUFSIZ */ +#include +#include +#include + +#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 + +/* The stupid, stupid, unix specification guys added dprintf to stdio.h! + Wonder what kind of weed they were smoking when doing that... */ +#include +#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 + +#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 = ≈ + 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) "ef; + (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 ""; + } +} + +#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 +#include /* PIPE_BUF */ +#include +#include +#include +#include + +/* + * 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 +#include + +#include +#include +#include +#include "shinstance.h" /* for unistd.h types/defines */ + +#ifdef SETMODE_DEBUG +#include +#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 +#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 +#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 + * + * + * 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 +#include +#include + +#if K_OS == K_OS_WINDOWS +# include +# define WIN32_NO_STATUS +# include +# if !defined(_WIN32_WINNT) +# define _WIN32_WINNT 0x0502 /* Windows Server 2003 */ +# endif +# include //NTSTATUS +#else +# include +# include +# include +#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 + * + * + * 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 +#include +#ifdef _MSC_VER +# define _PATH_DEVNULL "nul" +# define _PATH_DEFPATH "." +#else +# if !defined(__sun__) +# include +# 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 /* for PIPE_BUF */ +#ifndef _MSC_VER +# include +# include +# ifndef O_BINARY +# define O_BINARY 0 +# endif +# ifndef O_TEXT +# define O_TEXT 0 +# endif + +#else +# include +# include + +# 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 +#include +#include "shinstance.h" +#include + +/******************************************************************************* +* 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 +; +; 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 +; +; + +;******************************************************************************* +;* 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 + * + * + * 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 +#include +#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 +# else +# include +# 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 + * + * + * 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 + * + * + * 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 +#include +#ifdef _MSC_VER +# include +#else +# include +# include +#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 +# 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 + * + * + * 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 /* BUFSIZ */ +#include /* NSIG */ +#ifndef _MSC_VER +# include +# include +# include +# include +#endif +#include +#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 +#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 + 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 +# 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 +# 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 +#include +#include + +#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, "", 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("\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); + } + 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 + +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 + * + * + * 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 +#elif K_OS == K_OS_OS2 +# include +# include +#else +# include +#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 + * + * + * 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 + * + * + * 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 +#include +#ifdef __HAIKU__ +# include /* silly */ +#elif !defined(_MSC_VER) +# include +#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 /* 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 +#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 + * 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 +//__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 +#include + +/* + * 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 + +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 +#include + +/* 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 +# +# 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 +# +# + +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 + +#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 +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 + * + * + * 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 +#include + + +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 +#include +#include + +#ifdef PC_OS2_LIBPATHS +#define INCL_BASE +#include + +#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 -- cgit v1.2.3