commit: 4120f98a13f09cc5bcd6f946e624dcfd6c9e9696
parent:
author: Brian Callahan <dodonpachi-github@mailinator.com>
date: Mon, 5 Jan 2015 19:17:21 -0500
Import oksh
56 files changed, 43345 insertions(+)
diff --git a/BUG-REPORTS b/BUG-REPORTS
@@ -0,0 +1,1398 @@
+$OpenBSD: BUG-REPORTS,v 1.19 2013/11/28 10:33:37 sobrado Exp $
+
+List of reported problems (problems reported and fixed before 5.0.4 not
+included). Unresolved problems (may or may not still exist) marked by *,
+problems believed to be fixed marked by x.
+
+* pdksh 5.0.3, MIPS RISC/os 5.0 (bsd universe) (noted by Michael Rendell):
+ for interactive, job controlled shells, the kernel's tty state gets twisted
+ in such a way that all output is lost (eg, if ttyXX is wedged then
+ "echo hi > /dev/ttyXX" from a separate login appears to succeed but produces
+ no output on ttyXX).
+ Work around is to run a program and hit ^C.
+
+* pdksh 5.0.1, NetBSD 0.9a? (reported by Simon J. Gerraty): problem with
+ job control not finding tty
+ [from Mail.1:71]:
+ Also, I have noticed (with 5.0.1 anyway) that if as root I su to a
+ user I get:
+ root:511$ su foobar
+ warning: won't have full job control
+ [1] + Stopped (tty output) stty erase ^?
+ foobar:1$
+
+* pdksh 5.0.8, - (reported by Sean Hogan): attempting file name completion
+ on a word with a single backquote causes a "no closing quote" error and
+ loses the partially entered command (vi mode).
+ [see Mail.2:48]
+ [partly fixed in 5.2.14: backquote ok, but happens for the likes of ${.]
+
+* pdksh 5.0.10, - (reported by Andrew Moore): no overflow checking is done
+ in integer parsing code.
+ [see Mail.3:78]
+
+* pdksh 5.0.6+5.1.2, BSD43/MachTen (reported by Dan Menchaca): ksh freezes up
+ terminal after a while after printing process exit message. 5.1.2 causes
+ system to hang after executing two commands.
+ [see Mail.3:96,5:42]
+
+* pdksh 5.1.3, - (reported by Brad Warkentin & others): if the last command of
+ a pipeline is a shell builtin, it is not executed in the parent shell,
+ so "echo a b | read foo bar" does not set foo and bar in the parent
+ shell (at&t ksh will).
+ [see Mail.7:32,Mail.9:65]
+
+* pdksh 5.1.3, - (reported by Gabor Zahemszky): emacs/vi doesn't have \ as quote
+ character.
+ [see Mail.7:87]
+
+* pdksh 5.1.3, - (reported by Gabor Zahemszky): emacs default bindings doesn't
+ have vt52 arrow keys or vt100 alternate keypad mode bindings.
+ [see Mail.7:87]
+
+* pdksh 5.1.3, SCO 3.2.2 (reported by Gabor Zahemszky): shell hangs
+ waiting for finished process to finish.
+ [see Mail.7:87]
+
+* pdksh 5.2.0, - (reported by Gabor Zahemszky): ^V in vi leaves cursor at
+ start of the line.
+ [see Mail.8:43]
+
+* enhancements that haven't been merged yet
+ - Mail.6:36-39,78,84 recursive function diffs (add hard limit on
+ depeth of recursion)
+
+* pdksh 5.2.3, - (reported by David Gast(? gast@twinsun.com)): history (fc,
+ et al) don't work in shell scripts.
+ [see Mail.10:49]
+
+* pdksh 5.2.4, - (reported by Gabor Zahemszky): emacs: ^P steps through
+ multiline commands - should go to start of command.
+ [see Mail.XXX:XXX]
+
+* pdksh 5.2.7, - (reported by Adrian Marsh): typeset -L20u xxx is ok is ksh88
+ but not in pdksh.
+ [see Mail.XXX:XXX]
+
+* pdksh 5.2.7, - (reported by Gabor Zahemszky): TMOUT doesn't effect
+ select and read operations.
+ [see Mail.XXX:XXX]
+
+* pdksh 5.2.10, - (reported by Simon J. Gerraty): in emacs, <ESC><ESC> applied
+ to a word with a macro does not complete the word (only expands the macro).
+ [see Mail.XXX]
+
+* pdksh 5.2.12, - (reported by Han Holl <jeholl@euronet.nl>): pdksh does not
+ parse the whole script before executing, so some syntax errors are not
+ detected (if the shell exits before reading the whole file).
+ [see Mail.XXX]
+
+* pdksh 5.2.12, (reported by Michael Staats): emacs: file completion does
+ not complete as much as possible when file is ~/something (will list
+ possible completions but won't fill in to the first difference).
+ Happens since code doesn't distinguish between globbing and ~ (needs
+ special case to strip & replace a leading ~ during the completion
+ process).
+
+* pdksh 5.2.13, (reported by Martin Dalecki): shell dumps core when set -x
+ is used on some scripts
+ [awaiting more info...]
+
+* pdksh 5.2.13, (reported by Arthor Pool): interactive shells can't be
+ interrupted when processing ${foo#bar} expressions.
+ [same goes for globbing - fixing this means checking for interrupts in
+ the `tight' loops - could slow things down quite a bit]
+
+--------------------- put fixed problems below this line ---------------------
+
+x pdksh 5.0.3, NetBSD 0.9a (reported by Simon J. Gerraty): pipelines
+ occasionally hang.
+ [from Mail.1:71]:
+ Yes, I just built 5.0.3 on zen (NetBSD) and the menu stuff worked fine.
+ However I've just done:
+
+ sjg:910$ diff -cb /etc/profile profile | more
+
+ And it has been sitting there ever since.
+ [... gdb output indicating process groups set up ok - presumed problem is
+ with tty process group]
+ [Fixed in 5.0.4 - do tcsetpgrp() in both parent and child for first process]
+
+x pdksh 5.0.2, ISC unix 3.01 (reported by Sean Hogan): set +o monitor (in
+ interactive shell?) closes tty
+ [from Mail.1:64]:
+ I'm having two problems with the job control code, which I believe might
+ be related. The first one is that "set +o monitor" closes the tty,
+ which causes the shell to exit since its input is gone. According to
+ the code, that would imply that FTALKING has mysteriously been turned
+ off (jobs.c:343). But my understanding of the code is that FTALKING
+ would only be clear for background processes, and set would be done by
+ the shell. Do you have any insights here? It's not a big deal of course;
+ I don't need to turn off monitor anyway.
+ [fixed in 5.0.5 - problem was tty process group was being restored so
+ shell could no longer read from tty]
+
+x pdksh 5.0.4, - (reported by Simon J. Gerraty and Sean Hogan):
+ test "" -a x would fail.
+ [fixed in 5.0.5 - t_wp being unnecessarily decremented in primary()]
+
+x pdksh 5.0.4, -: test -p foo would always fail.
+ [fixed in 5.0.5 - spell S_ISFIFO correctly]
+
+x pdksh 5.0.4, -: test ! ! foo would generate error (unexpected !)
+ [fixed in 5.0.5 - nexpr() always calls nexpr(), changes to posix code]
+
+x pdksh 5.0.4, -: set -i would generate an internal error.
+ [fixed in 5.0.5 - use OF_SET in creating set_opts]
+
+x pdksh 5.0.4, -: let 0>22 would evaluate to true (and 0<22 false)
+ [fixed in 5.0.5 - reversed order of O_LT and O_GT in enum]
+
+x pdksh 5.0.4, - (reported by Sean Hogan): echo does not process escape
+ characters (ie, echo "foo\c" doesn't to the sysV thing)
+ [see Mail.1:98]
+ [fixed in 5.0.5 - echo now behaves like sysv echo]
+
+x pdksh 5.0.4, - (reported by Sean Hogan): tty process groups not restored
+ properly (vi, :sh, exit causes vi to received SIGTTOU).
+ [see Mail.1:98]
+ [fixed in 5.0.5 - restore tty process group in j_exit]
+
+x pdksh 5.0.4, - (reported by Sean Hogan): the exit command does not do the
+ stopped jobs check.
+ [see Mail.1:94,95,98]
+ [fixed in 5.0.5 - added LSHELL, hack c_exitreturn to use it]
+
+x pdksh 5.0.3, ISC unix 3.01 (reported by Sean Hogan): if notify is set,
+ running cat & produces "[1] + Stopped (tty input) cat", but jobs, fg,
+ etc. don't know about the job.
+ [from Mail.1:76]
+ I get [1] + Stopped (tty input) cat. Interestingly, "jobs" reports
+ nothing, and "fg" doesn't see it either. But it's definitely there in
+ the ps output. It only responds to kill -9, nothing else. I guess this
+ is a side track?
+ [see also Mail.1:97,Mail.2:2,3]
+ [fixed in 5.0.6 - don't remove stopped jobs in the notify code of check_job()]
+
+x pdksh 5.0.5, - (reported by Sean Hogan): repeated history commands were being
+ echoed after the command was executed.
+ [see Mail.2:5,6]
+ [fixed in 5.0.6 - call shf_flush() in case SHIST: of yylex()]
+
+x pdksh 5.0.5, -: wait with no arguments would hang forever.
+ [fixed in 5.0.6 - only wait for running jobs in waitfor()]
+
+x pdksh 5.0.2, HP-UX 9.01 (reported by Sean Hogan): scripts occasionally get
+ stopped with SIGTTIN
+ [from Mail.1:68]:
+ I noticed another small problem today, which is that occasionally
+ (on an HP 9000/715, HP-UX 9.01, cc from the ANSI C developer set)
+ a background process which is definitely not reading from its input
+ stops with SIGTTIN. I first noticed this with a nohup'ed process, but
+ it periodically happens without as well. The process is a perl script,
+ if that makes any difference. Have you seen this on your HP(s)?
+ [hasn't been seen in 5.0.3: Mail.1:73,76]
+ [not a shell bug, see Mail.2:14,15]
+
+x pdksh 5.0.6, - (reported by Gordan Larson, Ed Ferguson): shell does not
+ compile when VI isn't defined.
+ [see Mail.2:22,40]
+ [fixed in 5.0.7 - fixed up lex.c]
+
+x pdksh 5.0.6, - (reported by Gordan Larson): ksh.1 font typo.
+ [see Mail.2:23]
+ [fixed in 5.0.7]
+
+x pdksh 5.0.6, FreeBSd 1.1.5 (reported by Thomas Gellekum): CLK_TCK is defined
+ to wrong value on FreeBSD; no depend target in Makefile; update /etc/shells
+ in install target.
+ [see Mail.2:28]
+ [fixed in 5.0.7 - include <limits.h> in sh.h to get the right value; added
+ depend target; print warning if ksh not in /etc/shells]
+
+x pdksh 5.0.6, - (reported by Michael Haardt): shell does not compile if JOBS
+ not defined.
+ [see Mail.2:32]
+ [fixed in 5.0.7 - added ifdefs to jobs.c(check_job)]
+
+x pdksh 5.0.6, - (reported by Nick Holloway): exit status of command
+ substitution is lost (known problem).
+ [from Mail.2:33]:
+ This is a variation on a theme of bug number 10 (and is one reason why
+ currently ksh can not be used for Linux's MAKEDEV).
+
+ The exit status from command substitution is not available when used with
+ variable assignment.
+
+ x=`false` && echo "Non-zero exit status lost".
+ [fixed in 5.0.7 - instead of faking :, set rv to subst_exstat]
+
+x pdksh 5.0.7 - (reported by Sean Hogan): CMASK redefined in emacs.c
+ [see Mail.2:44]
+ [fixed in 5.0.8 changed CMASK to CHARMASK]
+
+x pdksh 5.0.7 - (reported by Sean Hogan): "r" (fc -e -) doesn't work.
+ [see Mail.2:45]
+ [fixed in 5.0.8 - increment wp, change strcmp() test]
+
+x pdksh 5.0.7 - (reported by Thomas Gellekum): make install typeo.
+ [see Mail.2:46]
+ [fixed in 5.0.8 - added missing $]
+
+x pdksh 5.0.8 - (reported by Sean Hogan): "FOO=bar exec blah" does not
+ put FOO in environment.
+ [see Mail.2:50]
+ [fixed in 5.0.9 - re-arranged exec/command/builtin code in comexec()]
+
+x pdksh 5.0.8, QNX 4.2 (reported by Brian Campbell): "exec > /dev/null"
+ generates an error.
+ [see Mail.2:51]
+ [see Mail.2.58 - caused by ambitious compiler using same label for c_exec()
+ and c_builtin()]
+ [fixed in 5.0.9 - c_exec() no longer an empty function.]
+
+x pdksh 5.0.8, - (reported by Brian Campbell): "echo a{b," prints a "Missing }"
+ error - at&t ksh does not. at&t ksh always has brace-expansion on (unless
+ set -o nogolob).
+ [see Mail.2:51]
+ [fixed in 5.0.9 - brace expansion now compatible with at&t ksh]
+
+x pdksh 5.0.8, - (reported by Sean Hogan): ulimit output garbled; syntax error
+ in c_ulimit.c; no configure check for HAVE_SETRLIMIT.
+ [see Mail.2:64]
+ [fixed in 5.0.9 - use shprintf instead of shellf to print values; add
+ setrlimit() check to configure]
+
+x pdksh 5.0.7, - (reported by Jan Djarv): `echo > /foo/bar' causes a script to
+ exit - POSIX says it shouldn't.
+ [see Mail.2:60]
+ [fixed in 5.0.9 - iosetup returns error code, error messages cleaned up, etc]
+
+x pdksh 5.0.8, - : `more /etc/passwd &' followed by fg messes up tty settings.
+ [fixed in 5.0.9 - only save new tty settings if job originally started in fg]
+
+x pdksh 5.0.9, - (reported by Andrew Moore): a blank line causes $? to be
+ set to zero, newline after a here-document marker isn't read.
+ [see Mail.3:5,6]
+ [fixed in 5.0.10 - don't execute null trees, read the newline]
+
+x pdksh 5.0.9, - (reported by Michael Sullivan): mail checking reports you
+ have mail, when there is only old mail.
+ [fixed in 5.0.10 - use atime/mtime instead of size]
+
+x pdksh 5.0.9, - (reported by Chris Oates): if RANDOM is in ksh's environ
+ when it starts, the shell dumps core.
+ [see Mail.3:7,8]
+ [fixed in 5.0.10 - var.c(typeset): free t->val.s instead of
+ t->val.s + t->type]
+
+x pdksh 5.0.9, - (reported by Seah Hogan): ISC 3.01's make is confused by
+ a backslash followed by a blank line.
+ [see Mail.3:9,13]
+ [fixed in 5.0.10 - changed make depend target to change blank lines to sh.h]
+
+x pdksh 5.0.9, - (reported by Andrew Moore): commands without a newline cause
+ syntax errors - sh/ksh execute the commands.
+ [see Mail.3:15]
+ [fixed in 5.0.10 - have yyparse() accept newline and EOF]
+
+x pdksh 5.0.9, - (reported by Andrew Moore): empty arithmetic expressions not
+ accepted.
+ [see Mail.3:15,17]
+ [fixed in 5.0.10 - v_evaluate(): if first token is END, changed to literal 0]
+
+x pdksh 5.0.9, - (reported by Andrew Moore): nulls in input are not ignored.
+ [see Mail.3:15]
+ [fixed in 5.0.10 - added strip_nuls() function and calls to it]
+
+x pdksh 5.0.9, - (reported by Andrew Moore): \241 (M-!) not passed through
+ command substitutions.
+ [see Mail.3:15]
+ [fixed in 5.0.10 - evaluate(): cast c to a char before comparing to MAGIC]
+
+x pdksh 5.0.9, - (reported by Andrew Moore): newlines after here-documents
+ are read twice; shell reports an error if newline is missing.
+ [see Mail.3:25]
+ [fixed in 5.0.10 - fixed up readhere()]
+
+x pdksh 5.0.9, - (reported with fix by Mike Jetzer): 'r r' repeats the r
+ command forever.
+ [see Mail.3:38]
+ [fixed in 5.0.10 - start the search from the previous command]
+
+x pdksh 5.0.9, - (reported by Mike Jetzer): edit of multi-line commands
+ does not result in single history entry.
+ [see Mail.3:38]
+ [fixed in 5.0.10 - use hist_append() to add second+ lines]
+
+x pdksh 5.0.9, - (reported by Dale DePriest): ksh_times.h uses BROKEN_TIMES
+ [see Mail.3:43]
+ [fixed in 5.0.10 - changed ksh_times.h]
+
+x pdksh 5.0.9, - (reported by J. T. Conklin): using [ instead of test is slow.
+ [see Mail.3:46]
+ [fixed in 5.0.10 - put in kludgy check for [ in eval.c(glob)]
+
+x pdksh 5.0.9, - (reported by Michael Haardt): signals do not interrupt
+ read commands.
+ [see Mail.3:20]
+ [fixed in 5.0.10 - changed c_read() to check for fatal signals after EINTR]
+
+x pdksh 5.0.10, BSDI (reported by David Tamkin): use of _POSIX_VDISABLE in
+ tty.h causes compiler error.
+ [see Mail.3:67]
+ [fixed in 5.0.10.1 - new variable vdisable_c set/used in edit.c]
+
+x pdksh 5.0.8, - (reported by Donald Craig): on systems with both union wait
+ and waitpid(), waitpid() is passed a union wait pointer instead of an int
+ pointer.
+ [see Mail.2:54]
+ [fixed in 5.1 - added ksh_waitpid() define; cast status arg as needed.]
+
+x pdksh 5.0.10, - (reported by David Tamkin): space in vi command mode does
+ nothing.
+ [see Mail.3:76]
+ [fixed in 5.1 - vi.c(classify[]) table got changed by accident.]
+
+x pdksh 5.0.10, - (reported by Danial Quinlan): forward-word and
+ delete-word-forward functions in emacs don't go to the right place.
+ [see Mail.3:79]
+ [Fixed in 5.1 - changed order of loops in emacs.c(x_fword())]
+
+x pdksh 5.0.10, - (reported by David Tamkin): eof in multiline command
+ causes shell to exit, even if ignoreeof is set.
+ [see Mail.3:76]
+ [Fixed in 5.1 - reset eof after longjmp() in main.c(shell)]
+
+x pdksh 5.0.9, Ultrix 4.2 (reported by Matthew Nethook): type-ahead while
+ shell is waiting for a command to finish is temporarily lost until a
+ program that reads from stdin or goes a stty/gtty is run.
+ [see Mail.3:61,62]
+ [Fixed in 5.1 - changed aclocal.m4 to not define HAVE_TERMIOS_H on ultrix]
+
+x pdksh 5.0.10, - (reported by David Tamkin): if INT is trapped, ^C in
+ vi/emacs won't flush buffer/re-issue new prompt.
+ [see Mail.3:5,76]
+ [Fixed in 5.1 - use unwind() in vi/emacs to get back to shell()]
+
+x pdksh 5.0.10, - (reported by Dale DePriest): in emacs mode, file completions
+ resulting in long names (>256) cause core dumps
+ [see Mail.3:72]
+ [Fixed in 5.1 - use dynamically sized buffers in emacs code]
+
+x pdksh 5.0.10, - (reported by Dale DePriest): in emacs mode, command
+ completions (^[=) resulting in multiple hits caused internal memory error.
+ [see Mail.4:8]
+ [Fixed in 5.1 - don't call list_stash() twice in compl_command]
+
+x pdksh 5.0.10, - (reported by Dave Hatton): autoloading functions fail
+ on the first attempt, then work.
+ [see Mail.4:10]
+ [Fixed in 5.1 - in findcom(), check for include() returning non-0 (was 0)]
+
+x pdksh 5.0.10, - (reported by Art Pina via Dale DePriest): when SECONDS
+ parameter is assigned, it always acts as if 0 were assigned.
+ [see Mail.4:12]
+ [Fixed in 5.1 - set internal seconds variable to time - assigned value]
+
+x pdksh 5.1.0 - (reported by Larry Bouzane): for/select loops don't allow
+ {..} to be used instead of do...done.
+ [see Mail.4:16]
+ [Fixed in 5.1.1 - changed syn.c(dogroup) to allow {/} instead of do/done]
+
+x pdksh 5.1.0 - (reported by Andrew Moore and Larry Bouzane): a command ending
+ in ; or & that is not followed by a newline causes a syntax error.
+ [see Mail.4:126,128]
+ [Fixed in 5.1.1 - don't call syntaxerr() in get_command() if EOF is read]
+
+x pdksh 5.1.0, - (reported by Simon J. Gerraty): ksh died reading history
+ file (complex history, in hist_skip_backup()).
+ [see Mail.4:24]
+ [Fixed in 5.1.1 - hist_skip_back(): don't start past the end of the buffer]
+
+x pdksh 5.1.0 BSDI 1.1 (reported by Karl Denninger): after receipt of SIGHUP,
+ shell waits for foreground process to complete.
+ [see Mail.4:50,57]
+ [Fixed in 5.1.1 - added fatal_trap flag, check in jobs.c(j_waitj)]
+
+x pdksh 5.1.0 - (reported by Bob Manson): a leading non-white-space IFS
+ character does cause a field to be delimited.
+ [see Mail.4:68]
+ [Fixed in 5.1.2 - changed expand() to do the right thing.]
+
+x pdksh 5.1.2, -: ^c during $ENV or .profile kills shell; should just go
+ to prompt.
+ [see Mail.5:14]
+ [fixed in 5.2.4 - added intr_ok flag to main.c(include)]
+
+x pdksh 5.1.2, - (reported by Dan Quinlan): when shell prints out
+ execution trees (typeset -f), if botches elif statements.
+ [see Mail.5:17]
+ [fixed in 5.1.3 - changed tree.c(ptree) to deal with elif.]
+
+x pdksh 5.1.2, - (reported by Dale DePriest): fc -l -- -40 fails if there
+ are fewer than 40 commands.
+ [see Mail.5:19]
+ [fixed in 5.1.3 - changed history.c(histget) to allow out of range numbers]
+
+x pdksh 5.1.2, - (reported by Art Mills): file completion in command mode
+ doesn't work on a single character.
+ [see Mail.5:13]
+ [fixed in 5.1.3 - in vi.c(vi_cmd) call complete_word() with 1 not 0]
+
+x pdksh 5.1.2, - (reported by Dan Quinlan): an error in a let statement
+ causes shell to exit function/script. at&t ksh just prints error and
+ returns from let.
+ [see Mail.5:17]
+ [fixed in 5.2.3 - added error_ok arg to evaluate() and v_evaluate()]
+
+x pdksh 5.1.2, - (reported by Art Mills): if markdirs option is set, file
+ completion in vi adds two slashes to directories.
+ [see Mail.5:35]
+ [fixed in 5.1.3 - vi.c(complete_word), don't add / if file ends in one]
+
+x pdksh 5.1.2, - (reported by Dale DePriest): history read from history file
+ have negative numbers and can't be accessed (fc thinks neg numbers are
+ relative).
+ [see Mail.5:39]
+ [fixed in 5.1.3 - EASY_HISTORY/hist_init: increment line for each line]
+
+x pdksh 5.1.2, - (reported by David Tamkin): FPATH isn't searched if PATH
+ search can't find command (undocumented at&t ksh feature).
+ [see Mail.5:45]
+ [fixed in 5.1.3 - exec.c(findcom) search FPATH if PATH search fails]
+
+x pdksh 5.1.2, - (reported by Dan Quinlan): output typeset -f isn't
+ very pretty (no indenting done).
+ [see Mail.5:17]
+ [fixed in 5.1.3 - indenting added to ptree routines]
+
+x pdksh 5.0.9, ISC 3.2 (reported by cobra@guarany.cpd.unb.br): Running the
+ following script with pdksh crashes the machine:
+ cat > /tmp/foobar
+ The same command in an interactive pdksh does not cause a crash.
+ [see Mail.3:21,Mail.5:62]
+ [Fixed by Interactive - it is caused by an OS bug for which there is a patch]
+
+x pdksh 5.1.3, linux - (reported by Dan Quinlan): doesn't compile under new
+ linux due to declaration conflict between basename() in unistd.h and
+ pdksh'd basename.
+ [see Mail.5:90]
+ [fixed in 5.2.0 - changed basename() to arrayname()]
+
+x pdksh 5.1.3, - (reported by William Hudacek): very long prompts cause
+ vi command line editor grief.
+ [see Mail.6:2]
+ [fixed in 5.2.0 - initial part of prompt is stripped if its too long]
+
+x pdksh 5.1.3, - (reported by Roberto Zacheo): when set -u, variable trimming
+ with always causes an error.
+ [see Mail.6:21]
+ [fixed in 5.2.0 - fixed varsub() to test if variable is null]
+
+x pdksh 5.1.3, - (reported by David Tamkin): when a function is autoloaded,
+ ksh complains the definition file didn't define the function, even if it did.
+ [see Mail.6:52]
+ [fixed in 5.2.0 - exec.c(comexec): when checking if defined, use cp,
+
+x pdksh 5.1.3, ICS unix 3.2 (reported by Robert Clark): auto configuration
+ test for memmove doesn't work
+ [see Mail.6:65]
+ [fixed in 5.2.0 - special cases added for memmove, bcopy, memset]
+
+x pdksh 5.1.3, Unixware (Intel-SVR4.2) (reported by Thanh Ma): auto
+ configuration test for memset doesn't work; same for rlimit type.
+ [see Mail.6:67]
+ [fixed in 5.2.0 - special cases added for memmove, bcopy, memset; rlim_t
+ configuration stuff re-arranged]
+
+x pdksh 5.1.3, - (reported by Mike Jetzer + fix): . in vi doesn't work
+ after history motion or after one command is completed and another is being
+ edited.
+ [see Mail.6:85]
+ [fixed in 5.2.0 - fix up classify table, special case for empty initial
+ insert]
+
+x pdksh 5.1.3, - Janjaap van Velthooven: ^v (version) missing in vi mode.
+ [see Mail.6:98]
+ [fixed in 5.2.0 - added]
+
+x pdksh 5.1.3, - : y% on or before right bracket/paren/brace doesn't yank the
+ brackets - just what is in the brackets...
+ [fixed in 5.2.0 - changes to vi.c(domove,vi_cmd)]
+
+x pdksh 5.1.3, - (reported by Rob Mayoff): [[ ]] command doesn't do lazy
+ evaluation.
+ [see Mail.7:2]
+ [fixed in 5.2.1 - test routines re-arranged to deal with this]
+
+x pdksh 5.1.3, - (reported by Will Renkel): "r | more" doesn't work (nothing
+ is sent to more).
+ [see Mail.7:13]
+ [fixed in 5.2.0 - history commands now done in c_fc, not pushed onto input
+ stack]
+
+x pdksh 5.1.3, - (reported by Rod Byrne, John Rochester): if a program leaves
+ the non-blocking (O_NONBLOCK) flag set after it exists, the shell
+ exits (multiple eofs).
+ [see Mail.7:15,16,51]
+ [fixed in 5.2.0: O_NONBLOCK is reset if read fails with EAGAIN,EWOULDBLOCK]
+
+x pdksh 5.1.3, - (reported by Dale DePriest + fix): emacs: can't delete chars
+ from pattern in incremental search mode.
+ [see Mail.7:17]
+ [fixed in 5.2.0 - handle it]
+
+x pdksh 5.1.3, Linux 1.2.2 (reported by Fritz Heinrichmeyer + fix): siglist.sh
+ doesn't work due to bug in bash 1.4.3 (trap is called incorrectly in
+ subshell causing temp file to be removed prematurely).
+ [see Mail.7:21]
+ [fixed in 5.2.0 - clear all traps in subshell so file isn't removed]
+
+x pdksh 5.1.3, - (reported by Dale DePriest + fix): emacs: can't prefix
+ commands with more than single digit; many commands don't use nnumber
+ prefix.
+ [see Mail.7:26,40]
+ [fixed in 5.2.0 - x_set_arg reads sequence of numbers, other commands
+ changed to use x_arg]
+
+x pdksh 5.1.3, - (reported by Dale DePriest): fc command line parsing
+ (and its interaction with history alias) doesn't act like at&t ksh:
+ history -40 gives bad option 4 error.
+ [see Mail.7:41,49]
+ [fixed in 5.2.1 - kludge parsing of -40 (numbers are option letters)]
+
+x pdksh 5.1.3, - (reported by Dale DePriest): if PS1 contains paramaters that
+ get expanded, and if those parameters contain any ! characters, the !'s get
+ changed to history numbers.
+ [see Mail.7:44]
+ [fixed in 5.2.0 - substitution done after ! and !! substitution]
+
+x pdksh 5.1.3, - (reported by Steve Wallis): set -a (set -o allexport) has
+ no effect.
+ [see Mail.7:47]
+ [fixed in 5.2.0 - changes to c_read, c_getopts, and comexec]
+
+x pdksh 5.1.3, - (reported by Alexander S. Jones): (sleep 10000&) waits for
+ the sleep to complete.
+ [see Mail.7:54]
+ [fixed in 5.2.0 - execute() case TASYNC clears EXEC flag in call to execute]
+
+x pdksh 5.1.3, - (reported by Will Renkel): positional parameters can't be
+ accessed within temporary variable assignments (eg, "FOO=$1 blah" doesn't
+ set FOO to $1.
+ [see Mail.7:57]
+ [fixed in 5.2.0 - var.c(newblock) - copy argc/argv from previous environment]
+
+x pdksh 5.1.3, SCO unix ? (reported by Sean Hogan): job control stuff doesn't
+ work as sco doesn't do job control operations on /dev/tty.
+ [see Mail.7:30,43,69,70,74]
+ [fixed in 5.2.0 - don't try opening /dev/tty if on SCO]
+
+x pdksh 5.1.3, - (reported with fix by Mike Jetzer): vi globing tacks
+ * at the end of files even if there are globing chars in last component
+ of filename (at&t ksh does not).
+ [see Mail.7:71]
+ [fixed in 5.2.0 - don't append * if there are unescaped globing chars]
+
+x pdksh 5.1.3, - (reported with fix by Gabor Zahemszky): typoes in acconfig.h,
+ sh.h uses SVR3_PGRP insteda of SYSV_PGRP.
+ [see Mail.7:87]
+ [fixed in 5.2.0]
+
+x pdksh 5.1.3, - (reported by Gabor Zahemszky): emacs doesn't have ^[^].
+ [see Mail.7:87]
+ [fixed in 5.2.0 - added search-char-backward]
+
+x pdksh 5.2.0, - (reported by David Tamkin): pwd -P doesn't strip .. and .
+ properly.
+ [see Mail.7:98]
+ [fixed in 5.2.0 - include ksh_stat.h in c_ksh.c]
+
+x pdksh 5.2.0, - (reported by Dale DePriest): unistd.h config test
+ doesn't include sys/types before dirent.h.
+ [see Mail.8:2]
+ [fixed in 5.2.0]
+
+x pdksh 5.2.0, - (reported by Robert Gallant): emacs file/command completion
+ code can clobber memory.
+ [see Mail.8:11]
+ [fixed in 5.2.1 - wrong variable being checked in buffer growing in
+ emacs.c(compl_file,compl_command)]
+
+x pdksh 5.2.0, - (reported by David Tamkin): when CDPATH set and cd'ing to a
+ directory that doesn't exist, the error message contains the last element
+ of the CDPATH.
+ [see Mail.8:8]
+ [fixed in 5.2.0 - fixed error message]
+
+x pdksh 5.2.0, - (reported by David Tamkin): if PS1 has an error in it
+ (eg, parameter expansion error), the shell loops forever printing
+ the error.
+ [see Mail.8:32]
+ [fixed in 5.2.3 - create error handling environment while expanding PS1]
+
+x pdksh 5.2.0, Coherent machines (reported by Gabor Zahemszky): insert after
+ movement in emacs mode replaces all chars with first char on line.
+ System's bcopy doesn't handle overlapping src/dst.
+ [see Mail.8:38,43]
+ [fixed in 5.2.1 - check for broken memmove/bcopy in aclocal.m4]
+
+x pdksh 5.2.0, - (reported by Gabor Zahemszky): ^[= in vi prints empty
+ strings for directory matches if markdirs is set.
+ [see Mail.8:48]
+ [fixed in 5.2.1 - skip trailing /'s before looking for last /]
+
+x pdksh 5.2.0, - (reported by Gabor Zahemszky): <ESC>^H bound to del-back-char
+ not del-back-word
+ [see Mail.8:50-52]
+ [fixed in 5.2.1 - fixed x_emacs_keys]
+
+x pdksh 5.2.1, - (reported by David Tamkin): compile fails due to lack
+ of c_test.h
+ [see Mail.8:58]
+ [fixed in 5.2.2 - fixed put c_test.h in distribution]
+
+x pdksh 5.2.2, - (reported by Simon J. Gerraty): hist_source not being
+ initialized in complex history.
+ [see Mail.8:64]
+ [fixed in 5.2.3 - set it in second hist_init()]
+
+x pdksh 5.2.2, - (reported by Gabor Zahemszky): set -A does not reset
+ the array contents.
+ [see Mail.8:65]
+ [fixed in 5.2.3 - changed var.c(unset) to unset whole array if appropriate]
+
+x pdksh 5.2.2, - (reported by Gabor Zahemszky): getopts stops after an error;
+ at&t ksh carries on with next option.
+ [see Mail.8:65]
+ [fixed in 5.2.3 - remove GI_DONE flag from ksh_getopt()]
+
+x pdksh 5.2.2, - (reported by Gabor Zahemszky): getopts prints shell name
+ twice in error messages.
+ [see Mail.8:65]
+ [fixed in 5.2.3 - added GI_NONAME flag]
+
+x pdksh 5.2.2, - (reported by Gabor Zahemszky): pdksh's test doesn't know about
+ /dev/fd/n.
+ [see Mail.8:65]
+ [fixed in 5.2.3 - added test_stat() and test_eaccess()]
+
+x pdksh 5.2.2, - (reported by Thomas Gellekum): config test for memmove/bcopy
+ missing semi-colon
+ [see Mail.8:67]
+ [fixed in 5.2.3]
+
+x pdksh 5.2.2, - (reported by Donald Craig): fc string doesn't find string
+ if it is the most recent command.
+ [see Mail.8:76]
+ [fixed in 5.2.3 - fixed off by one error in history.c(hist_get)]
+
+x pdksh 5.2.2, - (reported by Gabor Zahemszky): pdksh doesn't do the
+ "You have running jobs" when user attempts to log out.
+ [see Mail.8:74]
+ [fixed in 5.2.3 - added set -o nohup option with supporting code]
+
+x pdksh 5.2.2, - (reported by Gabor Zahemszky): configure test for
+ broken memmove/bcopy doesn't work.
+ [see Mail.8:93]
+ [fixed in 5.2.3 - fixed test to copy overlapping buffers]
+
+x pdksh 5.1.3, - (reported by <wendt@sv5.mch.sni.de>): doesn't compile on
+ solaris 5.x with COMPLEX_HISTORY defined.
+ [see Mail.8:98]
+ [fixed in 5.2.3 - undef COMPLEX_HISTORY if flock not available]
+
+x pdksh 5.2.2, - (reported by Gabor Zahemszky): tilde expansion not preformed
+ in word part of ${foo[-+=?} substitution.
+ [see Mail.9:7]
+ [fixed in 5.2.3 - allow ~foo to end in a close brace]
+
+x pdksh 5.2.2, - (reported by Gabor Zahemszky): "fc 30" edits from 30 to
+ most recent history (should be just 30).
+ [see Mail.9:7]
+ [fixed in 5.2.3 - if !-l and no last given, use first]
+
+x pdksh 5.2.2, - (reported by Gabor Zahemszky): [many problems with man page]
+ [see Mail.9:12]
+ [fixed in 5.2.3 - fixed problems]
+
+x pdksh 5.2.2, - (reported by Gabor Zahemszky): #else followed by non-comment
+ in sigact.c.
+ [see Mail.9:13]
+ [fixed in 5.2.3 - turn it into a comment]
+
+x pdksh 5.2.2, - (reported with fix by Gabor Zahemszky): two argument form of
+ cd doesn't work.
+ [see Mail.9:14]
+ [fixed in 5.2.3 - in c_cd(), use current_wd not path]
+
+x pdksh 5.2.2, - (reported with fix by Gabor Zahemszky): command -V doesn't
+ report reserved words.
+ [see Mail.9:30]
+ [fixed in 5.2.3 - in c_whence(), look for reserved words if vflag set]
+
+x pdksh 5.2.3, - (reported by Dale DePriest): at&t's tbl wants space
+ between font specification and end of table descrption (ie, fB . not
+ fB.).
+ [see Mail.9:41]
+ [fixed in 5.2.4 - put spaces in]
+
+x pdksh 5.2.3, - (reported by David Tamkin & Claus L{gel Rasmussen): PS1
+ isn't imported from environment anymore.
+ [see Mail.9:43,76]
+ [fixed in 5.2.4 - main: don't set PS1 if it is already set]
+
+x pdksh 5.2.3, - (reported by Gary Rafe): If PS1 contains newlines, vi
+ editing mode dones't redraw lines properly.
+ [see Mail.9:63]
+ [fixed in 5.2.4 - added prompt_skip stuff to vi/emacs]
+
+x pdksh 5.2.3, - (reported & fixed by Mike Jetzer): cd: error message if
+ directory didn't exist was wrong.
+ [see Mail.9:66]
+ [fixed in 5.2.4 - print correct string in error message]
+
+x pdksh 5.2.3, - (reported & fixed by Mike Jetzer): vi: <ESC>* shouldn't append
+ a * if word contains a $.
+ [see Mail.9:66]
+ [fixed in 5.2.4 - vi.c(glob_word): check for $ in word, check for null
+ expansion]
+
+x pdksh 5.2.3, - (reported & fixed by Mike Jetzer): vi: <ESC>= doesn't
+ list expansions in column form.
+ [see Mail.9:66]
+ [fixed in 5.2.4 - use pr_menu to print things nicely]
+
+x pdksh 5.2.3, - (reported Larry Bouzane): should be a way of installing
+ binary/man page as pdksh instead of ksh.
+ [see Mail.9:100]
+ [fixed in 5.2.4 - use the --enable-shell=pdksh option to configure]
+
+x pdksh 5.2.3, - (reported by Gabor Zahemszky): [many problems with man
+ page]
+ [see Mail.10:20]
+ [fixed in 5.2.4 - fixed problems]
+
+x pdksh 5.2.3, - (reported by Gabor Zahemszky): exec 1<&9 reports
+ error with ">&9" in it.
+ [see Mail.10:20]
+ [fixed in 5.2.4 - changed iosetup()]
+
+x pdksh 5.2.3, - (reported by Gabor Zahemszky): man page doesn't document
+ /dev/fd/N
+ [see Mail.10:20]
+ [fixed in 5.2.4 - updated manual]
+
+x pdksh 5.2.3, - (reported by Ted Coady): [[ foo/bar = foo* ]]
+ fails; should succeed.
+ [see Mail.10:32]
+ [fixed in 5.2.4 - fixed problem in exec.c(dbteste_getopnd)]
+
+x pdksh 5.2.3, - (reported by Ruei-wun Tu): make on NeXT/NeXTSTEP 3.3
+ doesn't understand .PRECIOUS target and so does nothing.
+ [see Mail.10:43]
+ [fixed in 5.2.4 - moved .PRECIOUS after all in Makefile.in]
+
+x pdksh 5.2.3, - (reported & fixed by Paul Borman): shell doesn't kill
+ foreground process when SIGHUP received; Also, CONT sent before HUP.
+ [see Mail.10:44]
+ [fixed in 5.2.4 - j_exit now sends HUP to foreground process]
+
+x pdksh 5.2.3, AIX 3.2.5 (reported by Ian Portsmouth): C compiler compains
+ about sigtraps[] being re-declared in trap.c.
+ [see Mail.10:73]
+ [fixed in 5.2.4 - use cpp define to avoid bogus re-declaration error]
+
+x pdksh 5.2.3, - (reported by Michael Haardt): ENV should not be
+ included if shell is compiled as sh and posix option not set.
+ [see Mail.10:83]
+ [fixed in 5.2.4 - only include ENV if POSIX, if compiled as sh]
+
+x pdksh 5.2.3, - (reported & fixed by DaviD W. Sanderson): case statements
+ don't allow {/} in place of IN/ESAC.
+ [see Mail.10:77,78]
+ [fixed in 5.2.4 - allow {/} in case statements]
+
+x pdksh 5.2.3, - (reported by Larry Daffner): $? is incorrectly zero'd
+ at start of traps.
+ [see Mail.11:9]
+ [fixed in 5.2.4 - don't clear exstat in main.c(shell)]
+
+x pdksh 5.2.3, - (reported by Frank "Crash" Edwards): configure on linux XXX
+ doesn't detect the presence of lstat().
+ [see Mail.11:36]
+ [fixed in 5.2.4 - change configure to include <sys/stat.h> in lstat() test]
+
+x pdksh 5.2.3, - (reported by Gabor Zahemszky): typeset -f dumps core
+ in the after using autoload functions.
+ [see Mail.11:74?]
+ [fixed in 5.2.4 - c_typeset no longer traverses the array link for functions]
+
+x pdksh 5.2.3, - (reported by Gabor Zahemszky): typeset -f does not report
+ undefined autoload functions
+ [see Mail.11:74?]
+ [fixed in 5.2.4 - c_typeset: don't ignore unset functions]
+
+x pdksh 5.2.3, - (reported by Dale DePriest): alias -t -r does not
+ reset aliases.
+ [see Mail.11:99]
+ [fixed in 5.2.4 - c_alias: call ksh_getopt_reset() before calling c_unalias]
+
+x pdksh 5.2.3, - (reported & fixed by Jason Tyler): 'echo abc^Jfc -e - a=b e'
+ echos b, not bbc.
+ [see Mail.11:100?]
+ [fixed in 5.2.4 - hist_replace: use s, not last]
+
+x pdksh 5.2.3, - (reported by Jason Tyler): 'fc -e -' when there is
+ no history causes infinite loop.
+ [see Mail.11:100?]
+ [fixed in 5.2.4 - histbackup: allow histptr to go below history]
+
+x pdksh 5.2.4, - (reported by David Tamkin): jmp_buf is used instead of
+ sigjmp_buf.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.5 - added ksh_jmp_buf and defined appropriately]
+
+x pdksh 5.2.4, - (reported by Stephen Coffin): /<RETURN> in vi mode does not
+ repeat last search.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.5 - vi.c(vi_hook) - make it repeat last search]
+
+x pdksh 5.2.4, - (reported by Gabor Zahemszky): functions containing select
+ commands aren't printed correctly by typeset.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.5 - tree.c(ptree) - add case for TSELECT]
+
+x pdksh 5.2.4, - (reported & fixed by Stefan Dalibor): COLUMNS isn't set on
+ shell start up (and window size is ignored) 'cause tty_fd isn't valid when
+ x_init() is called.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.5 - call x_init() after j_init() is called]
+
+x pdksh 5.2.4, - (reported by Will Renkel): "echo -" just prints a blank
+ line - should print the minus.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.5 - c_ksh.c(c_print): don't do argument parsing on lone -]
+
+x pdksh 5.1.3, - (reported by Gabor Zahemszky): emacs doesn't have ^[*.
+ [see Mail.7:87]
+ [fixed in 5.2.5]
+
+x pdksh 5.2.3, - (reported by Mike Jetzer): in vi, <ESC>= doesn't append
+ a / after directories.
+ [see Mail.9:66]
+ [fixed in 5.2.5]
+
+x pdksh 5.2.0, - (reported by Gabor Zahemszky): can set readonly variables
+ via command assignments (eg, "readonly x=y; x=z /bin/echo hi" should
+ fail and doesn't).
+ [see Mail.8:50,65]
+ [fixed in 5.2.5 - LOCAL_COPY flag passed from comexec() down to local()]
+
+x pdksh 5.2.4, - (reported by Tom Karches): history: "r old=new", with
+ no commands prefix given, prints "fc: too mnay arguments" - it should
+ do the subst on the previous command.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.5]
+
+x pdksh 5.2.3, - (reported by Vigen Pogosyan): assignments in $(( ... ))
+ remember the base that was assigned in pdksh - does not in at&t ksh.
+ [see Mail.10:54]
+ [fixed in 5.2.5: uset setint() in expr.c(evalexpr)]
+
+x pdksh 5.2.4, - (reported by Gabor Zahemszky): emacs: ^O steps down
+ two lines (should be 1).
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.5: convert history line to command number, then convert back]
+
+x pdksh 5.2.3, - (reported by David Gast(? gast@twinsun.com)): fc -ln -1 -1
+ reports the current command, not the previous command.
+ [see Mail.10:49]
+ [fixed in 5.2.5]
+
+x pdksh 5.2.3, - (reported by Matthew Green): foo=`^Jecho bar` doesn't
+ set foo to bar (foo is empty).
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.5: syn.c: set multiline.on when source is SSTRING]
+
+x pdksh 5.2.5, - (reported by Gabor Zahemszky): continue/break: if n
+ is too big, shell prints internal error message.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.6: fix c_brkcont to use last loop if n is too big]
+
+x pdksh 5.2.5, - (reported by Gabor Zahemszky): set: +o in ksh93
+ prints command that sets various options.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.6: changed misc.c(printoptions)]
+
+x pdksh 5.2.5, - (reported by Gabor Zahemszky): COLUMNS/LINES variables
+ are not exported.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.6: use typeset() in edit.c(x_init) to export COLUMNS/LINES]
+
+x pdksh 5.2.5, - (reported by Gabor Zahemszky): emacs: <ESC><ESC> puts
+ space after completed directories.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.6: check for single/non-directory match in emacs.c(do_complete)]
+
+x pdksh 5.2.5, - (reported by Gabor Zahemszky): vi: # removes comment
+ and executes if command already commented.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.6: added vi.c(do_comment)]
+
+x pdksh 5.2.7, - (reported by Adrian Marsh): test doesn't have == operator.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: added == to c_test.c operator table]
+
+x pdksh 5.2.7, - (reported by Mike Jetzer): pdksh sets/exports COLUMNS/LINES
+ which causes applications not to respond to window size changes.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: COLUMNS/LINES no longer exported automatically]
+
+x pdksh 5.2.7, - (reported by Gabor Zahemszky): getopts sets OPTIND differently
+ that at&t ksh when a bad option is given.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: OPTIND not set if option was bad - fragile fix - may go away]
+
+x pdksh 5.2.7, - (reported with fix by Marc Olzheim): sh version shouldn't
+ have mail check stuff, macro expansion in PS[0-9].
+ [fixed in 5.2.8: added lots of ifdefs]
+
+x pdksh 5.2.7, - (reported by Gabor Zahemszky): sub commands in PS1 cause
+ a warning message to be printed.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: lex.c(set_prompt) - don't print the warning message]
+
+x pdksh 5.2.7, - (reported by Tom Watson): some environment variables
+ (eg, PATH) are converted to uppercase on 16-bit int machine.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: struct tbl.flag (32 bit thing) was being treated as an
+ int in some places]
+
+x pdksh 5.2.7, - (reported by Gabor Zahemszky): unset always returns 0 - should
+ return 1 if variable/function is not set.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: fixed c_sh.c(c_unset)]
+
+x pdksh 5.2.7, - (reported by Gabor Zahemszky): select should only print the
+ menu the first time, if REPLAY is empty, or if a blank line is entered.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: fixed up exec.c(execute,do_selectargs)]
+
+x pdksh 5.2.7, - (reported by Gabor Zahemszky): shell reports "cannot execute"
+ error if file exists, even if . not in path.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: fixed up exec.c(comexec)]
+
+x pdksh 5.1.3, - (reported with partial fix by ra@rhi.hi.is): shell doesn't
+ listen to sigwinch.
+ [see Mail.7:7 and related]
+ [fixed in 5.2.8: changed edit.c(x_init) to catch sigwinch]
+
+x pdksh 5.2.7, - (reported by Gabor Zahemszky): typeset doesn't report
+ variables that have attributes (like export) but no values.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: fixed up c_ksh.c(c_typeset)]
+
+x pdksh 5.2.7, - (reported by Gabor Zahemszky): error message printed as
+ a result of "set -o nounset" is different from at7t ksh.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: fixed error messges in eval.c]
+
+x pdksh 5.2.7, - (reported by Gabor Zahemszky): vi/emacs: when listing
+ command/file completions, should go back at most one space. Also, should
+ allow completions on zero length names.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: fixed edit.c(x_locate_word); now allows zero-length
+ file completions (but not command)]
+
+* pdksh 5.2.7, - (reported by Gabor Zahemszky): emacs: <esc># doesn't do
+ the comment thing.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: added emacs.c(x_comment) et al.]
+
+x pdksh 5.2.7, - (reported by Gabor Zahemszky): arithmatic expressions
+ containing variables not expanded as in at&t ksh. eg, "x=1+2, let y=x"
+ fails.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: added evaling/INEXPREVAL/ET_RECURSIVE code to expr.c]
+
+x pdksh 5.2.7, - (reported by Gabor Zahemszky): unsetting the 0th element
+ of an array kills the whole array.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: var.c(unset) - allow ARRAY to be preserved]
+
+x pdksh 5.2.7, - (reported by Gabor Zahemszky): unsetting a function while
+ it is being executed can result in core dump.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: table.c(texpand) - dont free if FINUSE is set]
+
+* pdksh 5.2.7, - (reported by Gabor Zahemszky): exec 3<&p doesn't close
+ shells copy of the coprocess file desc.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.8: coprocess stuff made to act like ksh93 co-processes]
+
+x pdksh 5.2.8, - (reported with fix by Lars Hecking): doesn't compile as
+ sh - c_ksh.c and jobs.c boom out.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.9: added ifdef KSH to appropriate places]
+
+x pdksh 5.2.8, - (reported by Paolo Zeppegno): assignments containing brackets
+ are treated as commands.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.9: fixed bug in vars.c(skip_wdvarname).]
+
+x pdksh 5.2.5, - (reported by Adrian Marsh): configuration on Linux FT fails.
+ Caused by configure script using -g flag - gcc passes -lg to ld, ld fails
+ to find -lg (autoconf or Linux FT bug).
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.9: changed autoconf's -g test to do linking as well.]
+
+x pdksh 5.2.8, Solaris 2.5.1 (reported by Stefan Dalibor): 2 tests
+ (xxx-exec-environment-1 and 2) fail because printenv isn't found.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.9: changed test to use env instead]
+
+x pdksh 5.2.8, - (reported by Stefan Dalibor): shell assumes 80 columns when
+ it starts up if COLUMNS is set correctly in the environ.
+ [see Mail.XXX:XXX]
+ [fixed in 5.2.9: fixed so window size is checked at startup]
+
+x pdksh 5.2.8, NeXT machines (reported by Kai Wong): clock_t, which lives
+ in sys/time.h, isn't found by configure (causes warning messages).
+ [fixed in 5.2.9: configure now checks in sys/time.h]
+
+x pdksh 5.2.3, - (reported by Mike Jetzer): in vi, <ESC>= on word with ~
+ but no /, beeps (or prints final path component?).
+ [see Mail.9:66]
+ [fixed in 5.2.9: fixed edit.c(add_glob) so no * is appended to ~username]
+
+x pdksh 5.1.3, NeXT machines (reported by Jason Baugher): job control doesn't
+ work on NeXT machines (both m68k and x86 based) in rlogin sessions.
+ (caused by open("/dev/tty") failing - rlogin on NeXT doesn't set up
+ controlling tty properly).
+ [see Mail.7:29]
+ [fixed in 5.2.9: added hack to main to get a controlling tty]
+
+x pdksh 5.2.8, NeXT 3.2 (reported by Andrew S Townley): the output of the
+ siglist.sh script fills the disk. Also, the signal list generated (by the
+ fixed script) is mostly empty.
+ [see Mail.XXX]
+ [fixed in 5.2.9: fixed siglist.sh script to avoid infinite loops. Added
+ comment to README warning of problem with NeXT's native cc -E]
+
+x pdksh 5.2.9, - (reported by Loris Talpo): long prompts are messed up in
+ vi mode.
+ [see Mail.XXX]
+ [fixed in 5.2.9: lex.c(pprompt) was broken]
+
+x pdksh 5.2.9, - (reported by Will Renkel): a double backslash followed
+ by a newline in an unquoted here document results in one of the backslashes
+ and the newline being stripped.
+ [see Mail.XXX]
+ [fixed in 5.2.10: fixed backslash-newline processing in lex.c]
+
+x pdksh 5.2.9, - (reported by Han Holl): read x?prompt doesn't work
+ on non-interactive shells when the input is from a tty.
+ [see Mail.XXX]
+ [fixed in 5.2.10: changed test in c_read() from FTALKING to isatty]
+
+x pdksh 5.2.10, - (reported by Dale DePriest): expanding aliases causes
+ extra input.
+ [see Mail.XXX]
+ [fixed in 5.2.11: fixed syn.c(c_list).]
+
+x pdksh 5.2.10, - (reported by John Rochester): window size changes don't
+ happen on Dec unix (osf) because TIOCGWINSZ is defined in <sys/ioctl.h>
+ not in <termios.h>.
+ [see Mail.XXX]
+ [fixed in 5.2.12: sys/ioctl.h included with termios.h/termio.h if possible]
+
+x pdksh 5.2.11, - (reported by Randy Bouzane): aliases in command substitutions
+ result in code dumps.
+ [see Mail.XXX]
+ [fixed in 5.2.12: lex.c(getsc__): break if eof is read]
+
+x pdksh 5.2.11, - (reported by Will Renkel): aliases containing ;<newline>
+ or <newline><newline> aren't fully read/executed.
+ [see Mail.XXX]
+ [fixed in 5.2.12: syn.c(get_command): call inalias()]
+
+x pdksh 5.2.11, SGI/IRIX 5.2 (reported by bert@xpilot.com): pipelines
+ containing built in commands hang forever.
+ [see Mail.XXX]
+ [fixed in 5.2.12: fixed pgrp sync code in jobs.c]
+
+x pdksh 5.2.11, - (reported by Herbert Thielen via Larry Daffner): shell leaves
+ temp files around when executing shell scripts that have functions with
+ here documents which end in an exec.
+ [see Mail.XXX]
+ [fixed in 5.2.12: call main.c(cleanup_proc_env()) from exec.c(execute()).]
+
+x pdksh 5.2.12, - (reported with fix by Eric J. Chet via Thomas Gellekum):
+ . can cause core dump when cleaning up.
+ [see Mail.XXX]
+ [fixed in 5.2.13: call shf_close() before quitenv()]
+
+x pdksh 5.2.12, - (reported by Bruce Burns): test with a single -t option
+ always returns true (does a string test instead of isatty(1)).
+ [see Mail.XXX]
+ [fixed in 5.2.13: do the isatty(1) unless in posix mode]
+
+x pdksh 5.2.12, - (reported by David Tamkin): aliases ending in "; "
+ cause continuation prompt to be printed.
+ [see Mail.XXX]
+ [fixed in 5.2.13: syn.c(c_list) now handles blank lines, removed cf=CONTIN
+ in syn.c(get_command)]
+
+x pdksh 5.2.12, - (reported by Herbert Thielen via Larry Daffner): set
+ does not allow +o and -o options in the same command line.
+ [see Mail.XXX]
+ [fixed in 5.2.13: changed ksh_getopt to remove exclusion code.]
+
+x pdksh 5.2.12, - (reported by Han Holl <jeholl@euronet.nl>):
+ set -A foo -- bar doesn't skip the --.
+ [see Mail.XXX]
+ [fixed in 5.2.13: changed misc.c(parse_args) so A takes an option]
+
+x pdksh 5.2.12, - (reported by David Tamkin): -e (errexit) should be ignored
+ when processing profile and ENV.
+ [see Mail.XXX]
+ [fixed in 5.2.13: errexit saved/cleared/restored in main()]
+
+x pdksh 5.2.12, - (reported by Han Holl <jeholl@euronet.nl>):
+ [ -x foo ] succeeds for root if foo exists.
+ [see Mail.XXX]
+ [fixed in 5.2.13: changed c_test.c(test_access) to use stat in this case]
+
+x pdksh 5.2.4, - (reported by Gabor Zahemszky): echo ${foo[*]#/} generates
+ bad substsitution error, newer ksh's don't (older ones do);
+ error includes {...#@(/)}.
+ [fixed in 5.2.13: moved the @(..) hack from yylex() to expand()]
+
+x pdksh 5.2.8, - : extended pattern globing doesn't handle nested parens (),
+ e.g., [[ aby = +(a|b(x|y)) ]]
+
+x pdksh 5.2.12, - (reported by Marc Olzheim <marcolz@stack.nl>):
+ "echo a | (echo b | echo c)" causes a core dump.
+ [see Mail.XXX]
+ [fixed in 5.2.13: clear XPIPEI,XPIPEO at start of jobs.c(exchild)]
+
+x pdksh 5.2.12, - (reported by Curt Finch <curt@pnk.com>): in an interactive
+ shell, globbing isn't done on redirections if the command is part of
+ a pipeline. e.g., in "cat < /tmp/*gz | grep foo", the /tmp/*gz is
+ not expanded.
+ [see Mail.XXX]
+ [fixed in 5.2.13: clear XPIPEI,XPIPEO at start of jobs.c(exchild)]
+
+x pdksh 5.2.12, - (reported with fix by Greg A. Woods <woods@most.weird.com>):
+ in emacs, ^[_ (aka ^[.) gets the word from the previous line relative to
+ where the current line came from in the history. It should get it from
+ the last executed line ala at&t ksh.
+ [see Mail.XXX]
+ [fixed in 5.2.13: use absolute last command]
+
+x pdksh 5.2.12, - (reported by Gabor Zahemszky): MAILCHECK and MAIL
+ don't report `new mail' unless MAIL is re-assigned.
+ [see Mail.XXX]
+ [fixed in 5.2.13: mail code was using variables memory, which was later
+ trashed by exporting the MAIL variable. Fixed by saving copy of value.]
+
+x pdksh 5.2.12, - (reported by Gabor Zahemszky): memory gets badly fragmented
+ when reversing a (large) file using a simple while read loop and variable
+ concatenation.
+ [see Mail.XXX]
+ [fixed in 5.2.13: alloc.c changed to allow malloc() to deal with large
+ blocks.]
+
+x pdksh 5.2.12, - (reported by Bernd Eggink <eggink@rrz.uni-hamburg.de>)
+ ksh style functions don't save/reset/restore OPTIND.
+ [see Mail.XXX]
+ [fixed in 5.2.13: save and restore user_opt for ksh-style functions.]
+
+x pdksh 5.2.12, - (reported by Marc Olzheim <marcolz@stack.nl>)
+ ${..%..} stuff don't work in SH mode.
+ [see Mail.XXX]
+ [fixed in 5.2.13: removed ifdef KSH from misc.c(do_gmatch)]
+
+x pdksh 5.2.12, - (reported with fix by George Robbins
+ <grr@shandakor.tharsis.com>)
+ in sh, "exec 3>&1" does not keep fd 3 open in executed commands.
+ [see Mail.XXX]
+ [fixed in 5.2.13: c_sh.c(c_exec): added ifdef KSH around fd_clexec() call]
+
+x pdksh 5.2.12, - (reported with fix by George White
+ <gwhite@bodnext.bio.dfo.ca>)
+ in remove_temps(main.c), space for name field is not allocated correctly.
+ [see Mail.XXX]
+ [fixed in 5.2.13: initialize t->name in new structure]
+
+x pdksh 5.2.12, - (reported with fix by Marc Olzheim <marcolz@stack.nl>):
+ when at (past) end of the line, word/command completion will skip back
+ [see Mail.XXX]
+ [fixed in 5.2.13: edit.c(x_locate_word): delete special handling of
+ end-of-buffer]
+
+x pdksh 5.2.12, - (reported by Mike Kelly <tfsmiles@ecst.csuchico.edu>):
+ doting a directory (or a empty path) is allowed.
+ [see Mail.XXX]
+ [fixed in 5.2.13: exec.c(search_access): don't do non-regular files for R_OK]
+
+x pdksh 5.2.13, - (reported by Mike Kelly <tfsmiles@ecst.csuchico.edu>):
+ typeset -f FUNC doesn't print follows command (and expression) substitutions.
+ [see Mail.XXX]
+ [fixed in 5.2.14: tree.c(tputS): add wp++]
+
+x pdksh 5.2.13, - (reported by Thomas Gellekum):
+ make check fails on freebsd with "chmod 644 abcx failed - Inappropriate ...".
+ [see Mail.XXX]
+ [fixed in 5.2.14: make test/th convert permissions to octal]
+
+x pdksh 5.2.13, - (reported with fix by David E. Wexelblat):
+ when re-allocating memory, too much may be copied from old memory.
+ [see Mail.XXX]
+ [fixed in 5.2.14: use min old old size and new size]
+
+x pdksh 5.2.13, - (reported with fix by David E. Wexelblat):
+ set -o printed some options sans names.
+ [see Mail.XXX]
+ [fixed in 5.2.14: use 0 instead of null in options[] table]
+
+x pdksh 5.2.13, - (reported by Gabor Zahemszky):
+ emacs mode: <esc>. in very fist command causes core dump.
+ [see Mail.XXX]
+ [fixed in 5.2.14: ring bell if no history in emacs.c(x_prev_histword)]
+
+x pdksh 5.2.13, - (reported by Keith S McCabe): pdksh dumps core
+ after a cd command.
+ [see Mail.XXX]
+ [fixed in 5.2.14: exec.c(flushcom) was setting bits in table entry, instead
+ of clearing a single bit]
+
+x pdksh 5.2.12, - (reported by Jaime A. Urquidi): typeset -i reports
+ on array elements that have no value (at&t ksh reports on array
+ base name - no index).
+ [see Mail.XXX]
+ [fixed in 5.2.14: hack to c_ksh.c(c_typeset) to generate the ksh88 style
+ output]
+
+x pdksh 5.2.13, - (reported with fix by Todd C. Miller):
+ ulimit -ctn unlimittttted kills shell (resource exceeded).
+ [see Mail.XXX]
+ [fixed in 5.2.14: hacked c_ulimit to generate error val is 0 and expr doesn't
+ start with a digit]
+
+x pdksh 5.2.13, - (reported with fix by Theo de Raadt):
+ ". /dev/null" says access denied.
+ [see Mail.XXX]
+ [fixed in 5.2.14: exec.c(search_access): allow non-regular file if reading]
+
+x pdksh 5.2.13, - (reported with fix by Eric Youngdale): flag field in aliases
+ incorrectly changed (all flags set instead of clearing ISSET) in
+ exec.c(flushcom).
+ [see Mail.XXX]
+ [fixed in 5.2.14: exec.c(flushcom): change = ~ISSET to &= ~ISSET]
+
+x pdksh 5.2.13, - (reported by Andre Delafontaine): ${#array[*]} prints
+ largest index instead of number of (set) elements in an array (ksh88 does
+ the former).
+ [see Mail.XXX]
+ [fixed in 5.2.14: eval.c(varsub): count number of elements]
+
+x pdksh 5.2.13, - (reported by Clifford Wolf): sys_siglist[] doesn't
+ always have NSIG non-null entries...
+ [see Mail.XXX]
+ [fixed in 5.2.14: trap.c(inittrap): check for null sys_siglist entries.]
+
+x pdksh 5.2.13, - (reported with fix by Todd C. Miller): waitfor in jobs.c
+ can cause core dump if j_lookup fails.
+ [fixed in 5.2.14: jobs.c(waitfor): return if j_lookup fails]
+
+x pdksh 5.2.13, - (reported by Martin Bond): if shell is started several
+ times in quick succession, echo $RANDOM produces the same results.
+ [fixed in 5.2.14: main.c(main): seed RANDOM using time, pid, ppid]
+
+x pdksh 5.2.13, - (reported by Martin Bond): repeating "echo `echo $RANDOM`"
+ will always produce the same number.
+ [fixed in 5.2.14: call var.c(change_random) from jobs.c(exchild)]
+
+x pdksh 5.2.13, hpux 10.x (reported by Mike Kelly): pwd will dump core if
+ current directory is not readable.
+ [fixed in 5.2.14: config test & code to work around hpux C library bug]
+
+x pdksh 5.2.13, linux (reported by Mike Jetzer): getwd warning from linker
+ [fixed in 5.2.14: configure.in/misc.c: check for getcwd, use it over getwd]
+
+x pdksh 5.2.13, (reported by Dmitri Kulginov): "trap exit 1" does not set a
+ trap for HUP (exit is mistaken as a signal name, not a command).
+ [fixed in 5.2.14: c_sh.c(c_trap): use case sensitive lookup for first arg]
+
+x pdksh 5.2.13, (reported by Mark Funkenhauser): eval "$(false)" does not
+ result in $? being set to 1 (is 0).
+ [fixed in 5.2.14: c_sh.c(c_eval): set exstat to subst_exstat before shell()]
+
+x pdksh 5.2.13, (reported with fix by Kevin Schoedel): word boundaries in
+ file completion are only spaces - at&t ksh uses ()<>&| and spaces.
+ [fixed in 5.2.14: edit.c(IS_WORDC): changed macro to be LEX1 + quotes]
+
+x pdksh 5.2.13.5, (reported with fix by Martin Lucina <mato@kotelna.sk>):
+ exit status parsing in exit command incorrect (sets status to random
+ value if no argument given).
+ [fixed in 5.2.14: c_sh.c(c_exitreturn): only set exstat if arg given]
+
+x pdksh 5.2.13.5, (reported with fix by Martin Lucina <mato@kotelna.sk>):
+ KSH_CHECK_H_TYPE in aclocal.m4 has too many [] around the patterns.
+ [fixed in 5.2.14: aclocal.m4(KSH_CHECK_H_TYPE): remove two pairs on []]
+
+x pdksh 5.2.13.5, (reported by Charles M. Hannum <root@ihack.net>): An exit
+ trap set in a subshell is not executed (unless explicit exit used).
+ [fixed in 5.2.14: exec.c(execute): changed exit(rv) to unwind(LEXIT).]
+
+x pdksh 5.2.12, - : MAILCHECK isn't preserved from the environment on startup.
+ [fixed in 5.2.14: changed main's initcoms[].]
+
+x pdksh 5.2.13, (reported by Marc Olzheim): time at the end of a pipeline
+ doesn't print anything.
+ [fixed in 5.2.14: exec.c(execute): clear XEXEC when calling timex().]
+
+x pdksh 5.2.13, (reported by David J. McMahon): here documents in subshells
+ don't work if the parent exits before the subshell.
+ [fixed in 5.2.14: heredocs now saved in memory, written to temp when needed.]
+
+x pdksh 5.2.13, (reported by Seiichi Namba): emacs: keys bound in .profile/$ENV
+ are overridden by stty settings (eg, binding ^U in .profile has no effect).
+ [fixed in 5.2.14: emacs.c: added x_bound array to track what user has set.]
+
+x pdksh 5.2.13: vi: failed redo (.) commands caused line to be returned to the
+ shell (eg, "echo hi/there^[Bdf/.").
+ [fixed in 5.2.14: vi.c(vi_hook): removed !=0 from VREDO switch expression]
+
+x pdksh 5.2.13, (reported by Arthor Pool): man page: (a) the time reserved
+ word is not described; (b) description of command line wrapping is in vi
+ section only (not in emacs); (c) limit on array indices not mentioned;
+ (d) ignoreeof ignored if eof read 13 times.
+ [fixed in 5.2.14: man page updated]
+
+x pdksh 5.2.13, (reported by Arthor Pool): set -u causes loss of stdout
+ when command substitution with undefined parameter reference is run
+ in an interactive shell.
+ [fixed in 5.2.14: jobs.c(fill_command): don't eval TCOM arguments]
+
+ XXX fd 1 lost (general fd pool handler?, error handler for comsub?)
+
+ AP messages:
+ time not descr
+ vi/emacs <+>
+ trap
+ typeset -f ... "$(jasdsjh)" ...
+ array
+ os2 interrupts + pattern
+ ignoreeof
+ set -A --
+ set -u -> comsub errors & fill_command eval
+
+x pdksh 5.2.13, (reported by Dave Hillman): test -nt
+ and test -ot do not succeed if file2 (file2) does not exist.
+ [fixed in 5.2.14: c_test.c(test_eval): return true if appropriate stat fails]
+
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
@@ -0,0 +1,129 @@
+$OpenBSD: CONTRIBUTORS,v 1.10 2006/02/06 16:47:07 jmc Exp $
+
+This is a partial history of this shell gleened from old change logs and
+readmes (most of which are still in the misc directory) and the source
+code. Hopefully it is correct and no contributors have been left out
+(file a bug report if you spot a problem :-)).
+
+Release history:
+ * Eric Gisin (egisin@math.uwaterloo.ca), created pdksh, using
+ Charles Forsyth's public domain V7 shell as a base; also used parts
+ of the BRL shell (written by Doug A Gwyn, Doug Kingston, Ron Natalie,
+ Arnold Robbins, Lou Salkind, and others?, circa '87; the parts used in
+ pdksh included getopts, test builtin, ulimit, tty setting/getting, emacs
+ editing, and job control; the test builtin was based on code by Erik
+ Baalbergen).
+ '87..'89 ?
+ Released versions: .. 3.2
+ * John R MacMillan (@yonge.csri.toronto.edu:chance!john@sq.sq.com)
+ takes over as maintainer
+ dates?
+ Released versions: 3.3 (?)
+ * Simon J. Gerraty (sjg@zen.void.oz.au) takes over as maintainer
+ Nov '91..July '94 ?
+ Released versions: 4.0 .. 4.9
+ * Michael Rendell (michael@cs.mun.ca) takes over as maintainer
+ July, 1994
+ Released versions: 5.0 .. 5.2
+
+Major contributions:
+ * John R MacMillan (@yonge.csri.toronto.edu:chance!john@sq.sq.com), ?:
+ cleaned up configuration, many bug fixes (see misc/Changes.jrm).
+ * Simon Gerraty, (sjg@zen.void.oz.au), Nov '91..?: much improved emacs mode
+ ala at&t ksh, 386bsd port, sigaction routines for non-POSIX systems
+ (see misc/ChangeLog.sjg and misc/ReadME.sjg).
+ * Peter Collinson (pc@hillside.co.uk), July '92: added select, at&t ksh
+ style history file, original csh-style {} globbing, BSD/386 port,
+ misc bug fixes.
+ * Larry Bouzane (larry@compusult.nf.ca), Mar '89..'93: re-wrote job control,
+ added async job notification, added CDPATH and other cd fixes, misc bug
+ fixes.
+ * John Rochester (jr@cs.mun.ca), '87: wrote vi command line editor; various
+ bug fixes/enhancements.
+ * Jeff Sparkes (jsparkes@bnr.ca), Mar '89..Mar '90: added arrays,
+ merged John Rochester's vi code into pdksh, misc bug fixes.
+ * Michael Haardt (u31b3hs@POOL.Informatik.RWTH-Aachen.DE), Sept '94:
+ organized man page, filled in many of its copious blank spots; added
+ KSH ifdefs.
+ * Dale DePriest (daled@cadence.com): ported to OS/2 (initially based on
+ port of pdksh4.9 to OS/2 by Kai Rommel (rommel@ars.muc.de)); maintains
+ OS/2 port; misc bug fixes.
+
+Other contributors:
+ * Piercarlo Grandi (pcg@aber.ac.uk), Dec '93: fixes for linux port
+ * Neil Smithline (Neil.Smithline@eng.sun.com), Aug '92: emacs-style
+ filename completion.
+ * Mike Jetzer [mlj] (jetzer@studsys.mscs.mu.edu), ?;Nov '94: fixes for vi
+ mode (see misc/Changes.mlj), added v to vi, fixes for history; fixed
+ command redoing in vi; fixes to vi globbing.
+ * Robert J Gibson: mailbox checking code that was adapted for pdksh by
+ John R. MacMillan.
+ * ? (guy@demon.co.uk), ?: promptlen() function.
+ * J.T. Conklin (jtc@cygnus.com): POSIXized test builtin; miscellaneous
+ fixes/enhancements.
+ * Sean Hogan (sean@neweast.ca): fixes for ICS 3.0 Unix, found and helped
+ fix numerous problems.
+ * Gordan Larson (hoh@approve.se): fix to compile sans VI, ksh.1 typo.
+ * Thomas Gellekum (thomas@ghpc8.ihf.rwth-aachen.de): fixes for Makefile
+ typos, fixed CLK_TCK for FreeBSD, man page fixes.
+ * Ed Ferguson (Ed.Ferguson@dseg.ti.com): fix to compile sans VI.
+ * Brian Campbell (brianc@qnx.com): fixes to compile under QNX and
+ to compile with dmake.
+ * (guy@netapp.com), Oct '94: patch to use gmacs flag.
+ * Andrew Moore (alm@netcom.com): reported many bugs, fixes.
+ * William Bader (wbader@CSEE.Lehigh.Edu): fix to compile on SCO Unix
+ (struct winsize).
+ * Mike Long (mike.long@analog.com): makefile fix - use $manext, not 1.
+ * Art Mills (aem@hpbs9162.bio.hp.com): bug fix for vi file completion in
+ command mode.
+ * Tory Bollinger (tboll@authstin.ibm.com): allow ~ in vi mode to take
+ a count.
+ * Frank Edwards (<crash@azhrei.EEC.COM>): added macros to vi (@char).
+ * Fritz Heinrichmeyer (<Fritz.Heinrichmeyer@FernUni-Hagen.de>): fixes
+ to allow compile under Linux 1.4.3.
+ * Gabor Zahemszky (<zgabor@CoDe.hu>): SVR3_PGRP vs SYSV_PGRP, many
+ bug reports and man page fixes.
+ * Dave Kinchlea (<kinch@julian.uwo.ca>): DEFAULT_ENV patches.
+ * Paul Borman (<prb@bsdi.com>): j_exit: send HUP, then CONT; HUP fg process.
+ * DaviD W. Sanderson (<dws@ssec.wisc.edu>): patches to allow { .. } instead
+ of in .. esac in case statements.
+ * ? (<ra@rhi.hi.is>): partial patches to handle SIGWINCH for command line
+ editing.
+ * Jason Tyler (<jason@nc.bhpese.oz.au>): fixes for bugs in fc.
+ * Stefan Dalibor (<Stefan.Dalibor@informatik.uni-erlangen.de>): fix for
+ COLUMNS never being set in x_init().
+ * Arnon Kanfi (<arnon@gilly.datatools.com>): fix for prompt.
+ * Marc Olzheim (<marcolz@stack.nl>): patches to ifdef KSH the mail check
+ code and aliases; enum patches for old K&R compilers; handle missing dup2.
+ * Lars Hecking (<lhecking@nmrc.ucc.ie>): fixes so shell compiles as sh
+ again.
+ * Bill Kish (<kish@browncow.com>): added prompt delimiter hack for
+ hidden characters (eg, escape codes).
+ * Andrew S. Townley (<atownley@informix.com>): fixes for NeXT machines:
+ get a controlling if one needed, use correct profile.
+ * Eric J. Chet (<ejc@bazzle.com>): fix for core dump in . (quitenv() called
+ too soon).
+ * Greg A. Woods <woods@most.weird.com>: fix to make ^[_ in emacs work
+ as in at&t ksh.
+ * George Robbins <grr@shandakor.tharsis.com>: fix for sh mode to
+ keep exec'd file descriptors open.
+ * George White <gwhite@bodnext.bio.dfo.ca>: fix here-doc problem under OS/2
+ (memory allocated incorrectly).
+ * David E. Wexelblat <dwex@DataFocus.com>: fix to avoid memory overrun
+ in aresize(); fix to not print un-named options.
+ * Clifford Wolf (<clifford@clifford.at>): fix memory overrun in aresize();
+ fixed sys_siglist[] problem.
+ * Theo de Raadt (<deraadt@cvs.openbsd.org>): allow ". /dev/null".
+ * Eric Youngdale (<ericy@datafocus.com>): flag field incorrectly changed
+ in exec.c(flushcom).
+ * Todd. C Miller (Todd C. Miller <Todd.Miller@courtesan.com>): fix
+ for coredump in jobs.
+ * Kevin Schoedel <schoedel@kw.igs.net>: fix for word location in file
+ completion.
+ * Martin Lucina <mato@kotelna.sk>: fix for argument parsing in exit command,
+ fix for KSH_CHECK_H_TYPE.
+ * Mark Funkenhauser <mark@interix.com>: added $LINENO support.
+ * Corinna Vinschen <Corinna@Vinschen.de> and Steven Hein <ssh@sgi.com>:
+ port to cyngin environment on win95/winnt.
+ * Martin Dalecki <dalecki@cs.net.pl>: changes for 8 bit emacs mode.
+ * Dave Hillman <daveh@gte.net>: patch for bug in test -nt.
diff --git a/ChangeLog b/ChangeLog
@@ -0,0 +1,1600 @@
+Tue Jul 13 14:32:57 NDT 1999 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * made pdksh-5.2.14 distribution
+
+Wed Jun 30 17:42:54 NDT 1999 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * c_test.c(test_eval): changed -nt/-ot tests so they succeed
+ if file2 (file2) `does not exist' (ie, the stat fails).
+ (based on fix from Dave Hillman).
+
+Tue May 25 17:23:39 NDT 1999 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * jobs.c(fill_command): do not eval() TCOM arguments - can cause
+ problems.
+
+Tue May 25 15:26:31 NDT 1999 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * new-version.sh,ksh.Man: added version number to man page; update
+ version as well as date when updating tests/version.t and ksh.Man.
+
+Mon May 24 20:57:21 NDT 1999 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * c_sh.c(c_eval): only set exstat to substs_exstat if in non-posix mode.
+
+Mon May 24 15:44:10 NDT 1999 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * tree.h(FTIME): new define.
+ * c_sh.c(timex): stuff to get info to/from timex_hook.
+ * c_sh.c(timex_hook): new function (handles option processing).
+ * exec.c(execute): call timex_hook() after TCOM eval().
+
+Tue May 18 12:23:27 NDT 1999 Michael Rendell (michael@deimos.cs.mun.ca)
+
+ * vi.c(vi_hook): case VREDO: removed != 0 from switch expression.
+
+Tue May 18 11:24:12 NDT 1999 Michael Rendell (michael@deimos.cs.mun.ca)
+
+ * emacs.c(CHARMASK,X_TABSZ): changed from 128 to 256.
+ * emacs.c(x_size,x_zotc,x_mapout): use iscntl() vs range test.
+ (Based on changes from Martin Dalecki)
+
+Thu May 13 17:23:17 NDT 1999 Michael Rendell (michael@deimos.cs.mun.ca)
+
+ * emacs.c(x_bound,bind_if_not_bound): new variable/function.
+ * emacs.c(x_bind): set bit in x_bound[].
+ * emacs.c(x_emacs_keys): call bind_if_not_bound.
+
+Thu May 13 14:23:12 NDT 1999 Michael Rendell (michael@deimos.cs.mun.ca)
+
+ * sh.h: ifdefs for __CYGWIN__ for path defines.
+ * path.c(simplify_path): ifdefs for __CYGWIN__; preserve leading
+ double-slash on pathnames.
+ * c_ksh.c(c_cd): use cygwin_conv_to_full_posix_path().
+ * edit.c(x_mode): default eof char to ^D.
+
+ [fixes from Corinna Vinschen and Steven Hein, obtained from
+ ftp://ftp.franken.de/pub/win32/develop/gnuwin32/cygwin/
+ porters/Vinschen_Corinna/B20/]
+
+Wed May 12 12:30:09 NDT 1999 Michael Rendell (michael@deimos.cs.mun.ca)
+
+ * exit.c(x_mode): set fields of edchars to -1 if corrisponding char
+ is unset.
+ * exit.c(x_init): initialize edchars to -2, not -1.
+ * emacs.c(x_emacs_keys): check if char is >= 0 before setting.
+
+Wed May 12 11:31:24 NDT 1999 Michael Rendell (michael@deimos.cs.mun.ca)
+
+ * shf.c(shf_write): don't buffer if buffer is empty and we're
+ writting a large amount.
+ * shf.c(shf_open): changed to use shf_reopen instead of shf_fdopen
+ so alloca failing won't lose the fd.
+
+Wed May 12 10:19:43 NDT 1999 Michael Rendell (michael@deimos.cs.mun.ca)
+
+ * sh.h: deleted TT_HEREDOC_RAW define.
+ * tree.h(struct ioword): added heredoc field.
+ * tree.c(iocopy,iofree): copy/free heredoc field; remove special case
+ for IOHERE and name field.
+ * tree.c(ptree): changed to use heredoc content string (not open temp).
+ * lex.c(yylex): initialize heredoc field.
+ * lex.c(readhere): save to string instead of a temp file.
+ * exec.c(herein): changed first are from file name to heredoc content
+ string; changed all calls. Changed to always create a new temp file
+ and write content to it.
+
+Tue May 11 11:38:22 NDT 1999 Michael Rendell (michael@deimos.cs.mun.ca)
+
+ * tree.c(iofree): free delim field; don't free name of IOHERE iowords.
+
+Tue May 11 10:57:53 NDT 1999 Michael Rendell (michael@deimos.cs.mun.ca)
+
+ * sh.h(func_heredocs): deleted.
+ * sh.h(EF_FAKE_SIGDIE): added.
+ * lex.c(readhere): put function heredocs at bottom of env stack.
+ * main.c(quitenv,cleanup_proc_env): deleted remove_temps(func_heredocs)
+ calls.
+
+ * main.c(quitenv): moved exit of no oenv to en after reclaim.
+ * main.c(cleanup_parents_env): free ep->savefd and set to 0.
+ * main.c(unwind,quitenv): moved code for E_NONE from unwind()
+ to quitenv().
+
+Mon May 10 17:04:03 NDT 1999 Michael Rendell (michael@deimos.cs.mun.ca)
+
+ * exec.c(herein): restore source to osource after yylex().
+
+Mon May 10 12:14:40 NDT 1999 Michael Rendell (michael@deimos.cs.mun.ca)
+
+ * tree.c(iocopy): don't copy IOHERE name (it belongs to a struct temp).
+ * tree.c(wdscan): added default case to print internal error.
+
+Mon May 10 10:39:34 NDT 1999 Michael Rendell (michael@deimos.cs.mun.ca)
+
+ * sh.h(Temp_type): new enum (TT_HEREDOC_RAW, TT_HEREDOC_EXP,
+ TT_HIST_FILE).
+ * sh.h(struct temp): added type field.
+ * io.c(maketemp): added type and tlist arguments; changed
+ all calls.
+
+Tue Apr 27 11:31:48 NDT 1999 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * exec.c(execute): clear XEXEC in the call to timex() so time
+ can be used at the end of a pipeline.
+
+Fri Apr 23 16:29:01 NDT 1999 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * mail.c(mcheck): don't check if MAILCHECK is set, just check if
+ mplist is null.
+ * mail.c(mcset): new function.
+ * var.c(setspec): case MAILCHECK: call mcset.
+ * var.c(unspecial): new function.
+ * var.c(unsetspec): call unspecial for LINENO, MAILCHECK, RANDOM,
+ SECONDS, TMOUT.
+
+Fri Apr 23 15:34:39 NDT 1999 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * main.c(initcoms): put MAILCHECK, SECONDS, TMOUT in an eval to
+ preserve previous values.
+ * var.c(getspec): case V_SECONDS: don't do anything special if
+ variable not set.
+
+Thu Apr 22 15:03:27 NDT 1999 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * var.c(setstr): error if var is RDONLY.
+ * var.c(global): non-letter params: set RDONLY flag after setstr call.
+ * c_ksh.c(c_getopts), eval.c(expand), exec.c(execute):
+ removed readonly check.
+
+ * sh.h(KSH_UNWIND_ERROR, KSH_RETURN_ERROR): new defines.
+ * var.c(setstr): added error_ok argument; changed all calls.
+ * c_ksh.c(c_getopts): clear READONLY and INTEGER flags for OPTARG;
+ return non-zero if variable can't be set.
+ * var.c(typeset): if fake_assign fails, unset the variable's value
+ and carry on for rest of array, then unwind.
+ * expr.c(expand,v_expand): changed all calls to use KSH_UNWIND_ERROR
+ or KSH_RETURN_ERROR.
+
+Tue Apr 20 16:52:24 NDT 1999 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * configure.in: added check dup2.
+ * sh.h: added dup2 prototype.
+ * aclocal.m4: replace AC_HEADER_DIRENT so it checks -lndir.
+
+ * missing.c(dup2): new function.
+ Based on code from Marc Olzheim.
+
+Fri Apr 16 16:32:27 NDT 1999 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * syn.c(lineno_offset): removed variable and all references.
+ * tree.c(tcopy): copy lineno field.
+ * var.c(user_lineno): new variable.
+ * var.c(setspec): added case for V_LINENO (sets user_lineno).
+ * var.c(getspec): V_LINENO: add in user_lineno.
+
+Fri Apr 16 15:26:26 NDT 1999 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * tree.h(struct op): added lineno field.
+ * table.h(V_LINENO, current_lineno): new define/variable.
+ * exec.c(execute): set current_lineno for TCOM.
+ * syn.c(lineno_offset): new variable.
+ * syn.c(get_command): set t->lineno.
+ * syn.c(function_body): save/restore lineno_offset;
+ * syn.c(compile): initialize lineno_offset
+ * var.c(initvar,getspec): added V_LINENO entry.
+
+ Changes from Mark Funkenhauser.
+
+Fri Apr 16 12:18:08 NDT 1999 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * expr.c,misc.c(getoptions): added int casts to avoid errors from
+ old K&R compilers.
+ Fixes from Marc Olzheim.
+
+Fri Jan 15 12:51:53 NST 1999 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * expr.c: pass es as first param to all functions; deleted
+ es global variable.
+
+Tue Jan 12 12:28:41 NST 1999 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * emacs.c(x_defbindings[]): removed #else part of ifdef OS2.
+
+ * shf.c(shf_getse): added code to strip \r for OS2.
+ * lex.c(getsc_line): removed OS2 ifdefs
+
+ * os2/misc.c(ksh_execve),sh.h: added flags argument; changed all calls.
+ * exec.c(scriptexec): OS2: make copy of a0 before calling
+ search_access(X_OK).
+ * sh.h: OS2: changed EXECSHELL, EXECSHELL_STR.
+ * jobs.c(exchild): set XINTACT.
+ * os2/config.h: added HAVE_TERMIOS_H.
+ * os2/configure.cmd: changed test for existence of sed & gcc.
+
+ Fixes from Ilya Zakharevich.
+
+ * tests/th: added -C option, added "category" field.
+ * tests/th(category_check): new function.
+ * tests/*.t: added "category: !os:os2" to a few tests.
+
+Tue Jan 12 11:17:52 NST 1999 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(execute): changed exit(rv) to unwind(LEXIT) to
+ allow exit traps to be done.
+
+Tue Jan 5 16:45:00 NST 1999 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * aclocal.m4(KSH_CHECK_H_TYPE): remove extra [] from egrep pattern.
+ * c_sh.c(c_exitreturn): fixed logic of exit status parsing
+ (fixes from Martin Lucina).
+
+Tue Jan 5 16:31:37 NST 1999 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * edit.c(x_locate_word): changed IS_WORDC macro from !isspace
+ to !lex1/'/"
+ (based on fix from Kevin Schoedel).
+
+Wed Dec 16 15:02:48 NST 1998 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * io.c(kshdebug_init_,kshdebug_printf_,kshdebug_dump_),
+ sh.h(kshdebug_init,kshdebug_printf,kshdebug_dump):
+ new macros/functions.
+
+Wed Dec 16 12:12:23 NST 1998 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_sh.c(c_eval): set exstat to substs_exstat to propogate
+ substition exit status if resulting command is empty
+ (based on fix from Mark Funkenhauser).
+
+Tue Dec 15 15:50:34 NST 1998 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * main.c(initcom[]): PPID no longer read only.
+
+Mon Dec 14 17:09:52 NST 1998 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * trap.c(gettrap): added igncase argument; changed all calls.
+ * c_sh.c(c_trap): use case sensitive compare for first gettrap().
+ (fix "trap exit 1").
+
+Thu Dec 10 12:24:53 NST 1998 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * configure.in: added test for getcwd.
+ * aclocal.m4(KSH_OS_TYPE): added case for hpux; added test for
+ bug in hpux getcwd (dumps core if . is not readable).
+ * config.h.in: added HAVE_HPUX_GETWD_BUG define.
+ * aclocal.m4,configure.in: remove AC_C_CROSS or change to AC_PROG_CC.
+ * misc.c(ksh_get_wd): added code to handle bug in hpux getwd;
+ changed precedence of getcwd vs getwd (use getcwd if available:
+ getwd causes warnings under linux).
+
+Tue Dec 8 17:17:47 NST 1998 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * main.c(main): seed RANDOM using time, pid, ppid (was just time).
+
+Tue Nov 24 17:17:12 NST 1998 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ulimit.c(c_ulimit): improve setrlimit error message for EPERM
+ (fix from Todd C. Miller).
+
+Thu Nov 19 18:09:59 NST 1998 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * jobs.c(waitfor): if j_lookup fails, always return
+ (fix from Todd C. Miller).
+
+Fri Oct 23 19:59:25 NDT 1998 Michael Rendell (michael@lenny.cs.mun.ca)
+
+ * jobs.c(JF_SAVEDTTYPGRP,j_resume,j_waitj): added save_ttypgrp
+ stuff to deal with new gnu su which doesn't exec, but forks
+ then execs.
+
+Thu Sep 24 16:23:48 NDT 1998 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * trap.c(inittrap): Don't assume sys_siglist[] has NSIG non-null
+ entries (fix from clifford@clifford.at).
+
+Thu Aug 6 14:46:45 NDT 1998 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * eval.c(varsub): ${#array[*]} now prints N elements, not
+ max index.
+
+Sun Jul 19 11:50:21 NDT 1998 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * syn.c(function_body): fixed bug in handling of empty function
+ body; if empty, pretend there is a : command.
+
+Mon Jun 29 10:13:02 NDT 1998 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(search_access): allow non-regular files to be .'ed
+ (fix from Theo de Raadt).
+
+Thu Jun 25 17:01:36 NDT 1998 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ulimit.c(c_ulimit): added KSH_RLIM_INFINITY and defined
+ if system doesn't define RLIM_INFINITY; use when setting limits.
+ When setting, if expression evaluates to 0 and string was not
+ a number, generate an error (based on fix from Todd C. Miller).
+
+Wed Mar 11 16:35:37 NST 1998 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(flushcom): clear ISSET bit, don't set all the other bits
+ (fix from Eric Youngdale).
+
+Tue Dec 16 11:07:21 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * shf.c(shf_vfprintf): %e/%f/%g conversion now prints negative
+ numbers correctly (fix from Larry Bouzane).
+
+Thu Nov 20 15:16:15 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * emacs.c(x_prev_histword): check if histptr is 0.
+
+Sat Nov 8 11:46:32 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * misc.c(options[]): changed null entries to (char *) 0
+ (based on fix from David E. Wexelblat).
+
+Fri Nov 7 14:45:24 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * alloc.c(aresize): avoid memory overrun when copying old memory
+ to new memory.
+ (fix from David E. Wexelblat).
+
+Tue Oct 28 11:26:22 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * tests/th: file-setup code: convert chmod argument to octal.
+
+Tue Oct 28 11:00:45 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * tree.c(tputS): incr wp after COMSUB and EXPRSUB while loop
+ to get past null.
+
+Mon Oct 27 12:38:05 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.2.13 distribution
+
+Mon Oct 27 12:21:51 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_sh.c(c_dot): use search() error argument to report problem
+ correctly.
+ * exec.c(search_access): don't set *errnop if it is already set.
+ * exec.c(search_access): extended non-regular file check from
+ just X_OK to both X_OK and R_OK.
+
+Wed Oct 22 11:49:02 NDT 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * edit.c(x_locate_word): don't skip trailing space if at end
+ of buffer (based on fix from Marc Olzheim).
+
+Fri Aug 15 22:06:53 NDT 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * eval.c(varsub,expand), lex.c(yylex): allow :%, :#, :%% and :##
+ to be compatible with ksh88.
+
+Sat Aug 2 12:13:30 NDT 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * syn.c(get_command): case MDPAREN/DBRACKET: do not
+ clear KEYWORD|ALIAS from syniocf.
+
+Tue Jul 29 16:24:38 NDT 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_sh.c(c_exec): added ifdef KSH around fd_clexec()
+ (based on fix from George Robins).
+
+Tue Jun 3 12:52:05 NDT 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * misc.c(do_gmatch): removed ifdef KSH about @(..|..) code as it
+ is needed in SH mode for ${..%..} stuff.
+
+Mon May 19 16:10:06 NDT 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * table.h(struct block): added getopt_state and flags fields;
+ added BF_DOGETOPTS.
+ * sh.h,c_ksh.c: moved user_opt decl/defn from c_ksh.c to sh.h.
+ * var.c(getspec): added case for V_OPTIND.
+ * var.c(popblock): if BF_DOGETOPTS set, restore user_opt.
+ * exec.c(comexec): case CFUNC: save user_opt for ksh-style functions.
+ * c_ksh.c(getopts_reset,c_getopts): removed getopts_noreset variable
+ and code.
+ * sh.h(Getopts): added uoptind field.
+
+Fri May 16 11:40:22 NDT 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * io.c(error_prefix): don't print kshname if it is the
+ same as the source file name.
+
+Thu Mar 13 10:42:31 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * mail.c(mbset): save a copy of the path so it can't get trashed
+ (eg, by exporting a variable).
+
+Wed Feb 26 11:24:06 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * emacs.c(x_prev_histword): get word from last command entered,
+ not from last command relative to current location in history
+ (fix from Greg A. Woods).
+
+Sun Feb 16 13:18:52 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(FTALKING_I): new define
+ * misc.c(options[]): added anonymous options for internal use:
+ changed all code using options to not assume null option name
+ is the end of options (use NELEM()) instead.
+ Added slot for FTALKING_I
+
+ * c_sh.c(c_read), exec.c(iosetup): test FTALKING_I instead of FTALKING.
+ * main.c(main): set FTALKING_I.
+
+Fri Jan 10 16:36:36 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * jobs.c(exchild): use orig_flags instead of flags when testing
+ XPIPEI/XPIPEO; clear all flags except XEXEC and XERROK.
+
+Tue Jan 7 11:16:08 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.h(yynerrs): deleted (not used); deleted all assignments of it.
+
+Fri Jan 3 13:40:29 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.c(yylex): case STBRACE: interpret ( | ) as patterns;
+ case SPATTERN: allow ( as an alias for @(.
+
+Thu Jan 2 15:44:07 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * main.c(main): set PATH to def_path in startup.
+
+Thu Jan 2 10:19:43 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(comexec): ifdef KSH the setting of $_; only set $_ to
+ last arg if interactive.
+
+Wed Jan 1 13:38:26 NST 1997 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.c(yylex),eval.c(expand),tree.c(tputS,wdscan,wdstrip):
+ changed OSUBST/CSUBST encoding to have { or x after xSUBST.
+
+ * lex.c(yylex): case ${: don't prepend @( and append ) to trim patterns.
+ * eval.c(expand): prepend MAGIC @ and append MAGIC ) to trim patterns.
+
+ * syn.c(function_body): call wdstrip().
+ * tree.c(wdstrip): new function.
+
+ * lex.c(yylex): moved handling of < and > into one location.
+
+Wed Dec 11 13:00:05 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(ksheuid): new variable.
+ * main.c(main): set/use ksheuid.
+ * misc.c(change_flag): set ksheuid.
+ * c_test.c(test_eval): use ksheuid
+ * c_test.c(test_eaccess): if doing X_OK and user is root, use
+ stat to avoid false positives on files.
+
+Mon Dec 9 12:08:56 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * main.c(main): save/clear/restore FERREXIT flag while processing
+ profile and ENV.
+
+Wed Dec 4 12:25:23 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * misc.c(parse_args): change -A option handling - make getopts
+ gather the option (A: vs A).
+
+Thu Nov 21 15:42:57 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ksh.c(c_alias): accept + options; don't print alias definition
+ if + option used; allow export flag to be cleared; added -p
+ option.
+
+Thu Nov 21 14:35:47 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * tree.c(ptree),c_ksh.c(c_typeset): print ksh functions as
+ "function foo...", sh functions as "foo()...".
+
+ * c_ksh.c(c_typeset): accept -p flag (does nothing).
+
+Wed Nov 20 11:36:08 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ksh.c(c_typeset): simplified option exclusion code.
+
+ * misc.c(ksh_getopt): allow options in same command line to start
+ with either + or - (if appropriate). [code existed to similate
+ ksh88 typeset behaviour which disallowed "typeset +x -i foo"]
+
+Wed Nov 13 12:02:59 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * syn.c(c_list): added multi argument; changed all calls to pass
+ TRUE, except one in yyparse(); changed logic to accept and
+ ignore blank lines if multi flag is set.
+ * syn.c(get_command): removed multiline.on/cf=CONTIN test/assignment.
+ * syn.c(struct multiline_state,struct nesting_state,multiline,nesting,
+ multiline_push,multiline_pop,nesting_push,nesting_pop): renamed
+ *multiline* to *nesting*; removed struct multiline_state.on field
+ (deleted all references).
+
+Mon Nov 4 16:29:50 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_test.c(c_test): in special < 5 arg code: if single arg is
+ -t and not in FPOSIX mode, don't decide its a string test.
+
+Wed Oct 30 11:34:39 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * main.c(include): call quitenv() after shf_close()
+ (fix from Eric J. Chet).
+
+Wed Oct 30 11:23:17 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(comexec): case CFUNC: set $0 to kshname if non-function
+ function.
+
+$OpenBSD: ChangeLog,v 1.16 2013/11/28 10:33:37 sobrado Exp $
+
+Tue Oct 29 11:34:58 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.2.12 distribution
+
+Fri Oct 25 11:59:48 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(vi_cmd): case Cntl('i'): dont fall through, call complete_word().
+
+Tue Oct 22 17:38:21 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * table.h(USERATTRIB): new define.
+ * c_ksh.c(c_typeset): report unset params only if it has some
+ interesting attributes.
+
+Tue Oct 22 15:54:39 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * jobs.c(exchild): changed NEED_PGRP_SYNC code so j_sync_pipe[1] isn't
+ left open in 2nd+ children.
+
+Tue Oct 22 12:59:49 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * main.c(main): memset() env to 0.
+
+Mon Oct 21 12:53:44 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * main.c(cleanup_proc_env): new function.
+ * exec.c(execute): call cleanup_proc_env() before calling ksh_execve().
+
+Fri Oct 11 22:53:57 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(display): use ch not e->buf[cur] when printing character.
+
+Fri Oct 11 13:26:11 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * aclocal.m4(KSH_TIMES_CHECK,KSH_DUP2_CLEXEC_CHECK,KSH_OPENDIR_CHECK):
+ changed sense of test so "yes" result is printed if you have a good
+ system.
+ * aclocal.m4(KSH_C_FUNC_ATTR): changed return type of test_cnst to int.
+
+Fri Oct 11 13:05:40 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * syn.c(get_command): added inalias() call when setting cf = CONTIN.
+
+Thu Oct 10 16:22:03 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.c(getsc__): case SALIAS: if we read eof, break, don't continue.
+
+Tue Oct 8 13:14:00 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * aclocal.m4(KSH_TERM): added SYS_IOCTL_WITH_TERMIOS,
+ SYS_IOCTL_WITH_TERMIO tests.
+ * tty.h: include <sys/ioctl.h> with <termios.h>/<termio.h>
+ if possible.
+
+Tue Oct 8 11:42:36 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.2.11 distribution
+
+Tue Oct 8 11:02:54 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * syn.c(inalias): new function.
+ * syn.c(c_list): call inalias() instead of testing source->type.
+
+Mon Oct 7 17:00:40 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.2.10 distribution
+
+Mon Oct 7 16:23:53 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_sh.c(c_read): when printing prompt, use isatty, not FTALKING.
+
+Wed Oct 2 12:00:51 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.c(yylex): redirection stuff: save result of getsc() == '-'
+ and use it for ungetsc().
+
+ * lex.h(struct source): moved ugbuf out of union so it can be used
+ with alias stuff.
+ * lex.c(getsc__) case SALIAS: instead of appending a space, get the
+ next character and stuff it in ugbuf.
+
+ * lex.c(getsc_,getsc__): getsc_() renamed to getsc__().
+ * lex.c(getsc_,getsc): getsc() macro renamed to getsc_().
+ * lex.c(backslash_skip,ignore_backslash_newline): new variables.
+ * lex.c(getsc): new macro that checks backslash_skip.
+ * lex.c(getsc_bn_,getsc_bn): getsc_bn() macro deleted (all calls
+ replaced with getsc()); getsc_bn_ renamed to getsc_bn.
+ * lex.c(ungetsc_,ungetsc): ungetsc() macro deleted; renamed ungetsc_()
+ to ungetsc().
+ * lex.c(yylex,ungetsc,getsc_bn): set and use backslash_skip,
+ ignore_backslash_newline.
+ * lex.c(yylex): removed special cases for backslash-newline sequence,
+ explicitly ignore backslash followed by eof.
+
+Mon Sep 30 17:14:41 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.2.9 distribution
+
+Mon Sep 30 12:52:21 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.c(pprompt): fixed usage of ntruncate.
+
+Thu Sep 19 17:43:33 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(KSH_SYSTEM_PROFILE): new define.
+ * main.c(main): use KSH_SYSTEM_PROFILE.
+
+ * aclocal.m4(KSH_OS_TYPE): added case for NEXT.
+
+Thu Sep 19 15:39:54 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * tty.c(tty_init): added hack for NeXT's rlogin's missing controlling
+ tty.
+
+Mon Sep 16 11:18:10 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * edit.c(add_glob): don't append a * to a ~username.
+
+ * edit.c(x_init): set got_sigwinch before calling check_sigwinch().
+
+Wed Sep 11 14:38:38 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ksh.c(c_let): ifdef'd KSH.
+ * lex.h(SDPAREN),lex.c: ifdef'd KSH all uses of SDPAREN.
+ * lex.h(MDPAREN),syn.c: ifdef'd KSH all uses of MDPAREN.
+
+Mon Sep 9 16:18:03 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * aclocal.m4(AC_PROG_CC): replaced autoconf's version with
+ modified version.
+
+ * configure.in(clock_t): check in sys/time.h as well.
+ * ksh_times.h: include ksh_time.h.
+ * ksh_time.h,ksh_times.h: added ifndef KSH_TIME_H/KSH_TIMES_H.
+
+Fri Sep 6 13:20:24 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * edit.c(promptlen): X\r hack for delimiting hidden characters
+ in prompt.
+ (Based on fix from Bill Kish)
+
+Tue Sep 3 11:03:26 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * Makefile.in: removed options.h from HDRS (also removed file).
+
+Thu Aug 29 10:04:01 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * aclocal.m4(KSH_MEMMOVE): added return 0 to end of main().
+
+Fri Aug 23 14:23:50 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * aclocal.m4,ksh_stat.h: changed S_IFFIFO to S_IFIFO.
+
+Fri Aug 23 09:58:09 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * var.c(skip_wdvarname): don't check for array if first char
+ isn't [.
+
+Thu Aug 22 12:51:25 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * jobs.c: added ifdef KSH around Coproc_id/j->coproc_id usagae.
+ * c_ksh.c(c_read): added ifdef KSH around opipe.
+
+Tue Aug 20 09:41:32 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * configure.in: fixed quoting of sed LDSTATIC expression.
+
+Mon Aug 19 14:26:08 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.2.8 distribution
+
+Mon Aug 19 11:38:16 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * table.c(texpand): don't free entry if FINUSE is set.
+
+ * var.c(unset): preserve ARRAY and DEFINED if unsetting foo[0].
+
+Thu Aug 15 15:08:52 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * jobs.c(sm_sigchld,sm_default): moved to sh.h.
+ * sh.h(Coproc_id, struct coproc): new typedef; added njobs and
+ id fields to struct coproc.
+ * exec.c(execute): case TCOPROC: re-did coprocess stuff to use
+ njobs/coprocess id.
+ * jobs.c(struct Job): added coproc_id field.
+ * jobs.c(exchild): initialize coproc_id to 0; set job coproc_id
+ and increment coproc.njobs in parent.
+ * jobs.c(checkjob): check coproc_id and close co-process input/output
+ if needed.
+
+ * exec.c(iosetup): only play with coprocess fds if this is an
+ empty exec.
+ * c_sh.c(c_read): commented out coproc_readw_close() call and eof call.
+ * c_ksh.c(c_print): commented out closing coprocess fd on EPIPE.
+
+ * jobs.c(exchild): in parent, last part of job: use orig_flags (not
+ flags) when checking XCOPROC.
+
+Thu Aug 15 15:00:42 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * io.c(get_coproc_fd,cleanup_coproc): renamed to coproc_getfd() and
+ coproc_cleanup(), respecitively; changed all calls.
+
+Tue Aug 13 16:56:59 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * expr.c(O_COMMA,P_COMMA): new enums.
+ * expr.c(evalexpr): added case for O_COMMA.
+
+Tue Aug 13 15:18:28 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * expr.c(do_ppmm): new function to handle ++/--.
+ * expr.c(evalexpr): call do_ppmm() in P_PRIMARY code.
+ * expr.c(LAST_BINOP): deleted.
+ * expr.c(IS_BINOP): new define.
+ * expr.c(evalexpr): use IS_BINOP.
+ * expr.c(O_PLUSPLUS,O_MINUSMUNS,opinfo[]): new enums; updated opinfo
+ * expr.c(ET_LVALUE,ET_RDONLY): new enums.
+
+ * expr.c(token): var code: don't increment cp in iter part of for loop,
+ do it in body; don't correct for off by 1 in array or !noasign code.
+ * table.h(EXPRLVALUE): new define.
+ * expr.c(token): var code: set EXPRLVALUE flag if noassign.
+ * expr.c(intvar): copy temp var if EXPRLVALUE set.
+ * expr.c(assign_check): new function.
+ * expr.c(evalexpr): if assign-op, call assign_check().
+
+Tue Aug 13 11:02:32 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(do_comment),edit.c(x_do_comment): made do_comment generic,
+ renamed and moved to edit.c; changed all calls.
+ * emacs.c(x_ftab[]): added x_comment.
+ * emacs.c(x_defbindings[]): added XFUNC_comment as <esc>#.
+ * emacs.c(x_comment): new function.
+
+Mon Aug 12 16:13:36 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * expr.c(ET_BADVAR): deleted.
+ * expr.c(ET_RECURSIVE, struct expr.evaling),table.h(EXPRNEVAL): added.
+ * expr.c(v_evaluate): if curstate.evaling set, clear EXPRINEVAL.
+ * expr.c(evalerr): added ET_RECURSIVE case, removed ET_BADVAR case.
+ * expr.c(intvar): do recursion check, call v_evaluate() on value.
+
+Mon Aug 12 14:25:23 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * io.c(coproc_read_close): call coproc_readw_close() instead of
+ duplicating code.
+
+Mon Aug 12 11:21:39 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * edit.c(x_locate_word): changed to allow at most 1 leading blank
+ before the word.
+ * edit.c(x_file_glob,x_command_glob,add_glob): allow zero length word.
+ * edit.c(x_cf_glob): allow zero length globs on when doing file
+ completion.
+
+ * edit.c(x_complete_word): #if 0 - it isn't used...
+ * edit.c(x_file_glob,x_command_glob,x_locate_word): made static.
+
+ * eval.c(varsub): changed FNOUNSET error from "unset variable"
+ to "parameter no set", ala at&t ksh.
+
+ * c_ksh.c(c_typeset): print variables that aren't set (just
+ leave out the =...).
+
+Mon Aug 12 11:03:22 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(findfunc): removed redundant DEFINED check after tsearch().
+
+Fri Aug 9 22:16:21 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * jobs.c(j_change): when turning off FMONITOR and not FTALKING,
+ changed SS_RESTORE_CURR to SS_RESTORE_ORIG.
+
+ * edit.c(x_sigwinch): new function.
+ * edit.c(x_init): set up signal handler for SIGWINCH; moved
+ code to get window size into x_sigwinch(); call x_sigwinch().
+ * emacs.c(xx_cols): new variable.
+ * emacs.c(x_init): set xx_cols_to x_cols; change all uses of x_cols
+ to xx_cols.
+ * vi.c(display): when displaying morec, changed x_cols-2 to
+ pwidth+winwidth+1.
+
+Fri Aug 9 12:49:00 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * table.h(FKSH): new define.
+ * tree.h(struct op): put evalflags into new union u, added ksh_func
+ to union; changed all uses of evalflags.
+ * syn.c(function_body): set u.ksh_func.
+ * exec.c(execute): changed define() arg to t (was t->left).
+ * exec.c(define): copy t->left (was t); set FKSH in flag if is
+ a ksh function.
+ * exec.c(comexec): don't keep assignments for x() style functions.
+ * exec.c(comexec: case CFUNC: set kshname ($0) for ksh style functions
+ only (was FPOSIX).
+
+ * exec.c(execute): case TAND/TOR: pass XERROK on when executing right
+ hand side.
+
+ * jobs.c(exchild): deleted redundant code to set j->flags
+ (near new_job() call).
+
+ * sh.h(ksh_tmout),main.c(alarm_init),trap.c(alarm_init,alarm_catcher):
+ ifdef'd KSH.
+
+ * sh.h(SS_SHTRAP,Trap.shtrap): added.
+ * trap.c(trapsig): if shtrap is non-zero, call it.
+ * trap.c(setsig): set shtrap if SS_SHTRAP set.
+ * jobs.c(j_init),trap.c(alarm_init): pass SS_SHTRAP.
+ * jobs.c(j_sigchld),trap.c(alarm_catcher): don't call trapsig().
+ * trap.c(Sigact_alarm): removed.
+
+Thu Aug 8 15:57:14 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(comexec): case CEXEC: print cannot execute error only
+ if / in pathname; also, set exit code to 126.
+
+ * exec.c(do_selectargs): added print_menu arg; only print
+ menu if this is set, or if REPLY is null; removed "while isspace"
+ loop.
+ * exec.c(execute): case TSELECT: call do_selectargs with print_menu
+ of TRUE on first call only.
+
+ * exec.c(define): added was_set variable and logic.
+ * c_sh.c(c_unset): return 1 if variable/function to be unset wasn't
+ set to begin with.
+
+Wed Jul 31 10:33:00 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(Tflag): new type.
+ * sh.h(builtin_flag): changed type to Tflag.
+ * table.h(struct tbl): changed type of flag field to Tflag.
+ * c_ksh.c(typeset): changed type of flag, fset, fclr to Tflag.
+ * c_ksh.c(c_alias): changed type of xflag to Tflag.
+ * exec.c(comexec): changed type of old_inuse to Tflag.
+ * exec.c(builtin): changed type of flag to Tflag.
+ * var.c(typeset): changed set, clr args to Tflag; convert second
+ arg of call to local() to boolean.
+
+Wed Jul 31 10:26:25 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(C_QUOTE): new define.
+ * sh.h(ctypes[]),misc.c(ctypes[]): changed from char to short.
+ * misc.c(initctypes): set C_QUOTE bits in ctypes[].
+ * misc.c(print_value_quoted): use C_QUOTE.
+
+Mon Jul 29 11:38:36 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.c(set_prompt): don't print warning message if setjmp returns
+ non-zero.
+
+Fri Jul 26 10:16:27 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.c(set_prompt): don't do ! and parameter expansion if !KSH.
+
+ * table.h(V_MAIL,V_MAILPATH,V_MAILCHECK): ifdef KSH.
+ * var.c(initvar,setspec,unsetspec): ifdef KSH use of MAIL stuff.
+ * mail.c: ifdef KSH whole file.
+ * main.c(shell): ifdef KSH call to mcheck().
+ * main.c(initcoms[]): ifdef KSH the MAILCHECK=600.
+ (based on patches from Marc Olzheim).
+
+ * exec.c(PS4_SUBSTITUTE): new macro.
+ * exec.c(execute, comexec, iosetup): use PS4_SUBSTITUTE.
+
+Thu Jul 25 17:19:17 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(F_VIESCCOMPLETE): new define.
+ * misc.c(options[]): added vi-esccomplete.
+ * vi.c(classify[]): make ^[ a repeatable command.
+ * vi.c(vi_cmd): check F_VIESCCOMPLETE for ^[.
+
+Mon Jul 22 16:54:38 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ksh.c(c_getopts): return if variable is readonly; don't change
+ OPTIND if option is bad (fragile).
+ * c_sh.c(c_brkcont): use ksh_getopt(); changed error message if
+ n <= 0.
+ * c_sh.c(c_dot,c_eval,c_exitreturn): use ksh_getopt().
+ * misc.c(ksh_getopt): print `unknown option' instead of `bad option'.
+
+Mon Jul 22 16:08:40 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * edit.c(x_init): do NOT export COLUMNS/LINES - causes more problems
+ than it fixes.
+
+Mon Jul 22 15:49:35 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * syn.c(get_command): fixed test for '< foo (command)' so it
+ works.
+
+Fri Jun 21 09:57:47 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * aclocal.m4(KSH_OPENDIR_CHECK): include dirent.h if HAVE_DIRENT_H
+ defined (was DIRENT || _POSIX_VERSION).
+ * aclocal.m4(KSH_UNISTD_H): don't test HAVE_DIRENT_H when including
+ dirent.h.
+
+Wed Jun 12 11:02:32 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_test.c(b_ops[]): added "==" entry (ksh93ism).
+
+Mon Jun 10 14:00:21 1996 Michael Rendell (michael@lyman.cs.mun.ca)
+
+ * ksh_stat.h: undef S_ISSOCK if STAT_MACROS_BROKEN defined.
+ * aclocal.m4(AC_HEADER_STAT): redefine autoconf's version to handle
+ FreeBSD's S_ISSOCK.
+
+Tue Jun 4 08:41:19 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.2.7 distribution
+
+ * vi.c(CMDLEN): changed from 16 back to 1024.
+
+Sun Jun 2 11:54:46 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.2.6 distribution
+
+Sun Jun 2 11:46:56 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(search_access): changed ordering of xsuffixes[], rsuffixes[];
+ removed code that used xsuffixes[] when suffix is present.
+ * lex.c(getsc_line): set O_TEXT/O_BINARY if os/2.
+ * main.c(remove_temps): added os2 ifdefs.
+ [Changes from Dale DePriest.]
+
+Tue May 21 14:18:22 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(vi_cmd): case '#': call do_comment() to do work.
+ * vi.c(do_comment): new function.
+ * vi.c(putbuf,grabhist,grabsearch): fixed pesimestic off-by-1 error
+ (cbufsize - 1 -> cbufsize).
+ * vi.c(vi_hook): case VCMD: case -1: added refresh(0).
+ * vi.c(vi_cmd): case 'P': don't move cursor back if nothing added.
+
+Tue May 21 12:03:34 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * emacs.c(do_complete): don't add space if single match and
+ it doesn't end with a /.
+
+Tue May 21 11:51:36 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * edit.c(x_init): use typeset to set EXPORT attribute for
+ COLUMNS/LINES.
+
+Tue May 21 11:40:12 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * misc.c(parseargs): option setting: ignore context if option
+ isn't being changed.
+ * misc.c(printoptions): for non-verbose mode: print a set command
+ (eg, set -o vi -o ...) instead of just the option names.
+
+Tue May 21 11:14:27 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_sh.c(c_brkcont): if n is too big, use last enclosing loop.
+
+Fri May 10 09:27:47 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(Getopt): changed field p from int to unsigned.
+
+Tue May 7 12:10:47 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.2.5 distribution
+
+Tue May 7 11:45:37 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * syn.c(compile): set multiline if source is SSTRING.
+ * syn.c(yyparse): don't peek before calling c_list() - build
+ TEOF if c_list() fails and c is 0.
+ * syn.c(c_list): remove SSTRING test.
+ * syn.c(get_command): if EOF is reached, free iops,args,vars.
+ * syn.c(syntaxerr): set multiline.on to false when it is used;
+ don't use multiline.on if start token is 0.
+
+Tue May 7 10:11:41 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * history.c(c_fc,hist_execute): moved calls to histbackup() from
+ c_fc() to hist_execute().
+ * history.c(hist_get): number: took out +1 correction as histbackup
+ hasn't been done yet; string: added -1 correction to ensure
+ current fc command isn't searched.
+ * history.c(hist_get_newest,hist_get_oldest): don't find the
+ current (fc) command; removed print_err argument (was always
+ true).
+ * history.c(hist_get,hist_get_newest): added allow_cur argument;
+ changed all calls.
+
+Mon May 6 09:55:29 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * emacs.c(x_nextcmdp): renamed to x_nextcmd, changed from
+ char ** to int.
+ * emacs.c(x_nl_next_com): save absolute command number, not
+ relative position in history array (which changes).
+ * emacs.c(x_emacs): convert x_nextcmd back to relative position.
+ * emacs.c(x_init_emacs): initialize x_nextcmd to -1.
+
+Sun May 5 13:10:48 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * expr.c(evalexpr): when assigning a non-integer, call setint()
+ (not setstr(..., strval(...))).
+
+Sun May 5 12:16:11 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * mail.c(maddmsg): changed name to mprintit(); now prints message
+ directly instead of saving in a linked list; changed all calls.
+ * mail.c(mprint): deleted; deleted all calls.
+ * mail.c(mmsgs,struct mailmsg): deleted.
+
+Sun May 5 11:52:05 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.h(SF_TTY): new flag.
+ * lex.h(STTY): deleted.
+ * main.c(main): if tty, use SSTDIN, set SF_TTY.
+ * main.c(shell): check SF_TTY instead of STTY.
+ * lex.c(getsc_): call getsc_line for SSTDIN/SFILE.
+ * lex.c(getsc_line): new function (merged old STTY/SSTDIN/SFILE code).
+
+Fri May 3 11:24:17 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * main.c(shell): changed exit_atend to toplevel. Changed interactive
+ to be falking&toplevel (was talking&s->type==STTY).
+
+Fri May 3 10:59:22 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * var.c(getint): only allow one base (ie, disallow 2#4#5).
+
+Thu May 2 21:31:23 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * var.c(array_index_calc): new function
+ * var.c(global): call array_index_calc(); moved $2 code into
+ if (!letter(c))...
+ * var.c(local): call array_index_calc(); added copy argument & code;
+ changed all calls.
+ * table.h(LOCAL_COPY): new define.
+ * exec.c(comexec): maybe pass LOCAL_COPY to typeset().
+
+Thu May 2 16:34:29 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * emacs.c: command completion changes.
+ * emacs.c(Comp_type,CT_LIST,CT_COMPLETE,CT_COMPLIST): new type.
+ * emacs.c(x_ins): return type changed to int; return -1 if
+ string can't be inserted.
+ * emacs.c(x_do_ins): new function.
+ * emacs.c(add_stash,list_stash,compl_dec,compl_file,compl_command,
+ str_match): deleted; changed callers to use do_complete().
+ * emacs.c(do_complete,x_expand): new functions.
+ * emacs.c(x_ftab[],x_defbindings[]): added entry for file-expand;
+ bound to <ESC>*.
+
+Thu May 2 15:31:32 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.c(set_prompt): pass strlen() + 1 to shf_sopen.
+ (fix from Arnon Kanfi).
+
+Wed Apr 24 11:50:52 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * history.c(c_fc): -e -: don't increment wp past null; allow
+ pat=replace arg with "-1" type argument.
+ (based on fix from Jason Tyler).
+
+Mon Apr 15 11:58:34 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * table.c(tenter),alloc.c(alloc): changed use of offsetof() so field
+ parameter is a constant expression.
+ * sh.h: took out undef of offsetof on CRAYs.
+
+Fri Apr 12 16:01:40 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * jobs.c(JF_USETTYMODE): renamed JR_ORIGFG to JF_USETTYMODE.
+ * jobs.c(j_waitj): clear JF_USETTYMODE if fg job is stopped.
+
+Sun Apr 7 12:35:30 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ksh(c_print): echo: don't treat a lone minus as an option.
+
+Sat Apr 6 00:09:37 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ulimit.c(c_ulimit.c): always pass 2 args to ulimit().
+ * ksh_sigsetjmp(): changed all uses to be simple expressions - seems
+ to be required by the cray C compiler.
+ * sh.h(offsetof): undef if on a cray.
+ (based on fixes from Dave Kinchlea)
+
+Sat Mar 23 13:58:12 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * siglist.in: added WAITING,LWP,FREEZE,THAW,CANCEL
+
+Thu Mar 7 23:26:37 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * edit.c(x_init): set LINES if possible.
+
+Thu Mar 7 23:01:55 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * main.c(main): call x_init() after j_init()
+ (based on fix from Stefan Dalibor).
+
+Thu Mar 7 16:13:10 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * aclocal.m4(KSH_OS_TYPE): check for TitanOS (use cc -43).
+ * aclocal.m4(KSH_SIGNAL_TYPE): for bsd41 signals, check if signal
+ interrupt read().
+
+Thu Mar 7 13:59:29 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(strstr),missing.c(strstr): changed args to const.
+
+Wed Mar 6 17:21:36 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * io.c(errorf,bi_errorf): changed null pointer string check to
+ empty string; changed all calls (due to new error gcc warnings).
+
+Wed Mar 6 17:15:58 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(search_access): files aren't executable if they don't
+ have any execute bits.
+ * ksh_stat.h: added S_IXUSR,S_IXGRP,S_IXOTH.
+ * exec.c(search_access,search_access1): OS2: changed the meaning
+ of these two functions (search_access1 now called from search_access).
+
+Wed Mar 6 16:23:23 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * tree.c(ptree): add case for TSELECT.
+
+Wed Mar 6 12:40:34 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(Z_,is_zeroarg): new defines.
+ * vi.c(classify): use Z_ for G, g, _, |, v, ^I, ^F.
+ * vi.c(vi_cmd): use is_zerocount().
+ * vi.c(complete_word): if command prefixed by a count, complete
+ to count'th expansion (as reported by print_expansions()).
+
+Tue Mar 5 14:43:48 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * eval.c(GF_NONE,GF_EXCHECK,GF_GLOBBED,GF_MARKDIR): new defines.
+ * eval.c(glob_str): added markdirs argument; changed all calls;
+ made function non-static.
+ * eval.c(glob): added markdirs argument; changed all calls.
+ * tree.h(DOMARKDIRS): new define.
+ * eval.c(expand): set DOMARKDIRS if FMARKDIRS.
+ * edit.c(x_complete_word,x_print_expansions,x_file_glob,x_command_glob,
+ x_locate_word,x_cf_glob,x_add_glob,x_longest_prefix,x_free_words):
+ new functions.
+ * proto,edit.h: moved functions defined in edit.c to edit.h.
+ * vi.c(struct edstate): moved to top of file.
+ * vi.c(print_expansions): added struct edstate argument; changed all
+ calls.
+ * vi.c(struct glob,Glob,globstr,glob_word,): deleted
+ * vi.c(vi_pprompt): new function; changed all calls of pprompt() in
+ vi.c to use vi_pprompt().
+ * vi.c(x_vi): moved to top of file.
+ * vi.c(expand_word,complete_word): free buf if it is not null.
+ * vi.c(expand_word,complete_word,print_expansions): changed
+ to use new edit.c functions.
+
+Tue Feb 20 11:02:05 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * table.c(twalk,tnext,struct tstate),table.h(struct tstate): moved
+ struct tstate from table.c to table.h; changed twalk,tnext to take
+ struct tstate* argument; changed all calls; deleted static tstate
+ variable.
+
+Sat Feb 17 12:28:11 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(vi_hook): case VSEARCH: if new pattern is empty, repeat last
+ search.
+
+Sat Feb 10 15:59:28 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * table.h(struct arg_info): new struct.
+ * table.h(struct block): changed argv, argc fields to argi.
+
+Sat Feb 10 15:12:47 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ ANSI C name space requirements:
+
+ * vi.c(isbad,iscmd,islong,ismove,issrch,isundoable,iswordch): changed
+ to is_bad,is_cmd,is_long,is_move,is_srch,is_undoable,is_wordch.
+ * emacs.c(iscfs,ismfs): changed to is_cfs, is_mfs.
+ * emacs.c(strmatch): changed to str_match.
+ * sh.h(strchr_dirsep,strrchr_dirsep): changed to ksh_strchr_dirsep,
+ ksh_strtchr_dirsep; changed all calls.
+ * missing.c(strichars[]): changed to ichars[].
+ * var.c(strint,strval): changed to setint_v, str_val.
+ * missing.c(strsave,strnsave): changed to str_save,str_nsave.
+
+Fri Feb 9 11:30:15 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * main.c(main): remove envp parameter; declare and use environ.
+
+ * c_ksh.c(c_print): octal digit escape sequences must start with \0.
+
+Sat Feb 3 15:35:41 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(vi_cmd,classify[]): made ^I a command.
+
+Fri Feb 2 10:40:32 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.h(struct source): added u.freeme field.
+ * lex.c(getsc_): case SREREAD: free u.freeme iff start isn't u.ugbuf.
+
+Thu Feb 1 15:27:06 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_test.h(Test_env): added end union.
+ * c_test.c(c_test): keep track of end position using end.wp;
+ don't write on wp.
+
+ * emacs.c(x_mapin): changed to dup string, then munge; return duped;
+ changed all calls.
+
+ * eval.c(homedir): deleted getpwnam() declaration - can't believe
+ its needed anywhere (we shall see, though).
+
+ * sh.h(handler_t): use ARGS for prototype; use h
+ * sh.h(struct trap),trap.c(setsig,settrap),sigact.c,sigact.h:
+ use handler_t.
+ * history.c,c_sh.c,c_ksh.c: removed register declaration from
+ c_*() functions.
+ * exec.c(builtin),proto.h(builtin): use prototype for func.
+ * misc.c(qsortp,qsort1),proto.h(qsortp): use prototype for f.
+
+ * c_ksh.c(ksh_getopt): made options arg const.
+ * tree.c(fptreef,snptreef,vfptreef): made fmt arg const.
+ * jobs.c(waitfor,j_kill,j_resume,j_lookup,j_jobs): made cp arg const.
+ * shf.c(shf_snprintf,shf_smprintf,shf_vfprintf): made fmt arg const.
+ * c_test.h(Test_env.error),c_test.c(ptest_error): made msg arg const.
+ * c_test.c(test_stat,test_eaccess): made path arg const.
+ * c_test.c(ptest_getopnd,dbteste_getopnd): made return value const.
+ * c_test.c(ptest_eval,test_eval,dbteste_eval,dbtestp_eval,test_primary):
+ made opnd1,opnd2 arg const.
+ * c_test.c(test_isop): made s arg const.
+
+ * misc.c(bi_getn,getn): made as arg const.
+ * misc.c(getn): made as arg const.
+ * misc.c(gmatch): made s/p arg const.
+ * misc.c(has_globbing): made xp/xpe arg const.
+ * misc.c(do_gmatch): made s/p/se/pe arg const.
+ * misc.c(cclass): made p arg const.
+
+Thu Feb 1 14:54:32 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * edit.h, sh.h, tty.h: changed _I_ to I__.
+ * edit.h, edit.c: changed _D_ to D__.
+
+ * jobs.c,shf.c,tty.c: include ksh_stat.h (POSIX: needed for open).
+
+ * sigact.c: use ARGS instead of __P; comment out __P defines.
+
+ * shf.c: include math.h if FP.
+ * shf.c(my_ceil): remove modf() declaration.
+ * shf.c(shf_fvprintf): comment out frexp() declaration; changed
+ exp to expo.
+
+ * jobs.c(struct job, j_utime, j_stime): changed utime/stime to
+ usrtime/systime; change j_utime/j_stime to j_usrtime/j_systime.
+
+Wed Jan 31 16:13:44 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * edit.c(x_getc): cast return value to int to avoid warnings on
+ strange compilers.
+ * exec.c(funcfunc): changed second arg to unsigned int (was int).
+ * syn.c(elsepart): move return NULL to end of function (avoids
+ warning from some compilers).
+ * vi.c(classify[]): changed type to unsigned char.
+ * shf.c(shf_smprintf): delete unused variable n.
+ * aclocal.m4(KSH_TIMES_CHECK): define INT32 in test code.
+ * aclocal.m4(KSH_SIGNAL_CHECK): typeo: had bsd42 instead of bsd41.
+ * sh.h(MAGIC): changed to 7 to increase portability.
+ * jobs.c(tcsetpgrp,tcgetpgrp): define if TTY_PGRP (was TIOCSPGRP).
+
+Tue Jan 23 11:40:25 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(ksh_jmp_buf): new define.
+
+Thu Jan 18 15:03:19 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * history.c(hist_replace): fixed substitution code (again).
+
+Wed Jan 17 20:10:02 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.2.4 distribution
+
+ * main.c(initcoms): changed hash alias to "hash=alias -t".
+
+ * exec.c(do_selectargs): deleted c_read() declaration.
+
+ * c_ksh(c_alias): call ksh_getopt_reset() before calling c_unalias().
+
+Wed Jan 17 19:47:55 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * history.c(histbackup): changed "histptr > history"
+ to "histptr >= history".
+
+ * history.c(hist_replace): removed un-needed "last" - use "s" instead.
+ (based on fix from Jason Tyler).
+
+Thu Jan 11 15:59:46 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ksh.c(c_whence,c_command),main.c(initcoms[]): removed ifdef KSH
+ (type is a builtin in sys-5 sh).
+
+Wed Jan 10 11:49:59 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * Makefile.in: added NEWS.os2 to OS2FILES.
+
+ * version.c: include "sh.h" (needed for const define).
+
+ * exec.c(pr_menu): made non-static.
+ * vi.c(print_expansions): gather expansions into an arrat
+ and use pr_menu().
+ (fixes from Mike Jetzer).
+
+ * vi.c(redraw_line): added newline option; changed all calls.
+
+Wed Jan 10 10:21:06 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(classify): made 'U' a C_.
+ * vi.c(ohnum): new variable.
+ * vi.c(vi_reset): set ohnum to hlast.
+ * vi.c(grabhist): set ohnum.
+ * vi.c(vi_cmd): case n,N,/,? set ohnum; added case 'U'.
+ * vi.c(edit_reset): clear holdlen.
+ (based on fix from Dale DePriest).
+
+Tue Jan 9 11:23:36 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * emacs.c(iscfs): make ', " separators.
+ (fix from Dale DePriest).
+
+ * conf-end.h: deleted stuff to undef HISTORY, VI, EMACS, etc if
+ KSH wasn't defined (now done in configure).
+
+ * sh.h(GI_NONAME): changed to GF_NONAME; changed all uses.
+
+ * configure.in: added AC_ARG_PROGRAM.
+ * Makefile.in: replaced binprefix and manprefix with
+ program_transform stuff.
+
+Mon Jan 8 11:42:46 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(struct temp): added shf field.
+ * io.c(maketemp): changed to use O_EXCL; keep trying if open
+ fails (due to O_EXCL); fill in shf field; changed all calls.
+
+ * main.c(include): added intr_ok flag; changed all calls.
+
+ * main.c(main): if compiled as sh and posix option not set, do not
+ include $ENV.
+
+ * trap.c: define FROM_TRAP_C before including sh.h.
+ * sh.h: don't declare sigtraps if FROM_TRAP_C declared.
+
+ * c_ksh.c(c_cd): fixed error message.
+ * vi.c(glob_word): don't add * if word contains a $.
+ (Based on fixes from Mike Jetzer).
+
+ * eval.c(tilde): if HOME,PWD,OLDPWD aren't set, don't expand
+ ~,~+/~-.
+
+Fri Jan 5 12:15:58 NST 1996 Michael Rendell (michael@garfield.cs.mun.ca)
+
+ * c_ksh.c(c_typeset): separate loop for printing functions
+ (do not traverse array link).
+ * c_ksh.c(c_typeset): list functions: do not ignore unset functions.
+ * exec.c(findfunc): set val.t to 0 when creating new entry.
+ * exec.c(define): if FINUSE, use tail recursion.
+
+Thu Jan 4 11:10:22 NST 1996 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(globstr): deleted ifdef'd out code.
+
+Sun Dec 10 11:07:52 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.c(yylex): added case for STBRACE; wrap word part of
+ trim substitution in @(..).
+ * eval.c(trimsub): deleted code to wrap pattern in @(..); changed
+ '%' code to use strnsave().
+
+Fri Dec 8 22:55:56 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * eval.c(trimsub): if trim pattern contains a |, wrap pattern
+ in @(...).
+ * lex.c(yylex): make | special when incounted in a ${...}
+ substitution.
+
+Fri Dec 8 11:52:38 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * var.c: ifdef'd HISTFILE, HISTSIZE stuff with HISTORY (was KSH).
+
+ * *.c,*.h: ifdef'd coprocess stuff with KSH.
+
+Thu Dec 7 14:41:06 NST 1995 Michael Rendell (michael@angel.cs.mun.ca)
+
+ * options.h(BRACEEXPAND): changed to BRACE_EXPAND; changed all
+ references.
+
+Thu Dec 7 13:54:20 NST 1995 Michael Rendell (michael@angel.cs.mun.ca)
+
+ * exec.c(do_selectargs): don't print newline on eof.
+
+Thu Dec 7 10:23:30 NST 1995 Michael Rendell (michael@angel.cs.mun.ca)
+
+ * c_ksh.c(c_print): added -f for OS2.
+ * tree.h(DODIRSWP),eval.c: deleted define and all uses of it.
+ * exec.c(scriptexec): ...
+ * io.c(check_fd): set O_TEXT/O_BINARY flag for OS2.
+ * main.c(main): set O_BINARY/O_TEXT, search path for arg.
+ * emacs.c(compl_file): call opendir with buf, not dirnam.
+ (based on changes from Dale DePriest).
+
+Wed Nov 29 15:50:36 NST 1995 Michael Rendell (michael@angel.cs.mun.ca)
+
+ * eval.c(expand,debunk): handle extended pattern matching stuff.
+ * eval.c(debunk): now has two arguments, changed all calls.
+ * eval.c(globit): changed to use has_globbing.
+ * eval.c(copy_non_glob): deleted.
+
+ * misc.c(has_globbing): new function.
+ * misc.c(cclass): changed argument to unsigned char *; handle
+ extended pattern matching.
+ * misc.c(do_gmatch): new function (taken from gmatch()).
+ * misc.c(gmatch): changed to call do_gmatch.
+ * misc.c(do_gmatch): added cases for extended pattern matching
+ (*(foo|bar), etc.).
+ * misc.c(pat_scan): new function.
+
+ * lex.c(yylex): added SPATTERN case.
+
+ * lex.c(arraysub): changed to assume just past the leading [
+ (was assuming about to read [); changed all calls; changed
+ to use getsc_bn().
+
+ * lex.c(ungetsc): added argument; changed all calls; can now unget
+ arbitrary number of characters.
+ * lex.c(ungetsc_): new function.
+
+ * lex.h(struct source): added start field, removed u.start field,
+ changed all uses.
+ * lex.c(getsc_): case STTY: skip blank line only if this is first line
+ of a command (eg, not part of here documennt, etc.).
+
+ * lex.c(yylex): case SHEREDELIM,SHEREDQUOTE: ignore \newline.
+ * lex.c(readhere,get_brace_var): ignore \newline.
+ * lex.c(getsc_bn,getsc_bn_): new define/function.
+
+ * exec.c(iosetup): don't enforce noclobber for non-regular files.
+
+ * tree.h(OPAT,SPAT,CPAT): new defines.
+ * tree.c(tputS,wdscan): added cases for OPAT,SPAT,CPAT.
+
+ * lex.c(yylex): moved case '[' from Subst: switch to case SBASE:.
+
+Tue Nov 14 11:00:48 NST 1995 Michael Rendell (michael@angel.cs.mun.ca)
+
+ * syn.c(get_command,caselist): moved parsing of IN/ESAC into
+ caselist; allow {/} instead of IN/ESAC;
+ * syn.c(casepart): new parameter: endtok.
+ * lex.c(yylex): allow } as well as ESAC when ESACONLY set.
+ (changes based on fix from DaviD W. Sanderson).
+
+Tue Nov 14 10:22:17 NST 1995 Michael Rendell (michael@angel.cs.mun.ca)
+
+ * main.c(shell): do not zero exstat at start of routine.
+
+ * exec.c(execute): removed redundant "exstat = rv" before
+ unwind(LERROR).
+
+Thu Nov 9 15:01:54 NST 1995 Michael Rendell (michael@angel.cs.mun.ca)
+
+ * var.c(arrayname): made argument const.
+ * var.c(typeset): made var argument const.
+ * var.c(export): made val argument const.
+ * tree.c(wdscan): changed return type to non-const (added casts).
+
+Thu Nov 9 14:39:49 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ksh.c(c_alias),c_sh.c(c_set): made args[] array const.
+ * c_ulimt.c(c_ulimit): made limits[] array const.
+ * edit.c(x_mode): x_cur_mode no longer explicitly initialized to 0.
+ * emacs.c(x_tab,x_atab): no longer explicitly initialized to 0.
+ * exec.c(comexec): made texec non-static, non-initialized.
+ * history.c(hist_finish): once no longer explicitly initialized to 0.
+ * io.c(maketemp): io no longer explicitly initialized to 0.
+ * jobs.c(job_list,last_job,async_job,free_jobs,free_procs): no longer
+ explicitly initialized to 0.
+ * jobs.c(lookup_msgs[],tt_sigs[]): made array const.
+ * mail.c(mplist,mbox,mlastchkd,mmsgs): no longer explicitly
+ initialized to 0.
+ * vi.c(expand_word,complete_word): buf no longer explicitly
+ initialized to 0.
+ * vi.c(classify[]): made array const.
+
+Tue Nov 7 11:08:01 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * mkman: new script
+ * Makefile.in: use mkman to generate ksh.1
+ * ksh.Man,ksh.1: renamed ksh.1 to ksh.Man
+ * ksh.Man: changed way sh/ksh option handled.
+ (changes based on fix from Michael Haardt).
+
+Tue Sep 19 09:53:53 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * jobs.c(j_stopped): deleted function.
+ * jobs.c(j_exit): send SIGCONT, then SIGHUP; send SIGHUP if
+ job is in foreground.
+ (based on fix from Paul Borman)
+
+ * Makefile.in: move .PRECIOUS to after all.
+
+Wed Sep 13 15:00:22 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(dbteste_getopnd): changed tests from TO_STLT/TO_STGT
+ to TO_STEQL/TO_STNEQ.
+
+Thu Aug 31 11:54:02 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * jobs.c(exchild): if fork fails, allow user to ^C out of loop.
+
+Tue Aug 29 09:40:37 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(iosetup): don't do globing if not interactive (POSIX).
+
+ * exec.c(iosetup): print <& or >& as appropriate in error message.
+
+ * tree.h(IONAMEXP): new define.
+ * tree.c(pioact): handle IONAMEXP.
+ * exec.c(iosetup): set IONAMEXP.
+
+ * io.c(savefd): added noclose parameter; changed all calls.
+ * exec.c(iosetup): move call to savefd() to after the open();
+ re-arranged the dup'ing (failed dups reported).
+
+ * main.c(shell): call quitenv() before internal_error().
+
+Sun Aug 13 21:38:44 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(ksh_sigsetjmp,ksh_siglongjmp): new defines; changed
+ all uses of setjmp/longjmp to these.
+ * configure.in: added checks for sigsetjmp() and _setjmp().
+
+Wed Jul 26 10:08:23 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ulimit.c(c_ulimit): added -p ("maxproc", RLIMIT_NPROC)
+ (fix from Simon J. Gerraty).
+
+Thu Jun 29 10:22:51 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * edit.c(promptlen): added spp parameter; changed all calls.
+ * vi.c(prompt_skip): new variable.
+ * vi.c(edit_reset): set prompt_skip; use prompt_skip in all calls
+ to pprompt().
+
+Sat Jun 24 15:55:03 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * IAFA-PACKAGE: new file.
+ * Makefile.in: added IAFA-PACKAGE to DISTFILES.
+
+Mon Jun 19 10:04:52 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * main.c(initcoms[]): added EXTRA_INITCOMS.
+
+Fri Jun 16 12:33:10 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(search_access1): use FILECMP() instead of strcmp().
+
+ * sh.h(FIELCHCONV): OS2 version: added isascii().
+ * misc.c(gmatch); took unsigned out again for sc and pc.
+
+ * main.c(main): don't set PS1 if it's already set; set it if
+ we are root and prompt doesn't contain a #.
+
diff --git a/ChangeLog.0 b/ChangeLog.0
@@ -0,0 +1,3589 @@
+$OpenBSD: ChangeLog.0,v 1.5 2013/11/28 10:33:37 sobrado Exp $
+
+Thu Jun 15 11:02:06 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.2.3 distribution
+
+ * c_ksh.c(c_whence): search keyword table if vflag set.
+
+ * tree.h(DOVACHECK): new define.
+ * eval.c(expand): check DOVACHECK flag.
+ * exec.c(execute): when calling eval(), or in t->evalflags.
+ * syn.c(get_command): set evalflags to DOVACHECK instead of DOASNTILDE.
+
+Wed Jun 14 09:27:19 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ksh.c(c_cd): two argument format: use current_wd, not path
+ when appending elen bytes.
+ (fix from Gabor Zahemszky).
+
+Tue Jun 13 15:54:11 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * history.c(c_fc): if last not specified and !-l, use first as last.
+
+ * eval.c(maybe_expand_tilde): allow CSUBST to end tilde word.
+
+ * misc.c(gmatch): made sc and pc unsigned.
+
+Fri Jun 2 11:55:40 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * configure.in: added flock to AC_CHECK_FUNCS call.
+ * conf-end.h: undef COMPLEX_HISTORY if !HAVE_FLOCK.
+
+Tue May 30 20:38:47 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(SEEK_SET,SEEK_CUR,SEEK_END): define if not defined.
+ * history.c: change L_XTND to SEEK_END.
+
+Tue May 30 17:01:34 NDT 1995 John Rochester (jr@panda.cs.mun.ca)
+
+ * shf.c(shf_seek): new function.
+ * shf.h(shf_seek): new prototype.
+
+Tue May 30 16:42:41 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * aclocal.m4(KSH_DEV_FD): new test.
+ * acconfig.h(HAVE_DEV_FD): new define.
+ * configure.in: call KSH_DEV_FD.
+
+ * c_test.h(TO_FILAXST): new enum.
+ * c_test.c(test_stat,test_eaccess): new functions for /dev/fd/n
+ handling.
+ * c_test.c(test_evalop): call test_stat() and test_eaccess()
+ instead of stat() and eaccess() in most places; added case
+ for TO_FILAXST.
+
+Tue May 30 16:06:21 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * aclocal.m4(KSH_MEMMOVE): fixed test so copies overlap.
+
+Sun May 28 11:11:03 NDT 1995 John Rochester (jr@panda.cs.mun.ca)
+
+ * sh.h(safe_prompt): new variable.
+ * main.c(initsubs): removed PS1.
+ * main.c(main): initialize safe_prompt; initialize PS1 from
+ safe_prompt.
+ * lex.c(set_prompt): create new env while expanding PS1 - if expansion
+ fails, use safe_prompt.
+
+Sat May 27 20:59:02 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c: put comments around token after #endif.
+
+Thu May 25 10:10:45 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_test.c(test_eval): case TO_OPTION: negate test if option starts
+ with a !, always fail if option doesn't exist.
+
+ * sh.h(FNOHUP): new define.
+ * misc.c(options[]): "nohup" new option.
+ * jobs.c(j_stopped,j_stopped_running): name of j_stopped changed
+ to j_stopped_running; changed all calls; check for/warn about
+ running jobs if appropriate.
+ * jobs.c(j_exit): check for/kill running jobs if appropriate.
+ * main.c(shell),c_sh.c(c_exit): un-ifdef JOBS the j_stopped_running()
+ call and really_exit initialization/clearing.
+
+Wed May 24 10:06:14 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * options.h(DEFAULT_ENV): new define.
+ * main.c(main): if ENV isn't set and DEFAULT_ENV is defined, include
+ the later.
+ (based on patches from Dave Kinchlea).
+
+ * sh.h(LAEXPR): new define.
+ * expr.c(evaluate): changed return type to error indicator; added
+ rval and error_ok arguments; changed all calls (c_sh.c(c_shift),
+ c_ulimit.c(c_ulimit),eval.c(expand),var.c(global,local)).
+ * expr.c(v_evaluate): added error_ok argument; changed return value
+ to error indicator; call unwind() if !error_ok.
+ * expr.c(evalerr): changed errorf() to warningf(); call unwind(LAEXPR).
+ * c_test.c(test_eval): merged code for integer operations to have
+ two calls to evaluate().
+
+ * io.c(warningf): print trailing newline; changed all calls.
+
+ * history.c(hist_get): string search: use histptr, not histptr - 1.
+
+Tue May 23 11:07:50 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(GI_NONAME): new define.
+ * misc.c(ksh_getopts): honour GI_NONAME flag.
+ * c_ksh.c(getopts_reset): set GI_NONAME flag.
+
+ * exec.c(comexec): don't change $0 if FPOSIX flag set.
+
+ * misc.c(ksh_getopt): don't use GI_DONE to allow parsing past
+ bad options.
+ * sh.h(GI_DONE): deleted define.
+
+ * var.c(unset): added array_ref parameter; unset/free whole array
+ if not an array_reference; changed all calls.
+ * c_sh.c(c_unset): set array_ref parameter if there is a [ in the name.
+
+Mon May 22 10:33:14 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * history.c(hist_init): complex version: initialize hist_source
+ (fix from Simon J. Gerraty).
+
+Sat May 20 11:06:15 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.2.2 distribution
+
+ * Makefile.in: added c_test.h to HDRS.
+
+Fri May 19 12:35:18 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.2.1 distribution
+
+ * emacs.c(v_version): ignore typed character if it is a space.
+ * emacs.c(x_emacs_keys): bind <ESC>erase-char to delete-back-word
+ (was delete-back-char).
+ * emacs.c(x_defkeybindings[]): bound list-file to ^X^Y and
+ newline-and-next to ^O, as per man page.
+
+ * c_ksh.c(c_whence): changed "is a keyword" to "is a reserved word".
+
+ * sh.h: changed SVSV_PGRP to SYSV_PGRP.
+
+ * vi.c(vi_cmd): uncommented case for ^[ to make it easy to enable
+ completion.
+
+Mon May 15 15:25:22 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * history.c(c_fc): accept -40 as -- -40.
+ * main.c(initcoms[]): take -- out of history alias.
+
+ * vi.c(print_expansions): handle trailing slash correctly (don't
+ print empty strings).
+
+ * c_ksh.c(c_cd): put back ksh_get_wd() call for os/2.
+
+ * misc.c(ksh_get_wd): changed buf to b in call to getcwd().
+
+Tue May 9 13:57:31 NDT 1995 Michael Rendell (michael@dragon.cs.mun.ca)
+
+ * c_test.h: new file.
+ * c_test.c: major code restructuring: common parsing/evaluation
+ routines call/called-by three sets of routines: one for
+ normal test (and [..]), one for parsing [[ .. ]] one for
+ evaluating [[ .. ]].
+ * c_test.c(oexpr,aexpr,nexpr,primary,is_op): renamed to test_oexpr,
+ test_aexpr, test_nexpr, test_primary, test_isop.
+ * c_test.c(eval_unop,eval_binop): combined into new test_eval function.
+ * c_test.c(syntax): renamed to ptest_error,
+ * c_test.c(ptest_isa,ptest_getopnd,ptest_eval): new functions.
+ * syn.c(syntaxerr): added extra arg; changed all calls.
+ * syn.c(db_parse,db_oaexpr,db_nexpr,db_primary): deleted.
+ * syn.c(dbtestp_isa,dbtestp_getopnd,dbtestp_eval,dbtestp_error): added.
+ * syn.c(get_command): case DBRACKET: changed to call new routines.
+ * tree.c(ptree): case DBRACKET: changed.
+ * exec.c(execute): case DBRACKET: changed.
+ * exec.c(dbteste_isa,dbteste_getopnd,dbteste_eval,dbteste_error): added.
+
+Fri May 5 17:10:23 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * emacs.c(compl_file,compl_command): fixed buffer growing code.
+
+Thu May 4 22:44:01 NDT 1995 Michael Rendell (michael@garfield.cs.mun.ca)
+
+ * aclocal.m4(KSH_UNISTD_H): include <sys/types.h> and only include
+ <dirent.h> if HAVE_DIRENT_H is defined.
+
+Thu May 4 21:19:15 NDT 1995 Michael Rendell (michael@garfield.cs.mun.ca)
+
+ * c_ksh.c: include "ksh_stat.h".
+ * c_ksh.c(c_cd): don't do physical chdir if S_ISLNK not defined.
+
+Wed May 3 10:08:32 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.2.0 distribution
+
+ * misc.c: include <ctype.h>.
+ * misc.c(gmatch): added isfile argument; changed all calls.
+ * sh.h(FILECHCONV): (os2 version) - use isupper.
+ * emacs.c(strmatch): don't increment in FILECHCONV.
+
+ * aclocal.m4(KSH_HEADER_SYS_WAIT): new macro.
+ * configure.in: use KSH_HEADER_SYS_WAIT instead of AC_HEADER_SYS_WAIT.
+ * ksh_wait.h: if POSIX_SYS_WAIT not defined, undef W* macros.
+
+Tue May 2 12:10:39 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * emacs.c,emacs-gen.sh,emacs-c.in,emacs.out,Makefile.in: changed emacs
+ source munging to create emacs.out which is included by emacs.c
+ rather then munging emacs.c itself.
+
+ * lex.c(pprompt): flush shl_out.
+
+ * vi.c(glob_word): if path has *?[, don't add * (was if last component).
+
+ * emacs.c(x_search_char): renamed to x_search_char_forw.
+ * emacs.c(x_search_char_back): new function; bound to ^[^].
+
+ * sh.h: changed SVR3_PGRP to SYSV_PGRP.
+ (fixes from Gabor Zahemszky).
+
+Tue May 2 10:09:57 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ksh.c(c_cd): deleted OS2 ifdefs.
+ * path.c(make_path): use ISRELPATH instead of ISABSPATH
+ * path.c(simplify_path): use ISROOTEDPATH instead of ISABSPATH.
+ * sh.h(ISABSPATH,ISROOTEDPATH,ISRELPATH): changed/new defines.
+
+ * aclocal.m4(AC_LANG_C,AC_LANG_CPLUSPLUS,AC_TRY_RUN): copied
+ from autoconf's acgeneral.m4, changed to handle .exe suffix.
+ * aclocal.m4(KSH_OS_TYPE): os2 case: set $ac_exe_suffix.
+ * configure.in: substitute ac_exe_suffix.
+ * Makefile.in: changed references to E to exe_suffix, set to
+ ac_exe_suffix
+
+ * c_ksh.c(c_cd): ifdef S_ISLNK second use of get_phys_path().
+ * edit.c(x_mode): removed ifndef OS2.
+ (fixes from Dale DePriest)
+ * exec.c(search_access1): add .sh to suffix lists.
+ * vi.c(vi_insert,vi_hook): OS2: changes to allow arrow keys work
+ in insert mode.
+
+Mon May 1 16:28:44 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * path.c(ksh_get_wd): getcwd() case, return alloc'd buffer, not
+ a malloc'd one.
+
+Mon May 1 09:41:56 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * aclocal.m4: changed HAVE_SYS_RESOURCES_H to HAVE_SYS_RESOURCE_H.
+
+ * aclocal.m4(KSH_OS_TYPE): new macro.
+ * aclocal.m4(KSH_OS2_EMX): deleted.
+ * configure.in: deleted calls to AC_AIX,AC_MINIX,AC_ISC_POSIX,
+ KSH_OS2_EMX; replaced with KSH_OS_TYPE.
+ * acconfig.h(OS_ISC,OS_SCO): new undefs.
+ * sh.h: changed use of isc386 to OS_ISC
+ * edit.c: changed use of M_UNIX to OS_SCO.
+
+Sat Apr 29 21:10:54 NDT 1995 Michael Rendell (michael@garfield.cs.mun.ca)
+
+ * vi.c(glob_word): don't append * if there are unescaped globing
+ characters in the last component of the filename; some redundant
+ code eliminated.
+ (based on fix from Michael Jetzer).
+
+Fri Apr 28 16:10:22 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * eval.c(globit): save/restore actual DIRSEP char - don't use DIRSEP.
+
+ * c_ulimit.c: removed ARGS from declaration of ulimit to avoid
+ portability problems (osf/1 has ulimit(int,...), os2 has
+ ulimit(int,long)).
+
+ * tty.c(tty_init): added __SCO__ defines to avoid opening /dev/tty.
+
+ * configure.in,aclocal.m4,acconfig.h: added KSH_OS2_EMX test.
+ * os2/config.h, os2/configure.cmd, os2/make.sed: updated for new
+ autoconf.
+
+Tue Apr 25 12:20:45 NDT 1995 Michael Rendell (michael@dragon.cs.mun.ca)
+
+ * configure.in: added sys/param.h test; changed getcwd test to getwd.
+ * c_ksh.c(c_pwd): new function.
+ * sh.h(current_wd, current_wd_size): new variables.
+ * c_ksh.c(c_cd): changed to handle -L, -P.
+ * main.c(main): use set_current_wd when setting $PWD;
+ instead of changing to / when can't get pwd, print warning;
+ deleted pwd alias; don't make PWD and OLDPWD reaedonly.
+ * path.c(simplify_path): changed to handle relative paths.
+ * path.c(make_path): added phys_path argument to support cd -P.
+ * path.c(set_current_wd,get_phys_path,do_phys_path): new functions.
+ * misc.c(ksh_get_wd): new function.
+ * missing.c(getcwd): deleted.
+ * misc.c(options[]),sh.h: added "physical", FPHYSICAL.
+
+Mon Apr 24 14:33:03 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * shf.c(shf_smprintf): new function.
+
+ * expand.h(Xsize): new define.
+
+Fri Apr 21 21:22:44 NDT 1995 Michael Rendell (michael@garfield.cs.mun.ca)
+
+ * sh.h: changed SIZEOF_long to SIZEOF_LONG.
+ * exec.c(scriptexec): if OS2 ifdefed code, changed ISDIRSEP to
+ explicit /.
+
+Thu Apr 20 21:18:12 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * history.c(hist_get) if n < 0, use n + 1 to account for histbackup().
+
+ * lex.c(set_prompt): added source argument; changed all calls;
+ changed to do ! and !! substitutions when setting PS1.
+ * lex.c(pprompt): ifdef'd out code to deal with ! and !!.
+
+ * shf.c(shf_puts): new routine.
+ * exec.c(herein), lex.c(getsc_): changed to use shf_puts.
+
+Thu Apr 20 15:50:35 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * siglist.sh: clear traps in subshell to cover for bug in bash 1.4.3
+ (based on fix from Fritz Heinrichmeyer).
+
+Wed Apr 19 12:04:59 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(classify): cleaned up table; filled in U_ flag for commands
+ that don't modify things.
+ * vi.c(first_insert, saved_inslen): new variables.
+ * vi.c(vi_reset): don't reset yanklen, inslen, lastcmd, lastac;
+ set first_insert, saved_inslen.
+ * vi.c(vi_insert): added code to handle first insertion to allow
+ redoing commands from last edit.
+ (based on fixes from Michael Jetzer).
+
+ * vi.c(VVERSION): new state.
+ * vi.c(classify): cleared C_ flag for 032 (^Z); set it for ^V.
+ * vi.c(nextstate): added VVERSION.
+ * vi.c(vi_hook): cases for VVERSION.
+ * sh.h(ksh_version): new declaration; removed declaration from
+ all other files.
+
+ * Makefile.in: removed rcs-ci, rcs-diff targets; put RCSFILES
+ into DISTFILES and removed former.
+
+ * var.c(newblock): copy argc/argv from previous env if it exists.
+
+Tue Apr 18 23:10:32 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * jobs.c(exchild): report internal error if execute() returns in child.
+ * exec.c(execute): case TASYNC: clear exec flag in call to execute().
+
+Tue Apr 18 12:05:23 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * emacs.c(x_bind): added list argument.
+ * c_ksh.c(c_bind): added -l (list) option.
+
+ * emacs.c,emacs-c.in: moved emacs.c to emacs-c.in.
+ * Makefile: add rule to create emacs.c from emacs-c.in.
+ * emacs-gen.sh: new file.
+ * emacs.c(struct x_defbindings, x_defbindings[]): new struct/array.
+ * emacs.c(struct x_ftab, x_ftab[]): removed x_db_tab, x_db_char;
+ initialize x_ftab[] via script.
+ * emacs.c(x_init_emacs): changed to load key bindings from
+ x_defbindings.
+ * emacs.c(Findex): added typedef.
+ * emacs.c(x_tab[]): changed to index into x_ftab; changed all refernces.
+ * emacs.c(xft_*): changed to XFUNC_*.
+ * emacs.c(XF_PREFIX): new flag, used for x_meta1, 2, 3.
+ * emacs.c(KPREF,KNULL): deleted (no functional use), changed
+ references to KSTD.
+ * emacs.c(x_last_command): changed type to Findex.
+ * emacs.c(x_emacs): set x_last_command to 0 at start; removed
+ same from case KEOL.
+
+ * emacs.c(XF_ARG): new flag for struct ftab.
+ * emacs.c(x_ftab[]): filled in XF_ARG for appropriate commands.
+ * emacs.c(x_arg_defaulted): new variable.
+ * emacs.c(x_emacs,x_set_arg): set x_arg_defaulted.
+ * emacs.c(x_bword, x_fword,x_fold_case): removed use of x_last_command.
+ * emacs.c(x_fold_upper,x_fold_lower,x_fold_capitailze): trivial
+ functions that call x_fold_case; changed x_ftab[] to use these
+ instead of x_fold_case so arbitrary keys can be bound to them.
+ * emacs.c(x_fold_case): changed to assume argument is 'L', 'U', or 'C'.
+ * emacs.c(x_del_back,x_del_char,x_prev_histword,x_prev_com,x_next_com,
+ x_kill,x_insert): use x_arg and x_arg_defaulted.
+ * emacs.c(x_delete): don't change mark point (xmp) if <= cp; added
+ force_push argument; changed all calls.
+
+Mon Apr 17 10:30:12 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * emacs.c(x_e_getc): changed to handle macroptr, ungetting characters.
+ * emacs.c(x_e_ungetc): new function.
+ * emacs.c(x_emacs): let x_e_getc() take care of macroptr.
+ * emacs.c(x_version,x_search_hist): use x_e_ungetc() instead of
+ macroptr.
+ * emacs.c(x_set_arg): handle string of digits.
+
+ * emacs.c(x_search_hist): handle deleting chars from search string.
+ (fix from Dale DePriest)
+ * emacs.c(x_search): added sameline paramater.
+ * emacs.c(x_search_list): changes x_zots() to x_e_puts(); make
+ deleting in empty pattern break out of search.
+
+ * vi.c(domove): case '%': adjust ncursor forward only if matching
+ opening bracket (so when cursor is on the B in "(fooBar)", c%
+ changes the openbracket as well.
+ * vi.c(vi_cmd): case y/d/c: special case to move end point ahead
+ if move cmd is % and match was to the left of the cursor.
+
+Thu Apr 13 10:34:26 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(complete_word): no bell on ambiguous matches (user can
+ tell its ambiguous 'cause there is not space or slash appended)
+
+ * configure.in,aclocal.m4: added KSH_MEMMOVE, KSH_MEMSET tests
+ to fix problems with compiler builtins.
+
+ * misc.c(blocking_read, reset_nonblock) new routines.
+ * sh.h: deleted O_NONBLOCK ifdefs/defines.
+ * main.c(main),lex.c(getsc_),edit.c(x_getc),shf.c(shf_fillbuf):
+ use reset_nonblock().
+ (fix based on code from John Rochester)
+
+Tue Apr 11 14:36:22 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * history.c(c_fc): mostly POSIXized.
+ * history.c(hist_execute,hist_get_newest,hist_get_oldest): new routines.
+ * history.c(hist_get,histget): changed histget to hist_get.
+ * history.c(hist_replace,histrpl): changed histrpl to hist_replace.
+ * lex.h(SHIST,histpush): deleted; deleted all references.
+ * history.c(histget): add approx check for history that hasn't
+ happened yet.
+
+ * misc.c(getn): allow leading plus (eg, +3).
+
+ * main.c(initcoms[]): defined history as "fc -l --".
+
+ * conf-end.h(JOBS): don't define if no posix or bsd process groups
+ (was if SIGCONT not defined).
+
+Mon Apr 10 14:51:54 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(comexec),c_ksh.c(c_getopts),c_sh.c(c_read): use FEXPORT flag.
+
+ * ksh_wait.h: changed to work with autoconf 2.x AC_HEADER_SYS_WAIT -
+ if sys/wait.h uses union wait, don't include it.
+
+Thu Apr 6 12:19:58 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * tty.c(tty_init): print warning if open of /dev/tty fails.
+
+Sat Mar 4 01:20:03 NST 1995 Michael Rendell (michael@garfield.cs.mun.ca)
+
+ * io.c(maketemp): create valid dos filenames.
+
+Mon Feb 27 11:04:32 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * Changed from autoconf 1.x to autoconf 2.x.
+ * acconfig.h: included old config.h.top and config.h.bot.
+ * config.h.top, config.h.bot: deleted; deleted all references.
+ * install.sh: changed to install-sh; changed all references.
+ * Makefile.in: use @CPPFLAGS@, @CFLAGS@, @LDFLAGS@;
+ use @configure_input@; remove config.log and config.cache in
+ distclean; use @prefix@ and @exec_prefix@.
+ * ksh_dir.h: changed to use new autoconf defines; changed NLENGTH()
+ to NAMLEN(); changed all references.
+
+Mon Feb 27 9:31:02 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(ISABSPATH): new macro.
+ * var.c(setspec): use ISABSPATH() when setting tmpdir.
+
+ * emacs.c(compl_file): added OS2 ifdefs.
+ * exec.c(scriptexec): OS2: ignore path specified in #! scripts.
+ * sh.h(ksh_dupbase): OS2: now same as unix.
+ * trap.c(sigtraps[],inittraps): remove OS2 defines.
+ * trap.c(alarm_catcher): V7_SIGNALS: use sig, not i.
+ (Fixes from Dale DePriest)
+
+Mon Feb 27 10:06:00 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * configure.in: test for resource.h.
+ * c_ulimit.c: include ksh_time.h instead of sys/time.h; use
+ HAVE_SYS_RESOURCE_H when including sys/resource.h
+ (was HAVE_SETRLIMIT).
+ * aclocal.m4(KSH_RLIM_T): check sys/resources.h for rlim_t.
+
+Fri Feb 24 17:30:16 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(struct macro_state, macro): new structure/variable.
+ * vi.c(vi_hook, vi_cmd): use macro state info to allow nested macros,
+ detect recursive macros.
+
+Wed Feb 22 21:20:43 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ksh.c(c_whence): "an export" instead of "a export".
+ * vi.c(classify[]): added @<char>.
+ * vi.c(vi_hook,vi_cmd): added support for @<char> (macros).
+ (fixes from Frank Edwards).
+
+Sun Feb 19 11:57:20 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(comexec): case CFUNC: use cp (not tp->name) when checking if
+ an autoloaded function was defined; save/restore kshname before/after
+ function call.
+ * var.c(popblock): don't set kshname to e->loc->argv[0] - it isn't
+ always right.
+
+Fri Feb 10 12:36:16 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * misc.c(parse_args): check OF_SET when building set_opts (was
+ checking OF_CMDLINE).
+
+ * conf-end.h(JOBS): don't define if SIGCONT not defined.
+
+ * sh.h(FLOGIN) new enum.
+ * misc.c(options[],parse_args): added login option; set FLOGIN if
+ name in argv[0] starts with -.
+ * main.c(main): use FLOGIN flag; changed the way OS2 code looks
+ for profile.
+
+Wed Feb 1 09:55:40 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * expr.c(varsub): in FUNSET test, don't always fail # and %
+ substitutions (test for unset variable).
+
+Wed Jan 25 09:22:15 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(MIN_COLS): new define.
+ * sh.h(MIN_EDIT_SPACE): new define.
+ * vi.c(prompt_trunc): new variable.
+ * vi.c(edit_resize): calculate how much of prompt to truncate.
+ * lex.c(pprompt): added new argument; changed all calls.
+ * lex.c(yylex),emacs.c(x_emacs),vi.c(x_vi): move pprompt() inside
+ x_emacs(), x_vi() or just before read in yylex().
+
+Tue Jan 24 12:35:18 NST 1995 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * misc.c(parse_args): changed arrayname variable to array.
+ * var.c(basename): changed name of function to arrayname();
+ changed all references (Based on fix from Dan Quinlan).
+
+Fri Dec 30 10:34:50 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * ksh.1: modifications to generate two man pages: sh and ksh
+ (Fixes from Michael Harrdt).
+
+Wed Dec 28 16:55:13 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(complete_word): don't check for globing characters.
+
+Wed Dec 28 10:32:18 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(search_access1): don't use ret variable; move "." to end
+ of xsuffixes/rsuffixes.
+ * os2.c(_execve): OS2: fixed typo.
+ * sh.h(FILENCMP): changed stricmp to strnicmp.
+ * os2/config.h: added define for rlim_t.
+ * os2/make.sed: changed > null to > nul.
+ * Makefile.in(dist): generate os2/makefile after running Dist-fixup.
+ (Fixes from Dale DePriest)
+
+Thu Dec 22 15:06:06 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.1.3 distribution
+
+ * *.c: removed RCSids.
+
+Wed Dec 21 11:55:01 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * table.h(struct tbl): changed array field to union of array/fname;
+ changed all references.
+ * c_ksh.c(c_whence): print undefined function path.
+ * exec.c(comexec): do autoloading of undefined functions; print
+ error if function can't be found.
+ * exec.c(findcom): fill in tp->u.fname for undefined functions;
+ search FPATH if search of PATH fails.
+ * table.h(FC_NOAUTOLOAD): deleted define; removed all references.
+
+Tue Dec 20 14:16:16 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(herein): check if name is null.
+ * lex.h(HEREDELIM,SHEREDELIM,SHEREDQUOTE): new defines.
+ * lex.c(yylex): added code for HEREDELIM.
+ * syn.c(synio): use HEREDELIM.
+ * lex.c(readhere): changed to allow \n in here-delimiter.
+
+ * tree.c(tputS): quote ", ` and $ inside "-quotes.
+ * tree.c(ptree,pioact): made static.
+ * tree.c(ptree,fptreef,vfptreef): added indent argument; changed to
+ use indent argument; changed all calls.
+ * tree.h(struct ioword): added delim field.
+ * tree.c(iocopy),syn.c(synio,syntaxerr): deal with delim field.
+ * tree.c(pioact): print contents of here documents.
+
+ * c_ksh.c(c_typeset): typeset -f foo: set exit code to 1 if function
+ not found.
+
+Mon Dec 19 15:14:02 NST 1994 Michael Rendell (michael@garfield.cs.mun.ca)
+
+ * history.c(histinit): increment line number for each history line.
+
+ * exec.c(iosetup): OS2: if open /dev/null fails, try nul instead.
+ * Makefile.in(debugtools,install,uninstall): make check-pgrp last;
+ use $E.
+ * eval.c(eval,expand): OS2: added DODIRSWP code.
+ * main.c(main): OS2: only include $HOME/kshrc.ksh if interactive.
+ * sh.h(FILENCMP,FILECMP,FILECHCONV): new defines.
+ * misc.c(gmatch),vi.c(grabsearch,complete_word),emacs.c(compl_file):
+ OS2: case insensitive compares.
+ (fixes from Dale DePriest).
+
+Mon Dec 19 09:54:42 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(vi_cmd): make ~ honour argcnt (fix from Troy Bollinger).
+
+ * vi.c(complete_word): don't add trailing / if there is already one.
+ * vi.c(glob_word): return rval, not 0.
+
+Thu Dec 15 11:06:01 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(vi_cmd): call complete_word() with argument of 1 not 0.
+
+Tue Dec 13 12:07:50 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * history.c(histget): made static; added approx argument; changed
+ all calls.
+
+Tue Dec 13 10:58:14 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * Makefile.in(mandir): use $(manext), not 1 (fix from Mike Long).
+
+Mon Dec 12 20:55:53 NST 1994 John Rochester (jr@panda.cs.mun.ca)
+
+ * tree.c(ptree): print TELIF part of if statements
+
+Fri Dec 9 15:21:36 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * trap.c(inittraps): OS2: don't trap SIGTERM (temporary fix).
+
+ * exec.c(search_access1): OS2: fixed to check for valid suffix
+ and change mode from X_OK to R_OK if appropriate.
+
+ * edit.c: include <sys/stream.h>, <sys/ptem.h> for SCO unix
+ (fix from William Bader).
+
+ * c_ulimit.c(c_ulimit): changed type of val from long to rlim_t
+ (fix from Thomas Gellekum and J.T.Conklin).
+ * aclocal.m4(KSH_RLIM_T): new test for rlim_t.
+ * configure.in: use KSH_RLIM_T.
+ * acconfig.h: added rlim_t.
+
+Thu Dec 8 12:20:25 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * expr.c(evalexpr): changed div-by-zero test to only derefernce vr
+ if operation is a divide.
+
+Mon Dec 5 14:42:52 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(search): OS2: typo - changed namlen to namelen.
+ * exec.c(search_access): OS2: check execute bit explicitly.
+ * main.c(main): OS2: don't include ./profile.ksh.
+ * options.h(DEFAULT_PATH): OS2: added /os2 to path.
+ * sh.h(ksh_getdup): OS2: define to getdup(); prototype for getdup().
+ * Makefile.in(dist): create os2 Makefile based on distribution
+ Makefile.in.
+
+Mon Dec 5 12:17:14 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.1.2 distribution
+
+ * eval.c(globit): when searching directory, re-calculate end of
+ string based on prefix length.
+
+Fri Dec 2 11:07:48 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * syn.c(wordlist): if token isn't 'in', don't reject ;.
+
+ * eval.c(expand): leading non-white-space IFS chars no cause initial
+ empty field.
+
+Thu Dec 1 12:04:00 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.1.1 distribution
+
+Thu Dec 1 10:50:38 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(TF_FATAL,fatal_trap): new define,variable.
+ * trap.c(inittraps,trapsig,fatal_trap_check,trap_pending,runtrap,
+ settrap): use TF_FATAL, fatal_trap.
+ * trap.c(runtraps): changed argument from bool to TF_* flag; changed
+ all calls.
+ * jobs.c(j_waitj): check fatal_trap flag.
+
+Wed Nov 30 11:20:03 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * conf-end.h: new file.
+ * config.h.bot: moved guts to conf-end.h.
+
+ * emacs.c(struct x_ftab): changed type of xf_db_char from char to int.
+ * emacs.c(x_emacs): changed type of c from char to int.
+ * emacs.c(X_NTAB): new define.
+ * emacs.c(x_bind,x_init_eamcs): new X_NTAB, X_TABSZ.
+ * emacs.c(x_prefix3, x_meta3): ifdef OS2.
+ * emacs.c(x_bind): ifdef OS2; mask *a1 with CHARMASK.
+
+ * exec.c(search_access): new function.
+ * exec.c(search): use search_access() instead of duplicating test.
+ * exec.c(search,search_access1): ifdef OS2.
+
+ * Makefile.in(OS2FILES): new macro.
+ * Makefile.in(dist): add OS2FILES to distribution.
+
+ * options.h(DEFAULT_PATH): ifdef OS2.
+ * edit.c(x_getc,x_mode): ifdef OS2.
+ * path.c(make_path): ifdef OS2.
+
+Tue Nov 29 16:51:35 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(EXECSHELL,EXECSHELL_STR): ifdef OS2.
+ * exec.c(scriptexec): use EXECSHELL_STR (was "EXECSHELL").
+
+ * trap.c(sigtraps[]): ifdef OS2.
+ * lex.c(yylex): ifdef OS2.
+ * misc.c(change_flag): ifdef OS2.
+ * history.c(HISTFILE): ifdef OS2.
+ * eval.c(homedir): ifdef OS2.
+ * c_sh.c(shbuiltins[]): ifdef OS2.
+
+ * sh.h(ksh_execve,ksh_dupbase): new defines.
+
+ * jobs.c(exchild): ifdef use of nice.
+
+Tue Nov 29 12:32:26 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * eval.c(globit,copy_non_glob): changed to pass/use &xp it can change
+ (memory can be re-allocated).
+
+ * ksh_dir.h(NLENGTH): new macro.
+ * eval.c(globit): use NLENGTH macro.
+
+ * alloc.c(aresize): removed redundant np and optr variables.
+
+Mon Nov 28 14:55:49 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * config.h.bot(HISTORY): new define.
+ * lex.c(getsc_): ifdef HISTORY.
+ * history.c: ifdef HISTORY (dummy histsave, init_histvec and
+ hist_finish routines).
+ * c_ksh.c(kshbuiltins): c_fc: ifdef KSH
+ * lex.h(HISTORY): changed to HISTORYSIZE; changed all references.
+
+ * options.h(KSH): new define.
+ * config.h.bot: changed to deal with KSH define.
+ * exec.c(do_select,pr_menu): ifdef KSH.
+ * exec.c(execute): case TSELECT: ifdef KSH.
+ * c_ksh.c(c_whence,c_command,kshbuiltins[]): ifdef KSH.
+ * main.c(initcoms[],main): ifdef some aliases, SECONDS/RANDOM/TMOUT.
+ * syn.c(get_command): case TDBRACKET: ifdef KSH.
+ * syn.c(db_parse,db_aoexpr,db_nexpr,dp_primary): ifdef KSH.
+ * syn.c(tokentab[]): "select", "[[" ifdef KSH.
+ * var.c(special,getspec,setspec,unsetspec): ifdef KSH.
+ * ksh.1: ifdef KSH; misc fixups.
+ (changes mostly from Michael Haardt).
+
+Mon Nov 28 14:27:34 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * var.c(skip_varname,special,global,local), table.c(hash,tsearch,
+ tenter): made argument and return value const.
+
+ * main.c(version_param[]): new variable.
+ * main.c(initcoms[],main): use version_param instead of "KSH_VERSION".
+
+ * history.c(histsave): EASY_HISTORY: changed to take same arguments
+ as COMPLEX_HISTORY histsave(); changed all calls, removing
+ unneeded ifdefs.
+
+ * vi.c(x_vi), emacs.c(x_emacs): changed unwind() call from LINTR
+ to LSHELL so newline isn't printed twice - also lets runtrap()
+ set the exit code.
+
+ * vi.c(vi_cmd): increment source line if saving to history.
+
+Fri Nov 25 14:43:57 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * syn.c(get_command): don't generate a syntax error if EOF is read.
+
+ * configure.in: add LDSTATIC to LDFALGS if the former is set.
+
+ * history.c(hist_skip_back): start at the end of the buffer, not
+ one past the end (fix from Simon J. Gerraty).
+
+Thu Nov 24 09:53:49 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * syn.c(get_command,dogroup): allow { ...;} to be used instead
+ of do ...;done in for/select loops.
+
+Wed Nov 23 09:09:43 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.1.0 distribution
+
+ * var.c(setspec): set seconds to current time - assigned value,
+ not just current time.
+
+ * emacs.c(x_copy_arg): deleted ifdef'd out code (x_prev_histword()
+ does what it was supposed to do).
+
+ * emacs.c(compl_command): don't call list_stash() twice (happened
+ if type == 2 and multi set).
+
+Tue Nov 22 10:26:13 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_test.c(eval_unop): don't assume S_ISBLK, S_ISCHR, S_ISUID,
+ S_ISGID are defined.
+
+ * path.c(make_path): avoid addeding extra /'s in paths; avoid
+ infinate loop if result buffer not big enough.
+
+ * main.c(main): setting PWD: avoid calling setstr() with the
+ current value of PWD.
+
+ * var.c(typeset): set free_me to 0 if t is integer.
+
+ * emacs.c(x_search_hist): added overflow checking to fixed sized
+ buffers.
+ * emacs.c(compl_file,compl_command): removed fixed sized buffers.
+
+ * vi.c(x_vi), emacs.c(x_emacs): on interrupt, unwind instead of
+ calling runtraps().
+
+ * vi.c(vi_cmd): added 'g' command to goto the most recent command.
+
+ * c_sh.c(c_read), c_ksh.c(c_print): always increment source->line when
+ saving history.
+
+Mon Nov 21 10:45:34 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(do_selectargs): removed use of pmenu variable (redundant)
+ use isspace() instead of IFS chars; include <ctype.h>.
+
+ * aclocal.m4(KSH_TERM_CHECK): do not allow HAVE_TERMIOS_H check to
+ succeed on ultrix (avoid type-ahead loss).
+
+ * emacs.c(x_fword): cahnged loop to skip non word chars, then word
+ chars (was the opposite).
+
+ * main.c(shell): after error/interrupt/etc, reset an EOF if ignoreeof
+ option is set.
+
+ * vi.c(classify[]): changed space (040) from C_|U_ to M_
+ (got broken in 5.0.10).
+
+ * ksh_wait.h(ksh_waitpid): new define.
+ * jobs.c(waitpid): moved define to ksh_wait.h; changed use of
+ waitpid() to ksh_waitpid().
+
+ * history.c(hist_skip_back),io.c(maketemp): use procpid instead of
+ getpid().
+
+Fri Nov 18 16:08:09 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(FSHOW8): inverted meaning: now if set, do the M- stuff
+ (done so 8 bit char sets work by default).
+
+ * main.c(main): set exstat to 127 if command file can't be opened.
+
+ * main.c(main): use argv[0] instead of kshname when deciding
+ whether to include profiles.
+
+Fri Nov 18 14:25:11 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.0.10.1 distribution
+
+ * tty.h: deleted KSH_VDISABLE; moved _POSIX_VDISABLE stuff to edit.c.
+ * edit.c(x_init): calculate value for vdisable_c.
+ * edit.c(x_mode): use vdisable_c instead of KSH_VDISABLE.
+
+Thu Nov 17 12:09:13 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.0.10 distribution
+
+ * lex.c(getsc_),edit.c(x_getc): call runtraps(FALSE) if read is
+ interrupted.
+ * vi.c(x_vi),emacs.c(x_emacs): call runtraps(FALSE) (was TRUE).
+
+Wed Nov 16 09:48:54 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(execute,scriptexec): call __setostype(0)/(1) before/after
+ execve() on ISC machines.
+
+ * trap.c(trap_pending): new fuction.
+ * jobs.c(j_waitj): use trap_pending(); return -<signal-number> if
+ interrupted.
+ * jobs.c(waitfor): added sigp argument; changed all calls.
+ * c_sh.c(c_wait): use signal number set by waitfor() to set exit status.
+
+ * shf.c(SHF_INTERRUPT): no longer calls intrcheck() - now sets
+ error flag and returns EOF.
+ * c_sh.c(c_read): re-arranged to have single shf_getc() call; if read
+ interrupted and signal is fatal (fatal_trap_check()), make read
+ return with appropriate exit code.
+ * trap.c(fatal_trap_check()): new function.
+ * trap.c(inittraps()): catch and cleanup on SIGHUP; don't force the
+ setting of SIGINT,SIGQUIT,SIGTERM,SIGHUP.
+
+ * table.c(tenter): changed to use strlen()/memcpy() instead of loops.
+
+ * var.c(initvar): new function.
+ * main.c(main): call initvar().
+ * var.c(special): changed to use hash table for lookup.
+
+ * main.c(main),syn.c(initkeywords): moved table initialization
+ from main() to initkeywords().
+
+Tue Nov 15 10:01:20 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * eval.c(copy_non_glob): new routine.
+ * eval.c(globit): changed to use copy_non_glob() instead of strchr().
+
+ * misc.c(cclass): if [..] pattern has no closing ], do literal
+ compare of character with [ (used to always fail).
+
+ * eval.c(globit): handle symbolic links in the check code.
+
+ * configure.in: added check for lstat().
+ * ksh_stat.h: defined lstat to be stat if lstat is not available.
+
+ * exec.c(search): return Xclose() instead of Xstring().
+
+Mon Nov 14 16:28:41 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * ksh_times.h: changed BROKEN_TIMES to TIMES_BROKEN.
+
+ * c_test.c(syntax): removed \n from error messages.
+
+ * eval.c(glob,globit): changed to use dynamicly allocated string
+ instead of a fixed sized buffer.
+
+Thu Nov 10 10:47:55 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * history.c(sethistsize): don't set size if new size is < 0; fixed
+ offset calculation so histptr is not way beyond the end of array;
+ if history is shrinking, save newest history back.
+
+ * vi.c(vi_hook): case VSEARCH: call restore_cbuf() after \n or \r.
+
+ * main.c(quitenv): call restfd() even if fd < 0 to re-close fd.
+
+ * exec.c(execute): commented out code that set savefd[0/1] to -1
+ if input/output was a pipeline.
+
+ * missing.c(dup2_fixup): deleted function.
+ * sh.h(dup2->dup2_fixup): deleted define.
+ * io.c(ksh_dup2): new function; changed all dup2() calls to ksh_dup2().
+
+Wed Nov 9 11:11:31 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * edit.h(struct edchars): added eof field.
+ * edit.c(x_init): initialize eof fields.
+ * vi.c(x_vi): changed ^D to edchars.eof.
+ * vi.c(vi_cmd): make I/cc/S skip blanks.
+
+ * history.c(histsave): EASY_HISTORY: use memmove() to copy pointers
+ back one.
+
+ * vi.c(vi_cmd): make G act the same as at&t ksh.
+ * vi.c(ismeta,O_): deleted macros; removed all references to O_.
+ * vi.c(classify[]): add ^X and ^F to command mode.
+
+Tue Nov 8 11:15:01 NST 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * main.c(initsubs[]): don't set SHELL.
+
+ * vi.c(vi_cmd): added v command (start up vi).
+ * vi.c(vi_hook): added case for vi_cmd() returning 2.
+ * vi.c(grabsearch): set anchored flag if pattern starts with ^.
+ (based on fixes from Michael Jetzer).
+
+ * history.c(findhist): added anchored argument; changed all calls.
+ * history.c(histget): start searching from histptr-1; changed to
+ call findhist() to do searching.
+ * history.c(c_fc): changed to print multiline commands correctly.
+ (based on fixes from Michael Jetzer).
+
+Fri Nov 4 10:30:14 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.c(yylex): when pushing alias sources, allocate from existing
+ source's area.
+
+ * lex.c(struct source): added areap field.
+ * lex.c(pushs): added area argument; changed all calls.
+ * history.c(histrpl): changed constant sized hline[] to expandable
+ string; removed hline/hsize parameters; changed all calls; put
+ newline at end of string.
+ * history.c(c_fc): changed to use dynamically sized buffer when reading
+ commands; strip nulls after read.
+ * history.c(histbackup): made static.
+
+ * trap.c(block_pipe): if handler is SIG_DFL, change it to SIG_IGN.
+
+ * lex.c(readhere): changed to allow eof after end-of-file marker
+ (bug report from Andrew Moore).
+
+Thu Nov 3 09:09:39 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * io.c(coproc_read_close,coproc_write_close): new functions.
+ * c_sh.c(c_read): call coproc_read_close() when eof is read.
+ * c_ksh.c(c_print): set PO_COPROC if fd is coproc.write; call
+ coproc_write_close() if write fails due to EPIPE.
+ * exec.c(iosetup): call coproc_write_close() after #>&p.
+ * sh.h(EF_COPROC_DUPED): deleted.
+ * sh.h(struct coproc): deleted isopen field.
+ * io.c(cleanup_coproc): do not use isopen field.
+ * c_sh.c(c_exec): deleted EF_COPROC_DUPED code.
+ * exec.c(TCOPROC): don't set isopen; don't start new coprocess if
+ old job exists and write pipe hasn't been closed.
+
+ * misc.c(str_zcpy): new function.
+ * lex.c(getsc_): made line[] buffer local/static; use str_zcpy()
+ to fill line[].
+ * history.c(c_fc): use local hline buffer instead of global line[];
+ use str_zcpy() to fill hline[];
+ * history.c(histrpl): added hline and hsize parameters; changed all
+ calls.
+ * history.c(hist_init): EASY_HISTORY: use local hilne buffer instead
+ of global line[].
+ * lex.h(line[]): deleted.
+ * syn.c(compile): do not set s->str to null for STTY and SHIST.
+
+Wed Nov 2 11:48:36 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.c(getsc_): case SDDPAREN: set csstate before going to
+ SPAREN state.
+
+ * Makefile.in(RCSFILES): removed POSIX from list (now covered in
+ man page).
+
+Tue Nov 1 09:27:46 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * eval.c(comsub): save/restore source before/after compile().
+
+ * c_ulimit.c(c_ulimit): allow value to be arithmetic expression
+ (as per Korn book).
+
+ * c_sh.c(c_read): call set_prompt() before printing prompt.
+
+ * expr.c(v_evaluate): treat an empty expression as 0.
+
+Mon Oct 31 09:23:57 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(grabhist,grabsearch): check that history line doesn't overflow
+ edit buffer.
+
+ * history.c(hist_finish): (EASY_HISTORY) changed for-loop condition to
+ prevent passing the end of history.
+
+ * eval.c(expand): when stuffing MAGIC, cast c to char.
+
+ * misc.c(strip_nuls): new function.
+ * lex.c(getsc_): case STTY/SFILE/SSTDIN: call strip_nuls() after
+ reading commands.
+
+ * edit.c(set_editmode): reversed strstr() arguments - check for
+ vi/emacs in $EDITOR/$VISUAL string.
+
+ * syn.c(yyparse): allow EOF as well as newline after a command.
+ * lex.c(getsc_): case SSTRING: don't fake newline
+
+Sun Oct 30 10:55:20 NST 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ksh.c(c_print): echo: check for -n, -e and -E options.
+
+ * exec.c(comexec): don't allow command -p if restricted.
+
+Fri Oct 28 10:24:48 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * var.c(typeset): in fake_assign code, was freeing t->val.s + t->type
+ instead of t->val.s - now uses free_me variable instead of aflag.
+
+ * Makefile.in(depend): change blank lines in depend output to sh.h
+ so dumb make(1)s won't die.
+
+ * mail.c: changed checking to use atime/mtime instead of size; changed
+ struct mbox mb_size field to mb_mtime, changed all references.
+
+ * main.c(shell): do not execute (or set the exit status for) a null
+ command.
+ * lex.c(readhere): read the newline after the eof marker.
+
+Wed Oct 26 09:11:08 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * eval.c(globit): added FMARKDIRS support.
+
+ * emacs.c(x_ftab[]): added entries for ansi arrow key bindings.
+
+ * exec.c(execute,iosetup): move tracing of redirections from
+ execute() to iosetup() so expanded name can be printed.
+
+ * exec.c(execute): case TDBRACKET: read was being called instead of
+ test.
+
+ * ksh_stat.h(S_ISCDF): new define.
+ * c_test.c: added -H for context dependent files (HP bizarreness).
+
+ * main.c(initcoms[]): added alias local=typeset.
+
+ * Makefile.in(stamp-h,config.status): added double quotes CONFIG_FILES
+ and LDSTATIC assignments for dmake.
+ * aclocal.m4(KSH_SYS_SIGLIST): do something with sys_siglist so it
+ isn't optimized away.
+ * aclocal.m4(KSH_CLOCK_T): do extra check for clock_t in sys/times.h.
+ * acconfig.h(CLOCK_T_IN_SYS_TIMES_H): new define.
+ * sh.h(SIGNALS): use _SIGMAX if NSIG, _MINIX not defined.
+ (fixes from Brian Campbell <brianc@qnx.com>)
+
+ * emacs.c(x_transpose): changed behavior if FGMACS flag set
+ (fix from <guy@netapp.com>).
+
+Tue Oct 25 17:11:58 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * tty.c(KSH_VDISABLE): new define.
+ * edit.c(x_init): use KSH_VDISABLE.
+
+Tue Oct 25 09:55:09 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.0.9 distribution
+
+ * c_ulimit.c(c_ulimit): changed SOFT, HARD from enum to defines
+ to avoid problems with ancient compilers.
+
+ * vi.c(CHAR_LEN,char_len): changed macro to function; added FVISHOW8
+ support.
+ * misc.c(options[]), sh.h(FVISHOW8): added FVISHOW8 option.
+
+Sun Oct 23 11:02:26 NDT 1994 Michael Rendell (michael@maple.cs.mun.ca)
+
+ * main.c(shell): keep unwinding if LINTR and not interactive.
+
+ * lex.c(yylex): do redumentery quote parsing for $(..).
+
+Thu Oct 20 11:02:27 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(execute): case TSELECT: set rv to 1 if eof is read.
+ * exec.c(execute): case TFOR/TSELECT/TWHILE/TUNTIL: set rv to 0 before
+ entering loop, but after setjmp incase of a continue; rv to 0
+ after a break.
+ * exec.c(execute): case TFOR/TSELECT: do readonly check before
+ assigning value.
+ * c_ksh.c(c_getopts): do readonly check before assigning value.
+
+ * misc.c(print_columns),c_ksh.c(kill_fmt_entry),
+ misc.c(options_fmt_entry),exec.c(select_fmt_entry): new functions.
+ * c_ksh.c(c_kill),misc.c(printoptions),exec.c(pr_menu): use
+ print_columns() call a call-back routine to format information
+ in columns.
+
+Wed Oct 19 10:26:25 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * misc.c(cclass): require MAGIC before - and ].
+ * eval.c(expand): prefix - and ] with MAGIC if appropriate.
+
+ * var.c(typeset): don't allow export flag of readonly variables
+ to be cleared.
+
+ * eval.c(globit): added call to intrcheck().
+
+Mon Oct 17 11:48:05 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * lex.c(readhere): check for and report write errors.
+
+Sun Oct 16 16:10:59 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * c_ksh.c(c_cd): don't allow cd if restricted.
+ * exec.c(comexec): if restricted and command contains /, print error.
+ * exec.c(ioestup): if restricted, don't allow file creations.
+ * main.c(is_restricted): new function.
+ * main.c(main): save and reset FRESTRICTED during .profile/ENV reading;
+ set FRESTRICTED if argv[0] or SHELL refers to restricted shell;
+ make PATH, ENV, SHELL readonly if restricted.
+ * var.c(typeset): check for restricted shell and PATH/ENV/SHELL.
+
+Thu Oct 13 21:01:14 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * main.c(shell): only call j_notify() for interactive shells.
+
+ * c_sh.c(c_read): check if variable is readonly before assigning
+ value.
+
+Wed Oct 12 14:08:46 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.h(COPROC),tree.h(TCOPROC,XCOPROC): added defines.
+ * lex.c(yylex): return COPROC for |& token.
+ * syn.c(tokentab): added COPROC.
+ * syn.c(c_list): accept COPROC, create TCOPROC node.
+ * tree.c(ptree): added case for TCOPROC.
+ * exec.c(execute): added case for TCOPROC.
+ * io.c(check_fd,get_coproc_fd): new functions.
+ * c_sh.c(c_read),c_ksh.c(c_print): changed to use check_fd();
+ added -p option; for c_print() ensure SIGPIPE doesn't kill shell.
+ * exec.c(iosetup): changed to use check_fd() for IODUP; when
+ checking fore close, require exactly the string '-', not any
+ string starting with '-'; added strerror() to error message.
+ * jobs.c(exchild): don't open /dev/null if XCOPROC; close
+ coproc.read/write/childread in child if XCOPROC; don't pass
+ XCOPROC flag on to execute(); set coproc.job to job in parent
+ if XCOPROC.
+ * jobs.c(check_job): clear coproc.job if said job dies.
+ * trap.c(block_pipe,restore_pipe): new functions.
+ * sh.h(struct coproc, EF_COPROC_DUPED): new structure and define.
+ * c_sh.c(c_exec): if EF_COPROC_DUPED set, clean up co-process stuff.
+
+ * main.c(cleanup_parents_env): new function.
+ * jobs.c(exchild): call cleanup_parents_env() after fork().
+
+ * tree.h(IORDUP): new define.
+ * lex.c(yylex): changed redirection parsing to not accept & only after
+ a single < or >; set IORDUP flag for x<&y; fixed <</<>/>> check to
+ not allow >< (again).
+ * tree.c(pioact): use IORDUP flag to print <& or >&.
+
+ * jobs.c(exchild): set JF_ORIGFG flag if job started in foreground.
+ * jobs.c(j_waitj): don't get default tty settings if JF_ORIGFG not
+ set.
+
+ * misc.c(parse_args): treat -A as a flag that is handled later
+ (used to require argument); do array setting after argument
+ sorting.
+ * var.c(set_array): changed second argument from 0/1 flag to
+ -1/1 flag; changed all calls.
+
+Thu Oct 6 11:55:27 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * table.c(tinit): added initial table size argument; call texpand
+ if size isn't 0; changed all calls.
+ * main.c(main): try to make sure table size is big enough for
+ builtins and keywords (cut down on amount of re-hashing).
+
+ * eval.c(expand): added next and prev fields to struct SubType;
+ removed fixed length subtype array, changed code to allocate
+ SubTypes as needed.
+
+Wed Oct 5 09:25:06 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * main.c(main): moved initio() above inittraps() as later can print
+ stuff.
+
+ * table.h(IMPORT): new flag.
+ * var.c(typeset): if IMPORT flag set, don't allow array references,
+ insist on assignment.
+ * var.c(import): deleted function.
+ * main.c(main): use typeset() instead of import().
+
+ * sh.h: include expand.h.
+ * expand.h(Xnleft): new define.
+ * expand.h(struct XString, Xinit): added areap field; added area
+ argument to Xinit; changed all calls.
+ * lex.h(struct source): added xs field.
+ * shf.c(shf_gets,shf_getse): changed name fromshf_gets to shf_getse;
+ return pointer to null byte instead of start of buffer.
+ * lex.c(pushs): if type is SFILE or SSTDIN, initialize s->xs.
+ * lex.c(getsc_): case SFILE/SSTDIN: use s->xs instead of fixed
+ size line buffer.
+
+ * syn.c(compile): don't change s->str if SFILE.
+ * main.c(main): call pushs() explicitly for each of SSTRING,
+ SFILE, SSTDIN, STTY.
+
+ * aclocal.m4(KSH_GCC_FUNC_ATTR): changed GCC_FUNC_ATTR to
+ HAVE_GCC_FUNC_ATTR.
+ * config.h.bot: changed use of GCC_FUNC_ATTR; deleted
+ GCC_FA_NORETURN, GCC_FA_CONST, GCC_FA_FORMAT defines, created
+ generic GCC_FUNC_ATTR define; changed all uses of GCC_FA_*.
+
+ * main.c(main): set s->file for SSTDIN input.
+
+ * main.c(shell): pass LERROR on if not interactive.
+
+ * expand.h(Xcheck,XcheckN): added XcheckN define, changed Xcheck
+ to use XcheckN; made XcheckN call Xcheck_grow_() do do any real work
+ (to cut down on code size).
+ * misc.c(Xcheck_grow_): new function.
+ * exec.c(search),c_sh.c(c_read): changed to use Xstring() routines
+ (used to use the fixed size buffer line[]).
+ * exec.c(findcom): avoid re-saving search() result in ATEMP.
+
+Tue Oct 4 15:32:37 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * jobs.c(j_jobs): return int value indicating error/ok; changed
+ all calls.
+
+ * misc.c(getn): added int * argument to hold result; changed
+ return value to indicate success/failure; changed all calls.
+ * misc.c(bi_getn): new function.
+ * misc.c(getn_): deleted function.
+
+ * io.c(internal_error,error_prefix,warningf): new functions.
+ * *.c: changed errorf() calls reporting internal errors to
+ use internal_error() function; changed many shellf()s to
+ warningf().
+ * io.c(errorf),lex.c(yyerror): changed to use error_prefix().
+
+ * alloc.c(aprint): ifdef'd out.
+ * tree.c(phash): deleted function.
+
+Mon Oct 3 15:08:24 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * sh.h(kshname): new variable
+ * main.c(main): changed name to kshname, deleted local variable.
+ * exec.c(comsub): update kshname.
+ * var.c(popblock): restore kshname.
+ * io.c(errorf,bi_errorf): print shell name before error message.
+
+ * c_ksh.c(c_cd): print new directory on stdout, not stderr.
+
+ * sh.h(GI_MINUS): new define.
+ * misc.c(ksh_getopts): changed so once - or + introduces option,
+ all options must start with same character.
+
+ * sh.h(builtin_argv0): new variable.
+ * exec.c(call_builtin): set/clear builtin_argv0, builtin_flag; changed
+ argument to a struct tbl *; changed all calls.
+ * io.c(bi_errorf): new function.
+ * c_ksh.c,c_sh.c,c_ulimit.c,emacs.c,history.c,jobs.c: changed all uses
+ of errorf() to bi_errorf().
+ * emacs.c(x_bind): changed return value to int; changed all calls.
+ * history.c(histrpl): return 0 if there is an error; changed all calls.
+ * misc.c(parse_args): use bi_errorf(); return -1 for error; changed all
+ calls.
+ * misc.c(ksh_getopts): call bi_errorf instead of errorf which means
+ ksh_getopts() may return after an error, so changed all calls to
+ check for '?' return.
+
+ * exec.c(iosetup): use shellf() to report errors and return value
+ indicating success or failure.
+ * exec.c(execute): if iosetup fails, cause fatal error for special
+ builtins, return otherwise; print PS4 and redirections.
+
+Fri Sep 30 15:17:37 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * c_ulimit.c(c_ulimit): accept unlimited as a valid value.
+
+ * c_test.c(c_test): changed posix special case code to use
+ while loop.
+
+ * c_ksh.c(c_whence): for whence -p, don't look for built-ins or
+ fuctions.
+
+Thu Sep 29 10:34:59 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * c_ksh.c(c_alias): added -r option so the sysv-bounre shell
+ hash -r will work.
+
+ * eval.c(debunk): use strchr() to find first MAGIC, if any.
+
+Wed Sep 28 15:34:32 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * sh.h(O_NONBLOCK): define to O_NDELAY or FNDELAY if not defined.
+ * main.c(main): if stdin is O_NONBLOCK'd, clear O_NONBLOCK.
+
+ * misc.c(options[], parse_args): make -c a normal flag, not an option
+ with an argument (POSIX); deleted cargp argument to parse_args().
+ * main.c(main): print error if -c and no arguments left.
+
+ * lex.h(SSTDIN): new define.
+ * lex.c(yylex): added case for SSTDIN.
+ * main.c(main): if -s flag used, set source type to SSTDIN.
+
+Tue Sep 27 08:52:11 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * lex.c(get_brace_var): new function.
+ * lex.c(yylex): removed most ${..} parsing - leave it to expand();
+ use get_brace_var() to read the variable part of a ${..} expression.
+ * tree.c(tputs,wdscan): case OSUBST: delete code that understood
+ partially compiled ${..}.
+ * sh.h(C_SUBOP,C_SUBOP1,C_SUBOP2): changed C_SUBOP to C_SUBOP1,
+ added C_SUBOP2.
+ * misc.c(initctypes): removed # and % from C_SUBOP; changed C_SUBOP to
+ C_SUBOP1; added C_SUBOP2.
+ * eval.c(varsub): look at word part of substitution to figure out
+ type of substitution; check for bad substitutions; check for unset
+ variables for #/% substitutions.
+ * eval.c(struct SubType): changed type field to stype; changed quote
+ field to short; added f field.
+ * tree.h(DOTEMP_): new define.
+ * eval.c(expand): case CSUBST: case '=': deleted bad substitution
+ error (now handled in varsub); case OSUBST: removed special handling
+ of trimming - varsub() does it now; when pushing/poping state (st),
+ save/restore value of f; set f to DOPAT when trimming; case CSUBST:
+ case '=': restore original position in string, substitute the value
+ of the variable (as opposed to the value that was assigned to the
+ variable); case OSUBST: if '?' qualifier, turn off DOBLANK when
+ expandined word part; define DOTEMP_ when expanding word part
+ of ${..[#%=?]..}; deleted first_eq and tstart - replaced with
+ tilde_ok and saw_eq.
+
+ * eval.c(expand): tilde expansion: use tstart variable instead of cp;
+ changed '?' error message to be like at&t ksh; don't test if strval()
+ returns NULL - it doesn't.
+
+ * var.c(strval): if !ISSET, instead of returning null, set s to null.
+
+ * exec.c(comexec): case TDBRACKET: don't pass DOASNTILDE to evalstr().
+
+ * exec.c(scriptexec): changed line[] to buf[] so it doesn't get
+ confused with global the line[].
+
+ * main.c(initsubs): initialize PS4.
+ * edit.c(x_getc): cast char to unsigned before returning.
+
+Mon Sep 26 11:06:55 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * eval.c(globit): call strnsave instead of strsave; if file has
+ trailing /, use stat() to check that it is a directory.
+
+ * eval.c(expand): case CSUBST: case #/%: deleted duplicate *dp = 0;
+ case CSUBST: case =: copy string and call debunk() to oust MAGICs.
+
+ * misc.c(print_value_quoted): deleted bogus shf_shlout argument to
+ shprintf(); deleted unneeded test (p != s).
+
+ * main.c(main): turn on FBRACEEXPAND.
+ * misc.c(change_flag): turn FBRACEEXPAND off if turning FPOSIX on.
+
+ * vi.c(x_vi): use x_vi_zotc() to print ^D.
+ * vi.c(CHAR_LEN): new define.
+ * vi.c(vi_hook): use CHAR_LEN() instead of inline tests for
+ c < ' ' || c== 0x7f; search editing: display M- if necessary.
+ * vi.c(display): changed to deal tiwh meta-characters.
+
+ * vi.c(x_vi_zotc): print M- for meta chars.
+ * emacs.c(x_e_getc): new function; changed all x_getc() calls to
+ x_e_getc() calls.
+ * edit.c(x_getc): don't and out upper bit.
+
+ * sh.h(OPAREN,CPAREN,OBRACK,CBRACK,OBRACE,CBRACE): new defines
+ * expr.c(OPAREN,CPAREN): re-named to OPEN_PAREN, CLOSE_PAREN.
+
+ * eval.c(debunk): changed to convert MAGIC MAGIC -> MAGIC.
+ * eval.c(expand): removed ismagic_bracket stuff - not needed.
+ * eval.c(expand): always restore value of quote when CSUBST
+ reached; don't set DOGLOB in fdo if trimming.
+
+Sat Sep 24 11:46:03 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * tree.h(DOBRACE_): new define.
+ * eval.c(expand): changed check for leading ! in [..] to be more
+ robust (old test could have looked before start of string).
+ * eval.c(expand,maybe_expand_tilde): case ~: moved code into a function
+ (maybe_expand_tilde).
+ * eval.c(expand): expand alternations after macros, before globing
+ (was before macros).
+ * eval.c(alt_expand): changed to be called after macro expansion.
+ * eval.c(alt_scan,alt_count): deleted (no longer needed).
+
+ * misc.c(cclass): return NULL (no match) if first char in a range
+ is greater than the second.
+ * eval.c(expand): when building strings, stuff literal MAGIC chars.
+
+Thu Sep 22 15:05:48 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(comexec): re-arranged handling of builtin and exec;
+ handle command (and command -p, etc.); deleted comexec_flags
+ variable; made function static again; removed fcflags argument.
+ * table.h(FC_NOBLOCK): deleted define.
+ * c_sh.c(c_exec): changed empty function to deal with preserving I/O
+ redirects (code taken from comexec()).
+ * c_ksh.c(c_command): new function - calls c_whence.
+ * c_ksh.c(c_whence): removed code to deal with command -p.
+
+ * Makefile.in: changed [ to test.
+ * shf.h: changed errno structure member to errno_; changed all uses
+ (fixes for QNX from Brian Campbell).
+
+ * c_test.c(enum Op): deleted trailing comma (some compilers complain).
+ * proto.h: added volatile to tp arg of comexec() prototype.
+
+Thu Sep 22 11:08:31 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.0.8 distribution
+
+ * Makefile.in(install): added missing dollar (fix from Thomas Gellekum).
+
+ * emacs.c: changed CMASK to CHARMASK to avoid conflicts with some
+ system headers (eg, HP-UX 9.01 <sys/param.h>). Reported by Sean
+ Hogan.
+
+ * history.c(c_fc): wp not being incremented; -e strcmp() test reversed
+ (reported by Sean Hogan).
+
+Thu Sep 21 21:12:03 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.0.7 distribution
+
+Tue Sep 20 09:56:54 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * history.c(c_fc): use ksh_getopt() to parse arguments.
+ * c_ksh.c(c_bind): use ksh_getopt() to parse arguments.
+
+ * main.c(initcoms[]): changed hash alias from alias -t - to alias -t --.
+
+ * misc.c(print_value_quoted): don't use quotes if no special
+ characters.
+
+ * c_ksh.c(c_whence): added POSIX command command.
+
+ * c_sh.c(c_label): removed check for null wp.
+
+ * exec.c(comexec): added new flags argument (FC_*);
+ don't call newblock() if FC_NOBLOCK set; pass flags on to
+ findcom(); changed all calls; made comexc() a non-static
+ function.
+
+ * table.h:(FC_SPECBI,FC_FUNC,FC_REGBI,FC_UNREGBI,FC_PATH,FC_DEFPATH,
+ FC_NOAUTOLOAD,FC_NOBLOCK): new defines.
+ * exec.c(findcom): merged insert/justsearch/autoload arguments
+ into one flags argument; changed code to check various flags;
+ changed all calls.
+
+Sat Sep 17 20:17:59 NDT 1994 Michael Rendell (michael@garfield.cs.mun.ca)
+
+ * exec.c(comexec): print error if builtin has no command.
+
+ * exec.c(execute): before doing redirections, check for TCOM and
+ evaluate arguments and determine if it is a special builtin;
+ print arguments (using PS4) if FXTRACE set; case TCOM: simply call
+ comexec().
+ * exec.c(comexec): deleted vp argument; only call newblock() if
+ needed (ie, !special, !empty); evaluate assignments and put
+ in environment one at a time; print environment (using PS4) if
+ FXTRACE set; removed code to turn empty command into :;
+ removed environment setting code in switch statement.
+ * exec.c(echo): deleted function.
+
+ * lex.c(yylex): only honour CMDWORD if FPOSIX set.
+
+ * c_sh.c(shbuiltins): removed = attribute from false/true commands.
+
+ * sh.h(E_TCOM): delete define - not used.
+
+ * sh.h(null),var.c: use EXTERN for initialization of null.
+ * sh.h(space,newline,slash): new variables (" ", "\n", "/")
+ use these everwhere instead of "", " ", "\n", "/".
+ * path.c: include sh.h.
+
+ * exec.c(execute): combined TFOR/TSELECT cases.
+
+Fri Sep 16 11:32:01 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.h(CMDWORD): new define to prevent continued alias expansion in
+ non-command contexts.
+ * lex.c(yylex): only set ALIAS if SF_ALIAS and CMDWORD are set.
+ * syn.c(get_command): case LWORD/REDIR: pass CMDWORD if argc is 0.
+
+ * exec.c(comexec): if there is no command, do assignments and set
+ the return value to subst_exstat (used to fake a : command).
+
+ * sh.h(subst_exstat): new variable.
+ * exec.c(execute): case TCOM: clear subst_exstat before doing eval()s.
+ * eval.c(expand): set subst_exstat to return value of waitlast().
+ * c_sh.c(c_set): if !FPOSIX, return subst_exstat instead of 0.
+
+ * exec.c(execute): removed redundant "exstat = rv;" near if FERREXIT.
+ * exec.c(comexec): case CFUNC: for normal function completion, set
+ i to 0 and rv to return value of execute() (was i=LRETURN,exstat=..).
+
+ * main.c(include): return -1 if file could not be found/opened,
+ otherwise, the exit status of the last command is returned;
+ changed all calls.
+ * c_sh.c(c_dot): print error if include() returns < 0.
+
+ * var.c(setspec): ifdef EDIT'd V_VISUAL, V_EDITOR cases.
+
+ * misc.c(parse_args): no longer accept set -o alternations as
+ a substitute for set -o braceexpand.
+
+ * jobs.c(j_exit): when restoring tty process group, also restore
+ our process group.
+
+ * config.h.bot: define JOB_SIGS iff we have modern signal and wait
+ routines.
+ * jobs.c: use ifdef JOB_SIGS instead of ifdef JOBS when setting
+ signal masks and routines or using waitpid; define TTY_PGRP and
+ NEED_PGRP_SYNC separately from JOBS.
+ * jobs.c(j_kill): only send SIGCONT if job is stopped.
+ * jobs.c(j_jobs): remove exited/signaled jobs even if !FMONITOR,
+ un-ifdef JOBS same.
+
+ * jobs.c(check_job): ifdef FNOTIFY with JOBS (noted by
+ Michael Haardt <u31b3hs@POOL.Informatik.RWTH-Aachen.DE>).
+ * jobs.c(j_notify,j_waitj): put ifdef JOBS around use of FMONITOR.
+ * main.c(shell): removed ifdef JOBS from declaration of interactive.
+
+ * ksh_limval.h,sh.h: moved include of <limits.h> from ksh_limval.h
+ to sh.h since some machines define CLK_TCK there.
+
+Thu Sep 15 09:58:14 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * vi.c(vi_cmd): took out ESC as file completion char in command mode
+ (too annoying).
+
+ * jobs.c(exchild): if starting a job in the background and FBGNICE
+ is set, call nice().
+
+ * Makefile.in: changed maxext to manext (fix from Thomas Gellekum
+ <thomas@ghpc8.ihf.rwth-aachen.de>); in the install target, check
+ if the path of the installed shell is in /etc/shells and
+ complain if it isn't; added depend target, removed old $(OBJS)
+ and trap.o dependencies.
+
+Wed Sep 14 09:39:55 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * misc.c(options[]): changed position of vi-tabcomplete option.
+
+ * lex.c(yylex): ifdef use of FVI/FEMACS/FGMACES with VI/EMACS
+ (fix from Gordan Larson <hoh@approve.se>).
+
+ * ksh.1(DESCRIPTION): added missing P in \fP
+ (fix from Gordan Larson <hoh@approve.se>).
+
+Tue Sep 13 11:01:47 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.0.6 distribution
+
+Mon Sep 12 11:39:07 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(scriptexec): changed so it will compile if SHARPBANG is
+ defined; fixed error message; shell argument is everything up to
+ a newline; don't listen to #! line unless it ends in a newline.
+
+ * syn.c(get_command): case FOR: changed VARASN to ARRAYVAR.
+
+ * jobs.c(waitfor): restore signal mask before returning if named job
+ isn't own own; when waiting for unspecified jobs, only consider
+ running jobs; don't pass JW_STOPPEDWAIT flag to j_wait.
+
+ * table.h(V_TMPDIR,tmpdir): new define/variable.
+ * var.c(setspec, unsetspec): added case for V_TMPDIR.
+ * io.c(maketemp): use tmpdir variable if it is set, else use /tmp.
+
+ * var.c(popblock): if poping a variable that wasn't set in the old
+ environment, call unsetspec().
+
+Fri Sep 9 10:37:18 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(PATHMAX): increased value from 256 to 1024.
+
+ * main.c(main): moved initialization of name to start of main;
+ if getcwd() fails, use name in error message and call shf_flush().
+
+ * io.c(maketemp): check/use TMPDIR variable instead of /tmp; allocate
+ temp structure and path in one chunk.
+
+ * c_ksh.c(c_cd): when checking for no home directory, compare
+ against null, not (char *) 0.
+
+Thu Sep 8 10:52:59 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.c(yylex): case SHIST: flush shl_out after printing command.
+
+ * jobs.c(check_job): when notifing, do not remove job if it is stopped.
+
+Wed Sep 7 10:55:35 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.0.5 distribution
+
+ * main.c(shell): commented out shf_flush(shl_out) - shouldn't be
+ needed since -v flushes itself.
+
+Tue Sep 6 09:30:57 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * sh.h(LSHELL,really_exit): new define/variable.
+ * c_sh.c(c_exitreturn): if how is LEXIT, check if there are stopped
+ jobs and, if so, unwind with LSHELL (also check/set really_exit).
+ * main.c(shell): added case for LSHELL - stop unwinding if shell
+ is interactive; changed local reallyquit to global really_exit.
+ * main.c(include),exec.c(comexec): added case for LSHELL.
+
+ * c_sh.c(c_exitreturn): quitenv() for LRETURN as well as LEXIT.
+
+ * sh.h(TF_CHANGED): new define.
+ * trap.c(runtrap): default EXIT/ERR trap during execution and restore
+ original if TF_CHANGED not set.
+ * trap.c(settrap): set TF_CHANGED when setting trap.
+
+ * jobs.c(j_stopped): check that job created by current process; print
+ "You have stopped jobs" message.
+ * main.c(shell): don't print you have stopped jobs message.
+
+ * main.c(initcoms): removed echo alias.
+ * c_ksh.c(kshbuiltins): added echo as a builtin.
+ * c_ksh.c(c_print): if wp[0] is echo, act like strict sysv echo;
+ added \a (BEL) escape sequence.
+
+ * syn.c(function_body): new function; calls get_command() to get
+ the body of a function (old code did nested { } block which
+ caused problems with how redirections after the block were
+ handled).
+ * syn.c(get_command): call function body to deal with foo() and
+ function foo.
+
+ * jobs.c(restore_ttypgrp): new variable.
+ * jobs.c(j_change): set restore_ttypgrp if process group is set.
+ * jobs.c(j_exit): if necessary, restore tty process group for main
+ shell.
+
+Fri Sep 2 21:32:03 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * main.c(main): set FPRIVILEGED if uid (gid) doesn't match euid (egid);
+ don't include $HOME/.profile if FPRIVILEGED; include
+ /etc/suid_profile instead of $ENV if FPRIVILEGED.
+ * misc.c(change_flag): if clearing FPRIVILEGED flag, set euid (egid)
+ to uid (gid).
+
+Fri Sep 2 21:10:23 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * main.c(main): don't include $ENV if uid (gid) doesn't match
+ euid (egid) (from J.T.Conklin).
+
+Fri Sep 2 12:07:14 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * syn.c(get_command): removed MPAREN case, taken care of in '(' case,
+ as per POSIX.2 which says () is two operators, not one.
+ * lex.c(yylex): don't check for/return MPAREN.
+ * lex.h(MPAREN): deleted define.
+
+ * configure.in: add test for library routine confstr(); add
+ header test for paths.h.
+ * sh.h: include paths.h if available; define DEFAULT__PATH.
+ * table.h(def_path): new variable.
+ * options.h(DEFAULT_PATH): new define.
+ * main.c(main): initialize value of def_path; set path to def_path;
+ remove PATH initalization from initsubs; do not set value of HOME
+ variable (POSIX); allow SHELL, PS1, PS2, PS3 to have empty values
+ (at&t ksh).
+ * var.c(unsetspec): when unsetting PATH, set path to def_path.
+
+ * jobs.c(j_waitj): restore proc mask before calling error if
+ 1st tcsetpgrp() fails.
+
+Thu Sep 1 10:28:03 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * Makefile.in: added check-sig.c, check-fd.c and check-pgrp.c
+ to RCSFILES; added rules for compiling the above; added debugtools
+ target to compile them all.
+
+ * c_test.c(arg0,t_error,T_ERR_EXIT): new variables/defines.
+ * c_test.c(c_test): set arg0 to wp[0], t_error to 0; after
+ calling eval_binop() or oexpr() check t_error and if set,
+ return T_ERR_EXIT.
+ * c_test.c(syntax): set t_error exit; use shellf() instead of
+ errorf(); use arg0 instead of "test"; delete GCC_FA_NORETURN
+ attribute; changed all calls to return after calling.
+ * c_test.c(oexpr,aexpr,primary): check terror after calling
+ oexpr(), aexpr(), nexpr().
+
+ * c_test.c(primary): if unary operator is -t and there is no
+ argument, don't increment t_wp; if missing closing parenthesis,
+ show next operand (if any) in error message.
+ * c_test.c(eval_unop): default case, print t_wp[-2] (was -1).
+ * c_test.c(c_test): set t_wp before calling eval_binop() incase
+ there is an erorr.
+ * c_test.c(syntax): print first message even if op is an empty string.
+
+Wed Aug 31 11:48:51 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * expr.c(O_LT, O_GT): reverse order of enums to match opinfo table.
+
+ * c_test.c(nexpr): always call !nexpr() (never !primary()).
+ * c_test.c(c_test): switch on argc-1 to make code match POSIX
+ description; make 4 arg case fall into 3 arg case, and 3 arg case
+ fall into 2 arg case.
+ * c_test.c(is_not,is_and,is_or): new defines.
+ * c_test.c(c_test,oexpr,aexpr,nexpr): use is_not,is_and and is_or.
+ * c_test.c(primary): don't decrement t_wp in final string case.
+
+ * c_test.c(eval_unop): change S_ISIFO to S_ISFIFO and S_ISFITO
+ to S_ISFIFO.
+
+ * misc.c(parse_args): use OF_SET when initializing set_opts.
+
+Wed Aug 31 09:32:39 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * made pdksh-5.0.4 distribution
+
+ * jobs.c(j_change): do not restore tty process group when turning
+ off job control; no need to save original tty process group;
+ deleted orig_ttypgrp variable. (fixes bug in which turning off
+ job control causes an interactive shell to exit)
+
+Tue Aug 30 14:43:48 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * aclocal.m4(KSH_OPENDIR_CHECK): always include sys/types.h;
+ set return value according to what failed.
+
+Tue Aug 30 11:17:09 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * missing.c(strerror): for systems without sys_errlist[], report
+ error number if unknown.
+
+ * Makefile.in: added BUG-REPORTS to DISTFILES.
+
+ * jobs.c(exchild): do tcsetpgrp() in both parent and child after
+ the first process is created (may need to change to every child).
+
+ * aclocal.m4(KSH_PGRP_SYNC): new test - defines NEED_PGRP_SYNC.
+ * acconfig.h: added define for NEED_PGRP_SYNC.
+ * configure.in: use KSH_PGRP_SYNC test.
+ * jobs.c(exchild,j_startjob,j_sync_open,j_sync_pipe): if NEED_PGRP_SYNC
+ is defined, use a pipe to block the first process in a pipeline
+ until the whole pipeline is set up.
+
+Mon Aug 29 09:15:00 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * jobs.c(exchild): for background, unmonitored jobs, don't open
+ /dev/null if input is a pipe.
+
+ * jobs.c(exchild): for background, unmonitored jobs, use setsig()
+ instead of setexecsig() to set up SIGQUIT and SIGINT; changed
+ restoration of SIGTSTP,SIGTTIN,SIGTTOU - set them to DFL if
+ monitoring and not a `..` command, otherwise leave them alone.
+ * jobs.c(j_init): only use SIGTSTP,SIGTTIN,SIGTTOU if talking
+ or monitoring - if just talking leave signals ignored.
+ * jobs.c(j_change): if going into job control, set TF_SHELL_USES
+ flag for sigtraps[SIGTSTP,SIGTTIN,SIGTTOU]; if leaving job control
+ ignore signals if interactive, else restore original signals.
+
+ * table.h(SPEC_BI, REG_BI): new defines.
+ * exec.c(builtin): check for * or + in front of builtin names and set
+ SPEC_BI or REG_BI if found.
+ * exec.c(findcom): search for special builtins first, then functions,
+ then regular builtins, then PATH search.
+ * c_sh.c(shbuiltins[]),c_ksh(kshbuiltins[]): add */+ in front of POSIX
+ special/regular builtins; add = infront of unset;
+ remove = from alias.
+ * c_sh.c(c_label): set exit value according to name (for true/false).
+ * c_sh.c(shbuiltins[]): add entries for true and false.
+ * main.c(initcoms[]): deleted true/false aliases.
+
+ * aclocal.m4(KSH_OPENDIR_CHECK): new test - see if opendir() will
+ open non-directories.
+ * configure.in,acconfig.h: added KSH_OPENDIR_CHECK.
+ * missing.c,ksh_dir.h(ksh_opendir): new define/function.
+ * eval.c(globit),emacs.c(compl_file): use ksh_opendir() instead of
+ opendir().
+
+ * main.c(include): save source filename since search() uses line[]
+ for the filename and shell() trashes line[].
+
+ * table.h(FINUSE,FDELETE) new defines.
+ * exec.c(execute): case CFUNC: re-arranged code so normal return goes
+ through setjmp() switch; use FDELETE/FINUSE flags to avoid problems
+ with a function being undefined or redefined during its execution.
+ * exec.c(define): if FINUSE is set, set FDELETED and find a new table
+ entry.
+
+Fri Aug 26 21:58:25 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.c(getsc_): flush output after write when echoing.
+
+ * Makefile.in(dist): after creating distrubution, use pathchk -p
+ to check file names.
+
+Fri Aug 26 10:28:20 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * made pdksh-5.0.3 distribution
+
+ * expr.c(IS_ASSIGNOP): new define.
+ * expr.c(evalexpr): use IS_ASSIGNOP (bug fix).
+
+ * exec.c(execute): case TFOR,TSELECT: change e->type just
+ before setjmp() to avoid problems with bad jmpbufs.
+
+ * jobs.c(startlast): new function.
+ * jobs.c(waitlast): print error if job not started.
+ * eval.c(comsub): call startlast() after execute().
+ * jobs.c(exchild,j_startjob,j_sync_pipe): when starting a pipeline
+ use a pipe to ensure the first process doesn't die before
+ the last process is started.
+
+ * exec.c(execute): case TFUNC: set/clear FXTRACE according to
+ tp->flag & TRACE, and restore old value when function completes.
+
+ * c_test.c,exec.c,io.c,mail.c,vi.c: changed all uses of
+ (x&S_IFMT) == S_IF* to the equivilent S_IS* (for ISC unix).
+ * c_test.c(eval_unop): if system doesn't have symlinks or sockets
+ (S_ISLNK,S_ISSOCK), return 0 (used to cause internal error).
+ * ksh_stat.h(S_ISVTX): define if sys/stat.h doesn't.
+
+ * sigaction.c(Signal,signal): ifdef'd Signal() and signal() out as
+ they cause header file conflicts on some systems (eg, signal()
+ in ISC unix); also ifdef'd out other routines not used by ksh
+ (ie, sigdelset, sigfillset, sigismember, sigpending).
+
+Thu Aug 25 11:50:03 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * c_test.c(primary): always check that *t_wp isn't 0 before using it.
+
+ * eval.c(homedir): cache home directory values.
+
+ * exec.c(findcom): search builtins before tracked aliases.
+
+ * table.h(commands,taliases): changed name of commands to taliases.
+
+ * c_ksh.c(c_unalias): changed to use ksh_getopt(); added -t and -a
+ options; exit with non-zero value if non-alias name unaliased
+ (POSIX).
+
+ * main.c(initcoms[]): alias hash to 'alias -t -'; added autoload alias
+ as well; set selected comands to be tracked aliases (eg, grep,
+ ls, who, vi, emacs, etc.).
+
+ * c_ksh.c(c_alias): when printing aliases, check ISSET, not DEFINED
+ flag (so unset tracked aliases won't cause problems); changed
+ to use ksh_getopt(); added -t flag; added -x flag (does nothing).
+ * c_ksh.c(c_hash): deleted function; removed all references.
+
+ * table.h(CTALIAS): new define.
+ * exec.c(findcom): added search argument for whence -p; fixed
+ introduced bug preventing tracking of commands when insert set;
+ changed all calls; when creating tracked aliases, set type to CTALIAS
+ (was CEXEC).
+ * exec.c(findcom,flushcom): when freeing old tracked aliases, use
+ APERM, not commands.areap.
+ * c_ksh.c(c_whence): changed to use ksh_getopt(); assume findcom()
+ never returns 0 and never returns tp->type == CNONE; made output
+ closer to at&t ksh output; combined vflag/!vflag switch statements;
+ added case for CTALIAS.
+ * exec.c(findcom): set tracked alias type to CTALIAS.
+
+ * c_ksh.c(c_print): added -s option; changed to use Xstring() routines
+ and write() instead of shf routines; returns non-zero if there
+ was a write error.
+
+ * jobs.c(struct job): changed pid_t lpid field to Proc *last_proc;
+ changed all uses.
+ * jobs.c(check_job): use j->last_proc instead of lp.
+ * jobs.c(j_waitj): when checking for fake ^C, test j->last_proc
+ status to see if it was signaled and use WTERMSIG to get signal.
+
+ * main.c: initialize integer TMOUT parameter to 0; call alarm_init()
+ if FTALKING.
+ * trap.c(alarm_init,alarm_catcher): new functions.
+ * trap.c(runtraps): if ksh_tmout_leave is set, exit.
+ * sh.h(TMOUT_EXECUTING,TMOUT_READING,TMOUT_LEAVING,ksh_tmout,
+ ksh_tmout_state): new enum values/variables.
+ * table.h(V_TMOUT): new define.
+ * var.c(special,setspec,unsetspec): added V_TMOUT entry.
+ * edit.c(x_getc): call intrcheck() if read interrupted.
+
+Thu Aug 25 09:36:54 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * c_test.c(c_test): for argc cases 4 and 5, return the complement
+ of the expressioin result; for [[ .. ]] expressions, don't parse
+ based on argc; deleted struct t_op.op_flags field and related
+ defines UNOP,BINOP,ACCEPT_BE,ISTEST,ISDBRACKET,ISBOTH - changed all
+ uses to test the value of isdbracket.
+ * c_test.c(filstat): moved body of filstat() into eval_unop(), deleted
+ filstat().
+
+ * c_test.c: incorperated changes from J.T. Conlin (jtc@cygnus.com)
+ for POSIXization of test builtin.
+
+Wed Aug 24 12:16:25 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * new-version.sh: new file - updates date in version.c
+ * Makefile.in: added new-version.sh to RCSFILES; call new-version.sh
+ in dist: target.
+
+Tue Aug 23 09:28:10 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * made pdksh-5.0.2 distribution
+
+ * jobs.c(exchild): don't call restoresigs().
+ * exec.c(execute): do call restoresigs() (just before execve()).
+
+ * c_sh.c(c_trap): ifdef'd out code to print default traps. Too ugly
+ and it isn't clear POSIX needs it.
+
+ * c_ksh.c(c_print): put -e option back in for echo emulation (-R).
+
+ * c_sh.c(c_shift): generate error if n < 0.
+
+ * c_sh.c(c_trap): use shellf instead of errorf to report errors
+ (so we can return a value instead of unwinding - POSIX).
+
+ * exec.c(execute): if command fails and !FERREXIT, call
+ trapsig(SIGERR_).
+
+ * c_sh.c(c_exit,c_return,c_exitreturn): combined c_exit and c_return
+ functions.
+
+Mon Aug 22 09:39:54 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * eval.c(expand): case XARG: set word to IFS_WORD to force null
+ words to be created.
+
+ * lex.c(yylex): added indquotes flag and code for `...` parsing
+ (to deal with "`...`" the way sh/at&t-ksh does).
+
+ * trap.c(runtrap): don't reset ERR trap.
+
+ * sh.h: combnied SS_USER/SS_FORCE flags with RESTORE_CURR et. al.
+ flags; changed RESTORE_* to SS_RESTORE_*.
+ * trap.c(setsig): combined restore, force and user flags; changed
+ all calls.
+ * trap.c(setexecsig): use SS_RESTORE_* values as argument.
+
+ * table.h: changed LCASE to LCASEV (and UCASE_AL to UCASEV_AL) to
+ avoid problems with ioctl/tty LCASE define.
+
+ * sh.h(struct env): deleted interactive field.
+ * main.c(shell),exec.c(iopsetup): deleted e->interactive assignment.
+
+ * sh.h: made e a struct env * (was struct env); changed all references.
+ * main.c(newenv): copy loc, flags and interactive fields explicitly.
+
+ * var.c(newblock): allocate block structure, don't assume already
+ exists.
+ * var.c(popblock): free old block structure.
+ * main.c(main): set e->loc to 0 before calling newblock().
+ * exec.c(comexec): let newblock() allocate new structure; deleted
+ l variable (changed references to e->loc).
+ * table.h: deleted globals variable.
+
+ * c_ksh.c(c_print): treat a lone - like --.
+
+ * main.c(main),trap.c(inittraps): move SIGINT,SIGQUIT,SIGTERM signal
+ initialization to inittraps() and do it regardless of FTALKING.
+
+ * trap.c(settrap): deleted force trap since probably will never
+ add -f flag to trap.
+
+ * main.c(unwind): if we are dieing of SIGINT or SIGTERM, kill
+ ourselves with a signal.
+
+ * vi.c(x_vi),emacs(x_emacs): if ^C read, call trapsig()/runtraps();
+ don't return -2.
+ * edit.c(x_read): don't check for -2 return value.
+ * vi.c(x_vi): check for quit char (^\) and fake SIGQUIT.
+
+ * exec.c(comexec): made flags argument volatile.
+
+ * misc.c(getn_): new function.
+ * c_sh.c(c_exit,c_return): call quitenv() before unwind()ing;
+ moved c_exit() next to c_return(); use getn_() instead of getn().
+
+ * main.c(shell): added exit_atend argument to deal with POSIX trap exit
+ semantics; changed all calls.
+ * sh.h: added STOP_RETURN macro.
+ * c_sh.c(c_return): determine if we are returning or exiting before
+ unwind()ing so POSIX trap exit semantics are honored.
+
+ * jobs.c(j_sigchld): call trapsig() instead of messing with sigtraps[].
+ * trap.c(trapsig): don't restore signal handler if it wasn't set to
+ trapsig.
+
+ * sh.h: added TF_TTY_INTR flag.
+ * trap.c(inittrap): set TF_TTY_INTR for SIGINT.
+ * jobs.c(j_waitj): if
+
+ * jobs.c,sh.h: deleted SA_RESTART ifdefs; moved SIGCLD->SIGCHLD ifdefs
+ from jobs.c to sh.h.
+
+Sat Aug 20 15:26:24 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * sh.h: added KSH_SA_FLAGS define.
+ * trap.c(inittrap,setsig): set sa_flags field to KSH_SA_FLAGS.
+ * sigact.c(sigaction): if using BSD42_SIGNALS, set the SV_INTERRUPT
+ flag.
+ * configure.in: deleted AC_RESTARTABLE_SYSCALLS.
+ * config.h.bot: add error message to prevent compilation if using
+ BSD41 signals.
+
+ * shf.h: added SHF_INTERRUPT flag.
+ * shf.c(shf_fillbuf,shf_putchar,shf_write,shf_emptybuf): call
+ intrcheck() if read()/write() interrupted and SHF_INTERRUPT set.
+ * c_sh.c(c_read): use SHF_INTERRUPT flag.
+ * lex.c(getsc_): call intrcheck() if read() interrupted.
+
+ * main.c(main),trap.c(inittrap): moved Sigact* initialization
+ from main() to inittrap(); made Sigact_trap/Sigact_ign static;
+ deleted Sigact and Sigact_dfl.
+ * sh.h: deleted declarations of Sigact*.
+
+ * main.c(shell): deleted sigaction call - no longer needed.
+
+ * sh.h: added RESTORE_CURR, RESTORE_ORIG, RESTORE_DFL and RESTORE_IGN
+ defines.
+
+ * trap.c(intrcheck): new function.
+ * trap.c(runtraps): added intr argument; clear trap/intrsig
+ before running traps; changed all calls.
+ * trap.c(runtrap): save/restore exstat when running trap.
+ * jobs.c(j_waitj): changed interrupt test to check intrsig
+ and return -1.
+ * jobs.c(waitfor): if j_waitj() returns -1, call intrcheck().
+ * jobs.c(j_change): use setsig() instead of sigaction() to
+ set up SIGTTIN,SIGTTOU,SIGTSTP.
+
+ * trap.c(inittraps): initialize flags for INT/QUIT/TERM.
+ * sh.h(intrsig): new variable.
+ * trap.c(trapsig): set intrsig if signal has TF_DFL_INTR flag set;
+ deleted longjmp().
+ * trap.c(runtraps): clear intrsig.
+ * trap.c(runtrap): if signal is defaulted and TF_DFL_INTR is
+ set, set exstat and call unwind(); return if signal ignored;
+ reset an ERR trap before executing it.
+ * trap.c(cleartraps): deleted special case for EXIT; reset
+ command traps using settrap(); clear intrsig.
+ * trap.c(restoresigs): only deal with traps that have the TF_EXEC_IGN
+ flag set (others take care of themselves).
+
+ * trap.c(sigtraps[]): added ERR trap.
+ * trap.c(gettrap): deleted #if 0'd ERR/EXIT check.
+ * trap.c(gettrap,runtrap,cleartraps,restoresigs): use SIGNAL+1 to
+ go through trap table.
+ * sh.h(SIGEXIT_,SIGERR_): new defines.
+ * c_kill.c(c_kill): test for signals > 128 (was >= 128)
+ * c_sh.c(c_trap): when printing traps, use SIGNALS+1.
+
+ * sh.h(struct trap): replaced ourtrap and sig_dfl fields with
+ flags field; defined TF_SHELL_USES, etc. for flags field; added
+ cursig field.
+ * sh.h(struct env): replaced func_parse field with
+ flags field; defined EF_FUNC_PARSE, EF_BRKCONT_PASS for flags
+ field; defined STOP_BRKCONT(); changed uses of func_parse
+ (get_command()/readhere()).
+ * c_sh.c(c_brkcont): use STOP_BRKCONT(), EF_BRKCONT_PASS; call
+ unwind() to do the work.
+
+Fri Aug 19 09:59:43 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * trap.c(settrap): new function - does most of what c_trap did.
+ * c_sh.c(c_trap): changed to use settrap(); print out default
+ actions as well as caught/ignored actions.
+ * trap.c,c_sh.c(setsig): moved setsig from c_sh.c to trap.c and
+ made it static; added force and user arguments; don't do anything
+ for EXIT and ERR.
+ * main.c(main): use sigtrap() instead of ignoresig().
+ * trap.c(ignoresig): deleted function.
+
+ * exec.c(execute): case TSELECT: don't change SIGINT signal handler.
+
+ * proto.h: use GCC_FA_NORETURN on aerror().
+ * syn.c(yyparse): made function static and void.
+
+ * exec.c(herein): changed error() calls to errorf()s; use error()
+ in error handler; call shf_close() instead of shf_fdclose().
+
+ * tree.h,sh.h: moved LBREAK/LCONTIN from tree.h to sh.h;
+ added LEXIT, LRETURN, LERROR, and LINTR defines; changed values
+ of LBREAK/LCONTIN; changed all calls to longjmp() and setjmp().
+ * exec.c(execute): put Break: label after main switch, changed
+ goto Break[0-9] to Break; deleted Break[0-9] labels.
+ * exec.c(execute): changed FOR, SELECT, WHILE, DO loop setjmps
+ to explicitly check for LBREAK/LCONTIN, otherwise call unwind().
+ * exec.c(execute): case TFUNC: added setjmp switch statement to take
+ care of various L* values.
+ * main.c(include,shell): added setjmp switch statement to take care of
+ various L* values.
+ * main.c(unwind): added L* parameter to pass on to longjmp();
+ changed all calls.
+ * c_sh.c(c_return): just call unwind(LRETURN);
+ * main.c(unwind): put code from leave() in E_NONE case.
+ * main.c(error,leave): deleted functions; replace all calls with
+ unwind(LLEAVE or LERROR).
+ * *.c(longjmp): replaced all calls with unwind(L..) (except the
+ call in unwind()).
+
+ * shf.h: add areap field to struct shf.
+ * shf.c(shf_fdopen,shf_sopen): initialize areap
+ * shf.c(shf_emptybuf,shf_close,shf_sclose,shf_finish): use areap
+ instead of ATEMP.
+
+ * shf.c(shf_sopen): if buf is 0 and writing and DYNMAIC, allocate
+ a buffer; if writing, save room for a trailing null.
+ * shf.c(shf_sclose): new function.
+ * shf.c(shf_snprintf),tree.c(snptreef): use shf_sclose().
+ * tree.c(snptreef): changed return type to char *; if buffer
+ is null, pass SHF_DYNAMIC to shf_sopen(); return (possibly
+ allocated) string.
+ * syn.c(syntaxerr): use snptreef() instead of ident.
+
+ * tree.h: new define TDBRACKET; new defines DB_NORM,DB_OR,DB_AND,
+ DB_BE,DB_PAT.
+ * tree.c(ptree): added case for TDBRACKET.
+ * syn.c(get_command): case DBRACKET: make TDBRACKET command;
+ at end of function, null terminate args/vars if TDBRACKET.
+ * c_test.c(is_db_patop): new function.
+ * syn.c(db_primary): set type of arg to DB_PAT if is_db_patop()
+ returns true.
+ * exec.c(execute): added case for TDBRACKET.
+
+ * syn.c(get_command): case MDPAREN: make arg[0] an allocated stuffed
+ string (was a literal string).
+
+Thu Aug 18 11:06:49 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * c_test.c(is_op,is_binop,is_unop): new functions to test for
+ unary/binary operators.
+ * c_test.c(oexpr,aexpr,nexpr,primary): no longer take argument -
+ call is_unop/is_binop directly.
+ * c_test.c(ISDBRACKET,ISTEST,ISBOTH,ACCEPT_BE): new defines.
+ * c_test.c(struct t_op): changed op_type field to op_flags.
+ * c_test.c(ops[]): broke into two arrays: u_ops and b_ops;
+ set flag field to ISDBRACKET/ISTEST/ISBOTH as appropriate.
+ * c_test.c(t_lex): deleted function.
+ * c_test.c(primary): before checking for unary operator, check
+ for -BE (binary expression next) if appropriate.
+
+ * c_test.c: made operator type an enum to make it easier to add
+ operators; changed oexpr/aexpr/nexpr/primary/filstat/t_lex
+ operand/return types.
+ * c_test.c(struct t_op): changed op_text from char * to char [4].
+ * c_test.c(primary): case FILTT: using digit(**t_wp) causes core dump
+ when there is no arg - just test if *t_wp is 0; move test into
+ filstat().
+ * c_test.c(primary): use *opnd1 != 0 instead of strlen(opnd1) (3
+ instances).
+
+ * tree.c(wdscan,wdcopy): changed string argument to const; changed
+ return type of wdscan to const.
+
+ * eval.c(expand): case CSUBST: case '?': use st->var->anme instead
+ of cp to allow proper nesting.
+ * eval.c(expand): case OSUBST: don't change cp - declare local
+ variable.
+ * eval.c(expand): case COMSUB: deleted Xsavepos() call; XCOM: deleted
+ Xrestpos() call.
+ * eval.c(expand): save the position of the first unquoted = for tilde
+ expansion.
+ * eval.c(expand): case '~': use sp == (cp+2) instead of dp == Xstring
+ so we aren't fooled by ''~ or ${foo}~; sp[-1]/sp[-2] and firsteq
+ for the DOASNTILDE after first = or unquoted : test.
+
+ * tree.h(struct op): changed noexpand field to evalflags; changed
+ all uses.
+ * exec.c(execute): if t->evalflags is non-zero, pass them to eval().
+
+ * lex.h: added DBRACKET define for [[ keyword.
+ * syn.c(get_command): added case for DBRACKET.
+ * syn.c(db_parse,db_oaexpr,db_nexpr,db_primary): new functions
+ to parse [[ .. ]] expressions.
+ * syn.c(tokentab[]): added [[/DBRACKET keyword.
+ * c_test.c(is_db_unop,is_db_binop): new functions to test if arg
+ is [[ .. ]] unary/binary operator.
+
+ * syn.c(syntaxerr): call REJECT; before token; removed REJECT
+ before all calls to syntaxerr().
+ * syn.c(get_command): removed syntax error kludge.
+
+Wed Aug 17 11:07:40 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca)
+
+ * c_test.c(struct ops): changed -U to -O.
+
+ * tree.h: added new defined DOASNTILDE.
+ * eval.c(expand): case '~': changed !(f&DOBLANK) to f&DOASNTILDE.
+ * exec.c(execute): case TCOM: pass DOASNTILDE when evaluating vars
+ and when expanding args when t->noexpand is set.
+
+ * exec.c(execute): case TCASE: pass DOTILDE to both evalstr() calls
+ (POSIX).
+
+ * syn.c(get_command): don't check for redirections before determining
+ command type; handle REDIR where LWORD are handled; default case
+ always returns; pass ARRAYVAR flag to tpeek() if t->noexpand;
+ don't allow redirections before "x()" function; don't allow
+ redirection before keywords; allow a '(' in the case LWORD/REDIR:
+ if no variables or arguments (POSIX doesn't allow this, but
+ at&t ksh/bourne sh do).
+ * lex.h(ARRAYVAR): new define.
+ * lex.c(yylex): parse x[1 & 2] as one word if VARASN|ARRAYVAR.
+
+ * var.c(is_wdvarname): added aok argument; changed all calls.
+ * syn.c(get_command): case FOR/SELECT: check identifier is valid.
+
+ * c_sh.c(c_trap): use print_value_quoted() to print traps;
+ use ksh_getopt() to skip possible --.
+ * trap.c(gettrap): allow digits only if signal numbers match
+ POSIX values (ie, HUP=1,INT=2,QUIT=3,ABRT=6,KILL=9,ALRM=14,TERM=15).
+
+ * misc.c(print_value_quoted): new function to print strings with
+ appropriate quoting.
+ * c_ksh.c(c_typeset,c_alias): use print_value_quoted() when printing
+ values.
+
+ * lex.h: added new ESACONLY flag - only accept ESAC keyword.
+ * lex.c(yylex): check for ESACONLY flag when doing keyword search.
+ * syn.c(caselist): pass ESACONLY flag to tpeek().
+
+Tue Aug 16 10:17:47 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.h(struct source); changed echo field to generic flags field;
+ defined SF_ECHO and SF_ALIASEND.
+ * lex.c(_getsc): case SALIAS: use isspace() to check for trailing
+ space in alias; if alias does not end in a space, return a fake
+ space to keep the current alias in the source list - this allows
+ recursive alias detection; set SF_ALIAS in next source if alias
+ does end if space.
+ * lex.c(yylex): deleted expanding_alias, rec_alias_cnt and
+ rec_alias_table variables; check for recursive aliases by checking
+ the source list; don't use EXPALIAS flag; if SF_ALIAS is set in
+ current source, set ALIAS flag and clear SF_ALAIS; deleted global
+ alias variable.
+ * table.h: deleted EXPALIAS define.
+ * main.c(shell): changed s->echo to set/clr of SF_ECHO in s->flags.
+
+ * table.h: added aliases and keywords tables; deleted lexicals table
+ (POSIX says they are separate).
+ * syn.c(keywords,initkeywords): changed name of keywords() function
+ to initkeywords(); changed all calls; add to keywords table instead
+ of lexicals table.
+ * main.c(main): initialized aliases/keywords tables; deleted
+ initialization of lexicals table.
+ * c_ksh.c(c_whence,c_alias,c_unalias),lex.c(yylex): use
+ keywords/aliases tables instead of lexicals table; removed unneeded
+ type == CALIAS checks.
+
+ * var.c(getint): new function, largely take from strint().
+ * var.c(strint): call getint() to do most of the work; if vq
+ was an allocated string, free the string and clear the alloc
+ flag.
+ * var.c(intval): call getint() instead of strint().
+
+ * mail.c(mcheck): use getint() instead of strint() when getting
+ value of MAILCHECK.
+
+ * var.c(skip_varname): added argument to allow array references;
+ changed all calls.
+ * var.c(set_array): remove valid variable name check - done in
+ parse_args().
+
+ * var.c(strint): check if getspec()/setspec() need to be called.
+ * var.c(intval): remove getspec()/INTEGER checks - let strint() do it.
+
+ * var.c(skip_wdvarname,is_wdvarname,is_wdvarassign): new
+ functions.
+ * lex.h(VARASN): new define indicating variable assignment expected,
+ currently used to parse "x[1 & 2]" as one token - may be used
+ in future in returning AWORD (assignment word) to the parser.
+ * lex.c(yylex): Subst: case '[': use is_wdvarname() in determining
+ whether to parse an array dereference; do not require an = after the
+ dereference (typeset -r x[1 & 2] is legal).
+ * syn.c(many functions): pass VARASN to token/musthave/tpeek/synio
+ when ever the next token might be the first word of a simple
+ command.
+ * syn.c(get_command): case LWORD: use is_wdvarassign() to distinguish
+ variable assignments from normal arguments; allow aliases after
+ redirections and variable assignments; generate syntax error
+ for "foo=bar bogusfunction()"; allow array variables in for
+ and select statements.
+ * lex.c: make global alias variable static (no longer used by
+ get_command()).
+
+ * syn.c(get_command): put MDPAREN into its own case.
+
+ * lex.c(yylex): deleted place holder for tilde expansion.
+
+ * tree.h(struct tree): added noexpand field for
+ alias/export/readonly/typeset.
+ * syn.c(newtp),tree.c(tcopy): initialize/copy noexpand field.
+ * syn.c(get_command): case LWORD: set noexpand if assign_command()
+ returns true.
+ * syn.c(assign_command): new function - returns true if command
+ is alias, export, readonly or typeset.
+ * exec.c(execute): case TCOM: don't pass DOTILDE flag when
+ expanding t->vars; don't do field splitting/globbing/tilde expansion
+ of t->args if t->noexpand is set.
+
+ * table.h: re-grouped the struct tbl flags into common, variable,
+ funtion, builtin/alias, etc (some values overlap); new flag
+ names: KEEPASN (was overloaded with TRACE) and NOEXPAND.
+ * exec.c(comexec,builtin): changed TRACE to KEEPASN.
+
+ * tree.c(tcopy): when copying t->str, use strsave(), not wdcopy()
+ if not copying a TCASE.
+
+ * c_ksh.c(c_typeset): added -p flag for POSIX export/readonly.
+
+ * eval.c(expand): expand tilde in place; don't do tilde expansion
+ on results of substitution (POSIX); only do tilde expansion
+ if login name is unquoted (POSIX); don't check for fdo&DOTILDE
+ when a word is completed.
+ * eval.c(tilde): changed to return home directory of a given login name
+ (taking care of null/+/-) (old version copied string, scanning
+ for, and replacing, magic tildes).
+
+ * exec.c(findcom): don't create commands table entries when
+ not inserting; when doing access test of tracked alias,
+ check for ISSET, not ALLOC (but test ALLOC before calling afree).
+ * exec.c(search): use strcpy() instead of loop.
+
+Mon Aug 15 14:46:58 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ksh.c(c_print): added at&t ksh -R flag; deleted -e flag;
+ changed to use ksh_getopt().
+ * main.c(initcoms[]): changed echo alias from 'print -' to 'print -R'.
+
+ * sh.h: new variable procpid; added pid field to struct temp.
+ * jobs.c: changed references to my_pid to procpid; deleted my_pid
+ variable.
+ * io.c(maketemp): initialize pid field from procpid.
+ * main.c(remove_temps): only remove temporary files created by
+ the current process.
+
+Sun Aug 14 11:47:05 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * var.c: added var field to struct Expand and struct SubType; deleted
+ struct SubType.name field.
+ * var.c(expand): case OSUBST: use x.var/st->var field to save result
+ of global() for use in case CSUBST - this avoids problems with
+ x[i+=1] being evaluated twice; check to see if value is being
+ assigned to non-variable (eg, ${*:=aja}) or read-only variable.
+ * var.c(varsub): set value of xp->var; possibly generate error if
+ FNOUNSET set when expanding ${#*}, ${#var}, or ${#array[*]}.
+
+ * table.h: added struct tbl.areap field to get rid of lastarea
+ problems; deleted lastarea variable; changted all refernces
+ to lastarea to var->areap.
+ * table.c(tenter): initialize areap field.
+ * var.c(arraysearch): deleted area parameter; initialize areap field.
+ * var.c(global,local,intval,setint): initialize areap field of vtemp.
+
+Fri Aug 12 10:54:51 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * expr.c: re-wrote e*() functions using single function and table;
+ token() now does table lookup; added full C expressions (sans
+ pre/post increment, sizeof).
+
+ * table.h(INT_U,INT_L): new flags.
+ * var.c(strval): handle INT_U.
+ * c_ksh.c(c_typeset): add exclusions for INT_U/INT_L;
+ add -U option for unsigned (non-at&t ksh).
+
+ * var.c(set_array): use global() instead of local();
+
+ * var.c(global): when parsing $123, don't limit number to 1000.
+
+ * expr.c(v_evaluate): like old evaluate, but assigns result to
+ specified variable.
+ * expr.c(evaluate): changed to use v_evaluate().
+ * expr.c(e0): when parsing literals, use strint() instead of setstr().
+
+ * var.c(setstr): when assigning to integers, use v_evaluate() instead
+ of strint().
+
+Thu Aug 11 11:33:17 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * var.c(strint): don't change anything until number completely
+ parsed; don't clear ISSET flag if parsing fails; fail parse
+ if non-integer/non-letter found; default base if copying from
+ integer variable; set ISSET if parsing succeeds.
+
+ * expr.c(tempvar): set vp->val.i to 0.
+ * expr.c(token): when skipping a literal number, don't allow _;
+ use isspace() to skip.
+ * expr.c(asn): use strint()/setstr() instead of setint().
+ * expr.c(e0): added unary ~ and + (posix).
+
+ * var.c(global,local): always call substitute on contents of [..];
+ free sub when finished eval.
+
+ * ksh_limval.h: new file.
+ * shf.c: use ksh_limval.h.
+ * Makefile.in: added ksh_limval.h to HDRS.
+
+Wed Aug 10 10:57:01 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ksh.c(c_whence): case CFUNC: print exported/traced/undefined,
+ do not print function definition.
+ * exec.c(findcom): new argument indicated if functions to be
+ autoloaded; changed all calls.
+
+ * misc.c(ksh_getopt): added # option modifier for typeset.
+ * c_ksh.c(c_typeset): changed to use ksh_getopt(); added
+ -L, -R, -Z, -l, -u flags; quote variable values when printing;
+ generally re-arranged function.
+ * table.h: added LJUST,RJUST,ZEROFIL,LCASE,RCASE; deleted FUNCT.
+ * exec.c(findfunc): new function.
+ * exec.c(define,findcom): use findfunc(); skeleton autoload code.
+ * var.c(typeset): added two arguments for initial field width and
+ base; changed all calls; deal with LJUST,.. flags; re-arrange to
+ have one loop iterating over array; do readonly check before
+ changing attributes.
+ * var.c(strval): deal with LJUST,.. flags when getting integers.
+ * var.c(setstr): deal with LJUST,.. flags when setting strings.
+ * var.c(setstr): deal with LJUST,.. flags when setting strings.
+ * var.c(arraysearch,tenter): initialize {new,p}->field to 0.
+ * table.h: added struct tbl.field.
+
+ * siglist.in: changed signal messages to be more or less the
+ same as sys_siglist[]; moved SIGUNUSED before SIGBUS.
+ * jobs.c(j_print): assume sigtrap[].mess always valid; use
+ sigtrap[].mess directly for stopped processes.
+
+ * aclocal.m4(KSH_SYS_SIGLIST): new macro like KSH_SYS_ERRLIST.
+ * configure.in: use KSH_SYS_SIGLIST instead of AC_SYS_SIGLIST_DECLARED.
+
+Tue Aug 9 10:28:45 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * version.c: removed RCS logs since version numbers don't match
+ release numbers.
+
+ * configure.in: added AC_PROG_CPP
+ * Makefile.in: added rules for generating siglist.out
+ * siglist.in,siglist.sh: new files.
+ * trap.c(inittraps,deftraps[]): deleted deftraps[]; initialize
+ sigtraps[] directly by including siglist.out.
+
+ * tree.h: added EXPRSUB for $((..)), re-numbered defines.
+ * tree.c(tputS,wdscan): added case for EXPRSUB.
+ * eval.c(expand,alt_count,alt_scan): added case for EXPRSUB.
+ * lex.h: added SDDPAREN for $((..)).
+ * lex.c(yylex): added case for SDDPAREN.
+
+ * lex.c(yylex): case SPAREN: match parenthesis using counter instead
+ of pushing/poping states.
+
+ * lex.h: re-numbered S* defines to be sequential; added SREREAD.
+ * lex.c(yyerror): pop SREREADs.
+ * lex.c(getsc_): added case for SREREAD.
+ * lex.c(arraysub): changed to save whatever is read and return a value
+ indicating if brackets matched; changed all calls.
+ * lex.c(yylex): if brackets in array reference are not balanced,
+ or if array reference not followed by an =, re-read the input.
+
+Mon Aug 8 21:20:08 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h: fix lseek prototype (from sjg@zen.void.oz.au)
+
+ * configure.in,jobs.c: added HAVE_SYSCONF; don't use sysconf() unless
+ HAVE_SYSCONF defined (for NetBSD-Feb12 from sjg@zen.void.oz.au).
+
+ * jobs.c(put_job): removed PJ_ON_END case and define (not used).
+
+Wed Jul 27 10:19:42 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-5.0.1 distribution
+
+ * exec.c(comexec): when doing exec has no command, fall through
+ and do variable assignments.
+
+ * c_ksh.c(c_let): complain if there are no arguments.
+
+ * exec.c(comexec): if XEXEC is set, exit after executing
+ (builtin/function) command; deleted FERREXIT code; don't set
+ exstat.
+ * exec.c(execute): handle FERREXIT; set exstat to rv for all
+ cases.
+
+ * lex.c,syn.c: changed calls to errorf to yyerror (except the
+ on in yyerror()).
+ * lex.h(struct source): added errline field.
+ * lex.c(yyerror): now a varargs function; if source->errline field is
+ non-zero, print it instead of source->line.
+ * syn.c(syntaxerr): set source->errline if read EOF in a multiline
+ command.
+
+Tue Jul 26 11:22:06 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * lex.h(SYNTAXERR,INP): deleted macros.
+ * lex.h(HISTORY): upped to 128 as per POSIX.
+ * lex.h,syn.c(multiline): moved from lex.h to syn.c.
+ * tree.c(vfptreef): added %R to format I/O redirections.
+ * tree.c(pioact): don't output unit more than once; print unit
+ only if it isn't the default for the action; print only
+ one opening quote for quoted here documents; print <> for
+ read-write (was ><).
+ * syn.c(zzerr, syntaxerr): replaced zzerr function with new syntaxerr
+ function; changed uses of SYNTAXERR to syntaxerr() calls; print
+ out the last unused token; if in a multiline command when EOF
+ encountered, print token that was unmatched.
+
+ * tree.h(TBANG),lex.h(BANG): for POSIX ! keyword
+ * tree.c(ptree),exec.c(execute): added case for TBANG.
+ * syn.c(restab[]): added ! keyword.
+ * syn.c(get_command): added case for BANG (!).
+
+ * tree.h(XERROK): new define to allow non-zero exits to be
+ ignored in certian circumstances (set -e).
+ * exec.c(execute): pass XERROK to recursive execute() calls; set
+ XERROK when evaluating conditional part of if/while/until/&&/||.
+ * exec.c(comexec): pass XERROK on to functions; don't exit if
+ XERROK is set.
+
+ * jobs.c(async_pid): new variable needed since async_job may go
+ away, but $! should still be expanded.
+ * jobs.c(j_async): return async_pid.
+ * jobs.c(j_set_async): set async_pid.
+
+Mon Jul 25 14:15:25 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * misc.c(parse_args): changed to use ksh_getopt().
+
+ * c_sh(c_set): deleted call to resetopts() - neither POSIX nor
+ at&t ksh touch OPTIND when positional parameters are changed.
+ * exec.c(comexec): changed resetopts() call to getopts_reset(1) - this
+ is not a proper fix - should declare a local optind.
+
+ * misc.c(builtin_getopt,ksh_getopt): renamed builtin_getopt to
+ ksh_getopt (shorter); changed to use state structure instead
+ of static variables; changed all calls; optionally allow + to
+ introduce an option; don't skip lone - (or +) in arguments - set
+ optind to point to it.
+ * misc.c(ksh_getopt_reset): changed to use state structure instead
+ of static variables.
+ * sh.h(Getopt): new structure for ksh_getopt() state.
+ * exec.c(call_builtin): call ksh_getopt_reset().
+ * c_ksh.c(c_getopts,getopts_reset): new getopts implementation that is
+ POSIX complient and uses ksh_getopt() routine.
+ * var.c(setspec): call getopts_reset() when OPTIND set.
+ * getopts.c: deleted file.
+ * Makefile.in: deleted getopts.c and getopts.o.
+ * options.h(FASCIST): deleted option.
+
+Thu Jul 21 09:52:03 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-4.9+mun.5 distribution
+
+ * syn.c(get_command),shf.c(shf_fdopen,shf_sopen,shf_emptybuf):
+ added cast to alloc()/aresize() calls.
+ * sh.h: changed enum flags_enum to enum flags.
+ * misc.c(change_flag): changed type of first argument to enum flag.
+ * edit.c(set_editmode): change type of static array to enum flag.
+
+ * edit.c(x_init): initialize tty chars to -1, except for werase,
+ which is set to ^W.
+ * edit(x_mode): split oldedchars structure declaration and
+ initalization (some old compilers don't like them combined).
+
+ * c_test.c: made -h work like -L.
+
+Wed Jul 20 11:12:52 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * config.h.top: moved compile options into options.h; include
+ options.h.
+ * options.h: new file.
+
+ * c_ksh.c(c_fgbg): if !FPOSIX, set return job exit status.
+
+ * jobs.c(j_jobs,j_notify): use JF_REMOVE to flag jobs to delete
+ after all notification done (to prevent multiple + or - jobs).
+
+ * jobs.c(put_job): new funtion, takes argument to specify where
+ to put job; changed all calls to put_job_on_front and
+ put_job_on_end to use this; put background processes just
+ after stopped jobs (instead of at end) (POSIX).
+ * jobs.c(put_job_on_front,put_job_on_end): deleted.
+
+Tue Jul 19 10:33:55 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * aclocal.m4(AC_MMAP): copied from autoconf's acspecific.m4 and
+ modified to use the MAP_FILE flag if available.
+
+ * misc.c(change_flag): ifdef use of FVI/FEMACS/FGMACS.
+
+Mon Jul 18 13:19:29 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(async): deleted.
+ * var.c(global): call j_async() instead of using async global.
+ * jobs.c(j_async,j_set_async()): new functions.
+ * jobs.c(j_startjob): new function.
+ * jobs.c(exchild,j_waitlast): call j_startjob() to start job.
+ * jobs.c(j_sigchld): if any jobs aren't started, note signal
+ occured and return without calling wait.
+ * jobs.c(j_resume): allow un-reported dead jobs to be fg'd/bg'd;
+ if backgrounding, set async job.
+ * jobs.c(j_jobs,j_notify,j_waitj,check_job,remove_job): re-wrote to
+ deal with posix `known processes'.
+ * jobs.c(j_lookup()): if number specified, see if it is a lpid first,
+ then check for pgrp.
+ * jobs.c(new_job): added functionality of j_newjob().
+ * jobs.c(j_newjob): deleted function and all calls.
+
+ * tty.c(tty_init,tty_close): new functions which initialize
+ tty_fd, tty_state and tty_devtty.
+ * jobs.c(j_init,j_change): use tty_init()/tty_close(); changed
+ references of ttyfd to tty_fd; moved tty_fd, tty_state and
+ tty_devtty to tty.h; call tty_init() if !FMONITOR; save/restore
+ tty modes on foreground job completion if FTALKING (was FMONITOR).
+ * edit.h(X_chars): new structure for tty driver characters (replaces
+ ed_erase, ed_kill, ed_werase, ed_intr, ed_quit); moved prototypes
+ for emacs.c and vi.c from proto.h to edit.h; changed all references
+ to ed_* to edchars.*.
+ * edit.c(x_mode): use tty_state instead of cborig; re-initialize
+ tty state from tty_state whenever entering xmode; save tty characters
+ in edchars structure.
+ * edit.c(x_init): now called from main(); initializes edchars, x_cols,
+ and calls x_init_emacs.
+ * emacs.c(x_init_emacs): changed erase,kill,werase,intr,quit arguments
+ to X_chars argument.
+
+Fri Jul 15 10:35:13 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * io.c(restfd): only flush if fd is 2; use dup2() instead of
+ fcntl(F_DUPFD) (2 system calls instead of 3).
+
+ * ksh_wait.h(WEXITSTATUS): changed mask from 0x7f to 0xff.
+
+ * tty.h: added TTY_state structure.
+ * tty.c: new file - contains get_tty() and set_tty().
+ * edit.c(x_init,x_mode): use get_tty() and set_tty().
+ * jobs.c(j_waitj,j_resume): save/restore tty modes of stopped
+ jobs, as per POSIX.1, B.2, job control; for foreground jobs,
+ save tty state after successful completion, restore tty state
+ after non-successful completion (signaled, non-0 exit, stopped).
+
+ * misc.c(options): changed "alternations" to "braceexpand" (this
+ is what bash uses - no need to invent new option names); changed
+ ALTERNATIONS define to BRACEEXPAND, same for FALTERNATIONS to
+ FBRACEEXPAND.
+
+ * jobs.c(check_job): if process died of SIGINT or SIGPIPE, leave
+ job state as PEXITED (not PSIGNALLED).
+ * jobs.c(j_print): if printing short notice, ignore SIGPIPE the
+ way SIGINT is ignored.
+
+ * exec.c(comexec): flush shl_out after 'not found' message.
+
+ * c_ksh(c_kill): re-wrote function (again) to handle posix
+ options (-s, --, etc.) and posix -l output.
+
+ * configure.in: added strcasecmp function check.
+ * missing.c(strcasecmp): define strcasecmp function if not available.
+ * trap.c(gettrap): use strcasecmp when comparing signal names.
+
+ * var.c(global): expand $! to nothing if there haven't been any
+ asynchronous processes started yet.
+
+ * misc.c(options): added posix option (set automatically if
+ POSIXLY_CORRECT env variable is set or if POSIXLY_CORRECT config
+ define is defined)
+ * var.c(special,setspec): added POSIXLY_CORRECT.
+ * config.h.top: added POSIXLY_CORRECT define.
+ * main.c(main): set FPOSIX if POSIXLY_CORRECT is defined.
+ * POSIX: new file describing what the posix flag controls.
+
+Thu Jul 14 10:53:15 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * jobs.c,sh.h(killpg): moved killpg define to sh.h.
+
+ * jobs.c(exchild): set async variable when starting background
+ processes.
+ * jobs.c(j_resume): print [job-num] before command when backgrounding;
+ print to stdout instead of stderr.
+ * c_ksh.c(c_fgbg): call builtin_getopt to skip possible --, complain
+ about unknown options.
+
+ * jobs.c(held_sigchld): new global variable.
+ * jobs.c(j_sigchld): if any jobs aren't started, set held_sigchld and
+ return.
+ * jobs.c(exchild,waitlast): after setting JF_START, if held_sigchld
+ set, call j_sigchld().
+
+ * jobs.c(exchild): added/initialized ppid field to struct job; deleted
+ global is_child.
+ * jobs.c(waitfor): don't wait for a process that isn't a child of
+ the current process.
+ * jobs.c(j_exit): kill stopped jobs owned by current process only.
+
+ * jobs.c(check_job): don't do monitor stuff for XXCOM jobs, but do
+ set up notification.
+ * jobs.c(j_waitj): added JW_NOTIFY flag to print job notification
+ messages.
+
+ * jobs.c(exchild): remove !XPIPEI condition - we now close pipe so
+ pipeline doesn't have to call waitlast().
+ * exec.c(execute): case TPIPE: no need to restore 0 or call waitlast()
+ since exchild() handles everything.
+
+ * jobs.c(remove_job): set last_job to 0 if we are removing it.
+ * jobs.c(waitlast): check if last_job is 0.
+
+ * jobs.c(struct job): combined started, waiting, interactive
+ field into flags field; added JF_* flags; changed JW_NONOTIFY
+ to JW_NOTIFY and changed all calls to j_waitj() to reverse
+ this flag.
+
+ * jobs.c(exchild): ignore SIGTSTP, TTIN, TTOU for `command` jobs.
+
+Wed Jul 13 09:28:34 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * main.c(main): when checking if FMONITOR set on command line,
+ use 127 instead of -1 (signed vs unsigned char problem).
+
+ * tree.h: renumbered, XEXEC, XFORK, ... and DOBLANK, DOGLOB, ...
+ to use bits in order (instead of 0 5 2 4...).
+ * jobs.c(j_init): don't set sigtrap[SIGCHLD].sig_dfl = 1 as a
+ forked child may be a shell that needs to trap SIGCHLD.
+
+ * tree.h(XPCLOSE,XCCLOSE): flags for close in parent, close in child.
+ * jobs.c(exchild): added third argument - a file descriptor - if
+ flags has XPCLOSE, close fd in parent, if flags has XCCLOSE, close
+ in child.
+ * exec.c(execute): pass input side of pipe to exchild() so it can
+ be closed in the child.
+
+Tue Jul 12 10:21:57 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * main.c(main): let ignoresig() handle SIGTERM.
+ * jobs.c(exchild): let restoresigs handle SIGTERM.
+
+ * lex.c(yylex): don't parse array references inside double quotes
+ (partial fix).
+
+ * emacs.c(x_print): use shprintf instead of shellf so the output
+ of bind can be redirected.
+
+ * trap.c(deftraps): added SIGINFO (from jconklin@netcom.com).
+
+Fri Jul 8 09:37:51 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-4.9+mun.4 distribution
+
+ * Makefine.in: added $(LDSTATIC) to LDFALGS to make static
+ linking easier (suggested by sjg); use cp -p when creating
+ distributions to preserve file dates.
+ * configure.in: set LDSTATIC in Makefile if present in environ
+ when configure is run.
+
+ * sigact.c(sigsuspend): when calling 4.2bsd sigpause, pass *mask,
+ not mask.
+
+ * main.c(main): fixed up initialization of PWD (free memory, print
+ more informative message).
+
+ * misc.c(getcwd): range check backwards.
+
+ * c_sh.c(setsig): set sa_flags/sa_mask.
+
+ * edit.c(init_editmode),main.c,proto.h: deleted function - not needed
+ since VISUAL/EDITOR are special.
+
+ * lex.c(set_prompt),table.h: take out PS3.
+
+ * c_sh.c(c_umask): handle multiple actions in symbolic mode clauses
+ (eg, u+r-w); handle X (eg, o+X); ignore s (eg, u+s).
+
+ * shf.c(shf_fillbuf): continue reading if we get an EINTR.
+ * shf.c(shf_emptybuf,shf_write,shf_putchar): continue writing if we
+ get an EINTR.
+
+Thu Jul 7 10:19:24 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sigaction,sigprocmask: changed NULL third argument to
+ (struct sigaction *) 0.
+ * sh.h(kshpid): changed type from int to pid_t.
+ * proto.h(do_ulimit): removed do_ulimit() declaration.
+ * vi.c(complete_word): removed unused variable pos.
+ * syn.c(pipeline,elsepart): removed unused variable c.
+ * jobs.c(exchild): deleted variable s (assigned but not used).
+ * history(hist_init),shf.c(shf_gets): changed variable e to end
+ because there is a global e.
+ * exec.c(do_selectargs): removed secondarg argument; changed return
+ (char *) 1 to (char *) 0; changed all calls.
+
+ * io.c(canseek): use fd argument instead of 0.
+
+ * lex.c(readhere): don't use fixed sized buffer (line).
+
+ * expand.h: changed multi-statement macros to use do {..} while (0);
+ changed temporary variable vp to vp__ to avoid lint complaints.
+
+ * aclocal.m4(KSH_DUP2_CHECK): define F_GETFD/F_SETFD if not
+ already defined.
+
+ * etc/profile, etc/ksh.kshrc: replaced with new versions from
+ Simon J. Gerraty (sjg@zen.void.oz.au).
+
+Wed Jul 6 10:09:55 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * jobs.c(killpg),configure.in: restored use of killpg() - some
+ systems don't understand kill(-pgrp, signal) (ultrix 2.2);
+ added test for killpg() in configure.in.
+
+ * trap.c(inittraps): changed initialization of sigtraps - was
+ using -1 in deftraps[] as end of table marker but some systems
+ (eg, ultrix 2.2) define signals with -1 values (SIGPWR).
+
+ * Makefile.in(mandir,install): fixed mandir value; added /
+ in man installation; prefixed ksh.1 with $(srcdir).
+
+ * jobs.c(j_init): Ignore failure of TIOCSETD.
+
+ * misc.c(options[]): changed "vicomplete" to "vitabcomplete".
+
+ * emacs.c(x_e_putc,x_e_puts,x_debug_info): x_e_putc()/x_e_puts() are
+ the x_putc()/x_puts() functions from ksh4.9 edit.c (they got lost
+ in the merge); same for x_debug_info(); changed x_e_putc/x_e_puts
+ to call x_putc/x_puts.
+
+Mon Jul 4 09:29:05 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-4.9+mun.3 distribution
+
+ * main.c(remove_temps): use unlink() instead of remove(); delete
+ remove() define.
+
+ * sh.h(func_heredocs, struct env): added func_heredocs for here
+ documents in functions; added func_parse field to struct env
+ - set when a function is being parsed.
+ * lex.c(readhere): if e.func_parse, save temp file in func_heredocs.
+ * syn.c(get_command): increment/decrement func_parse when parsing
+ functions.
+ * main.c(remove_temps,reclaim,leave): added remove_temps(); make
+ reclaim() call remove_temps(); make leave() clean up function
+ here documents.
+
+ * aclocal.m4(KSH_TIMES_CHECK): new test - define TIMES_BROKEN
+ if times() doesn't exist or if it always returns 0.
+ * acconfig.h(TIMES_BROKEN): new define.
+ * missing.c(ksh_times): new function.
+ * ksh_times.h: new file.
+ * c_sh.c,jobs.c: changed <sys/times.h> to "ksh_times.h"
+ * c_sh.c,ksh_time.h(CLK_TCK): moved CLK_TCK define from c_sh.c
+ to ksh_time.h (needed in missing.c).
+
+ * syn.c(get_command): case TIME: don't call pipeline() with CONTIN
+ flag.
+
+ * c_sh.c(c_times): combined some printfs().
+
+ * jobs.c(j_jobs,j_kill,j_resume,waitfor): block SIGCHLD before
+ calling j_lookup() or looking at jobs list - avoids potential
+ problems with remove_job() being called in signal handler.
+
+Sun Jul 3 11:09:01 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * exec.c(do_selectargs, selread, pr_menu): re-wrote do_selectargs to
+ let c_read() to most of the work; deleted selread; make pr_menu
+ calculate width/ncolumns each call so select can be used recursively.
+
+ * configure.in, aclocal.m4(KSH_TERMIOS_H, KSH_TERM_CHECK): replaced
+ KSH_TERMIOS_H with KSH_TERM_CHECK; removed calls to
+ tcgetpgrp/tcsetpgrp - there is a separate test for this.
+
+ * main.c(main), sh.h: move getcwd() declaration to sh.h.
+
+ * eval.c(expand,varsub): added XNULLSUB case to deal with "$@"
+ (and "${foo[@]}") when $# (or ${#foo}) is 0.
+
+ * eval.c(expand,varsub): removed free_me field - let reclaim() take
+ care of it.
+
+Wed Mar 9 00:50:12 1994 Simon J. Gerraty (sjg@zen.void.oz.au)
+
+ * var.c (setstr): don't set ALLOC flag if vp.s is NULL.
+
+Thu Jun 30 10:16:44 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_sh.c(c_read): ignore null characters; use builtin_getopt()
+ instead of explicit parsing; removed -e option (neither at&t ksh
+ nor POSIX have this); added -s option (put line in history, at&t ksh);
+ if no variable specified, use REPLY (at&t ksh).
+
+ * exec.c(do_selectargs): use Xstring macros to deal with saving
+ input (was static, growing, buffers); flush shl_out after
+ shellf().
+
+ * vi.c(expand_word,complete_word): deleted call to free_edstate
+ since already done by restore_edstate().
+ * var.c(global): in 'if !letter(c)' block, deleted !c from
+ 'if (!c || !n[1])' - don't know why it was added since it makes
+ no difference to what is returned.
+
+ * syn.c(dogroup): removed onlydone argument since it is only
+ used in the while/until statements, where "while command; done"
+ is not allowed anyway; Changed all calls.
+
+ * misc.c(options[]): allow -i to be specified on the command
+ line.
+
+ * exec.c(iosetup): if stderr (fd 2) is being re-directed,
+ re-open shl_out to clear any errors.
+ * main.c(quitenv): if restoring fd 2, clear any write errors
+ * io.c(initio): initialize shl_out, shl_spare for writing
+ (was SHF_GETFL).
+
+ * main.c(main): ignore SIGQUIT if talking.
+
+Wed Jun 29 11:11:34 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_sh.c(c_exit): removed jobs/really quit stuff - exit should
+ always exit.
+ * main.c(shell): made reallyquit a local variable.
+ * sh.h(reallyquit): deleted reallyquit
+
+ * exec.c(comexec), main.c(main), misc.c(change_flag), jobs.c:
+ ifdef'd use of FMONITOR.
+ * sh.h, misc.c: define FMONITOR option only if JOBS defined.
+
+ * c_sh.c(c_wait): POSIXized: option parsing (of no options);
+ return 0 if not given any arguments; deal with multiple arguments.
+ * jobs.c(j_lookup): changed second argument to return an integer
+ error code, added defines for error codes, added error message
+ array; changed all calls to use new conventions.
+ * jobs.c(waitfor): returns -1 if job not found; added argument
+ to specify if notification messages should be suppressed.
+ * jobs.c(j_waitj): added flags argument instead of intr argument;
+ added JW_STOPPEDWAIT flag to wait for stopped jobs to complete;
+ added JW_NONOTIFY flag to suppress notification of normal
+ job termination.
+
+Tue Jun 28 16:13:10 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ksh.c(c_jobs): added -n and -p options, allow job ids to be
+ specified for -l option.
+ * jobs.c(j_jobs): added new arguments to deal with -n and -p options.
+
+ * shf.h(SHF_BSIZE): reduced size to 512 to reduce memory requirements
+ (I/O is used mostly for one line messages).
+
+ * config.h.bot: removed necessity for tty process groups to define
+ JOBS, added necessity of signal blocking/pausing.
+
+Mon Jun 27 21:49:52 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * jobs.c(exchild,j_print,j_waitj,j_jobs,check_job): exchild() - removed
+ commented out shf_flush() calls; j_print() - print a space between
+ status reports when lflag < 0, changed flag argument to use
+ long/medium/short defines; Changed Job.notify field to use
+ long/medium/short defines; j_waitj() - instead of calling j_print(),
+ set j->notify to print the job; j_jobs() - clear j->notify so user
+ won't be notified twice if a job finishes before a jobs command;
+ check_job() - put job on front of list only if it is stopped.
+
+ * main.c(main): pass flag to j_init() indicating if -m flag set/cleared
+ on command line; don't initialize ttyfd.
+ * sh.h: delete ttyfd.
+ * jobs.c(j_init,ttyfd,check_job): made ttyfd static; re-worked j_init()
+ to initialize ttyfd, initialize FMONITOR if not set by command line,
+ initialize shl_j for asynchronous job notification;
+ check_job() - use shl_j, look through saved fds to find real
+ standard-error.
+
+ * jobs.c(TTY_PGRP, ttypgrps_ok): modified conditions so job control
+ is useful without tty process groups; added ttypgrps_ok flag that
+ indicates if tty process groups should be set up; modifications so
+ job control useful if ttypgrps_ok not set.
+
+ * main.c(main): set FTALKING if 0 and 2 are tty as specified in POSIX
+ (was 0 and 1).
+
+ * misc.c(parse_args): do POSIX option processing for -A, -c and -o
+ (allow -onoglob, -ctrue).
+ * main.c(main): don't set up shl_stdout before/after parse_args() since
+ lone -o on command line no longer accepted; remove code to allow
+ -c with no options to read from stdin (at&t khs does this but POSIX
+ requires an option to -c).
+
+Thu Jun 23 17:46:54 NDT 1994 John Rochester (jr@panda.cs.mun.ca)
+
+ * trap.c(cleartraps): added special case for clearing trap 0 from
+ ksh-4.9 sources.
+
+Thu Jun 23 10:17:03 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * misc.c(builtin_getopt): print error messages for unknown option,
+ missing option; reset state if argv is 0.
+ * c_ulimit.c(c_ulimit): let builtin_getopt() print error messages.
+ * exec.c(call_builtin): call builtin_getopt() with 0 argv.
+
+ * c_sh.c(c_unset): added -v option (POSIX); use builtin_getopt() to
+ parse arguments; removed bogus comment about global() and special
+ variables; don't allow read-only variables to be unset (POSIX).
+
+ * var.c(unset, unsetspec): when unsetting a special variable, call
+ unsetspec(); unsetspec() new function.
+ * mail.c(mbset, mcheck): check that path is not 0 before calling stat
+ (so mbset() can be called with 0 when MAIL is unset); deleted #if 0'd
+ declarations of munset, mballoc and maddmsg.
+
+ * misc.c(parse_args): pass argv+i+1 to set_array (not argv+i); when
+ skipping arguments, leave i just before the NULL.
+
+ * exec.c(echo): flush shl_out when done.
+
+ * shf.c(shf_close): always used to return EOF.
+
+ * trap.c(trapsig): skip error handlers when checking for PARSE or LOOP.
+
+Wed Jun 22 10:24:09 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * sh.h(C_IFSWS), misc.c(initctypes): added and initialized C_IFSWS
+ (IFS white space).
+ * c_sh.c(c_read): print continuation prompt if line ends in backslash;
+ multiple non-white-space IFS chars delimit fields; strip trailing
+ IFS-white-space from last variable; watch out for backslash followed
+ by EOF.
+ * eval.c(expand): only do field splitting on the results of
+ parameter/command substition (POSIX, !v7-sh); multiple
+ non-white-space IFS chars delimit fields.
+
+ * eval.c(expand,alt_expand): removed NOALT tests since it could
+ never be set; added return after call to alt_expand() in expand().
+
+Mon Jun 6 10:12:41 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * misc.c(setctypes, initctypes): setctypes - don't allow leading null
+ in IFS (IFS="" could cause problems, 0 is already added);
+ initctypes - don't use a leading null.
+
+ * sh.h, eval.c: move definition of ifs0 from eval.c to sh.h; handle
+ null ifs0.
+ * var.c(setspec): set ifs0 to first character of IFS.
+
+ * lex.c(yylex): when parsing ${..}, array references were not being
+ null terminated.
+
+Fri Jun 3 12:28:06 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * var.c(export): changed to use memcpy() instead of loops.
+
+ * eval.c(varsub, expand): check for #foo[@] in addition to #foo[*];
+ handle ${foo[*]} and ${foo[@]} - added free_me field to struct Expand
+ so pointer vector can get freed.
+
+ * c_sh.c(c_dot), main.c(include): set up positional parameters if
+ any are specified; Added argc,argv args to include(); changed all
+ calls to include(); change include() to return 0 or 1 - caller
+ can check exstat if desired; c_dot() should return 1 if can't open
+ file (was -1).
+
+ * c_sh.c(c_brkcont): warning message could call getn() with NULL - save
+ original number and print it.
+
+ * main.c(main): set $0 to first argument when -c used, ie,
+ "sh -c cmd-string this-is-$0 argumernts..."
+
+ * exec.c(iosetup): print "cannot open" if IOHERE fails.
+
+ * io.c(errorf): set exstat to 1.
+
+ * exec.c(search): assume mode is R_OK or X_OK (not 0/1 - 0 is F_OK, we
+ want R_OK); changed all calls to pass R_OK/X_OK.
+ sh.h: define R_OK,W_OK,X_OK,F_OK if not defined.
+ eaccess(): changed all calls to use [RWXF]_OK.
+
+ * sh.h(flag[], shell_flags[], Flag()): Renamed flag[] array to
+ shell_flags[] to avoid conflicts with other uses of flag; Changed
+ all references to flag[] to Flag(); defined Flag() to cast its arg
+ to an int (for old pcc based C compilers).
+
+ * vi.c(iswordch): use letnum() instead of isalnum || _.
+
+ * misc.c(parse_args): call set_array() to deal with -A flag.
+ * var.c(set_array): new function.
+
+
+Fri Jun 3 10:22:26 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * eval.c(substitute): afree(s).
+
+ * var.c(global,local), sh.h: ARRAYMAX: new define - max index of an
+ array; changed 511 constants to this; changed global() and local()
+ to use array_ref_len() instead of arraysub().
+
+ * expr.c(token): deleted unneeded arraysub() decl.
+
+ * lex.c(arraysub), proto.h: made static, removed unused arguments,
+ changed callers; removed prototype from proto.h.
+
+ * ChangeLog: changed descriptions from func(file) to file(func).
+
+Wed Jun 1 09:17:50 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-4.9+mun.2 distribution
+
+ * Makefile.in: added RCSFILES macro and rcs-ci target.
+
+ * configure.in: add termio.h to the AC_HAVE_HEADERS() call.
+
+ * sh.h, main.c, edit.c, misc.c: use EXTERN for aperm, x_cols,
+ builtin_optind and builtin_optarg in sh.h, delete definitions
+ in main.c, edit.c and misc.c.
+
+ * io.c(maketemp): changed sizeof(PATH) to sizeof(path).
+
+ * aclocal.m4(KSH_VOID,KSH_DUP2_CHECK): test that a void * variable
+ can be used (Ultrix 2.2 compiler doesn't do this); added ifdef
+ HAVE_FCNTL_H to dup2 test.
+
+ * aclocal.m4, configure.in, sh.h, tree.c, io.c, shf.c: added
+ new config test KSH_PROTOTYPES to check for function prototypes
+ (MIPS RICS/os 5.0 C compiler isn't STDC and it can't mix <stddef.h>
+ with <varargs.h>). Removed stdarg.h test (now redundant). Changed
+ all varargs functions to use HAVE_PROTOTYPES instead of
+ HAVE_STDARG_H && STDC.
+
+ * eval.c(alt_scan): changed type of endc param from char to int to
+ avoid problems with mixing prototype declarations and K&R
+ definitions.
+
+ * main.c(main), sh.h: added plain getcwd() decl to main(), removed
+ ARGS() version from sh.h (some systems have getcwd() but don't
+ declare it, some have getcwd() with a size_t arg 2, some have
+ an int arg 2).
+
+ * misc.c(memset,memmove): changed the second memset() to memmove().
+
+ * c_sh.c(clocktos): changed #if CLK_TCK ... to if (CLK_TCK.. since
+ CLK_TCK is not always defined to a number (may be a _sysconf())).
+
+Tue May 31 10:49:16 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * configure.in, Makefile.in: added code to set GCC_WARNFLAGS from
+ $(srcdir)/Warn-flags if gcc is being used; Took -Wall, etc.
+ out of CFLAGS, took -Dno_RCSids out of DEFS. Added install.sh to
+ DISTFILES, grabbed copy of install.sh from autoconf (who grabbed
+ it from X11R5).
+
+ * io.c(errorf,shellf,shprintf),shf.c(shf_fprintf,shf_snprintf),
+ tree.c(fptreef,snptreef): don't use ansi decls with old style
+ varargs; changed ifdef __STDC__ to ifdef HAVE_STDARG_H.
+
+ * jobs.c(j_init): changed getpgrp() call to getpgID(), defined getpgID()
+ appropriately for BSD vs POSIX/SYSV getpgrp.
+
+ * expand.h, ksh_dir.h, ksh_stat.h, ksh_time.h, ksh_wait.h, shf.h,
+ tty.h: added RCS Id's.
+
+ * acconfig.h: updated SIGSET_T comment: unisgned int -> unsigned.
+
+ * aclocal.m4(KSH_CLOCK_T,KSH_TIME_T,KSH_SIGSET_T): make sure
+ type is a word (same fix as was done for more_t, et.al.).
+
+ * aclocal.m4(KSH_VOLATILE): check that the compiler can deal with
+ volatile pointers (dec/pmax ultrix 4.2 compiler can't).
+
+ * misc.c(parse_args): added skelatal code for dealing with -A.
+
+ * var.c,proto.h(skip_varname): new function; deleted isassign()
+ function, which is no longer called. Changed typeset(var.c)
+ to use skip_varname().
+
+ * var.c(strint): fail if base is not in the range 2..36; set variable
+ base according to first base seen; generate an error if a non-alnum
+ char is seen (1^A was the same as 11).
+
+ * var.c(strval): for integer variables, output base if != 10.
+
+ * sh.h: fixed typo in x_cols define (#defined -> #define).
+
+Fri May 27 16:49:29 NDT 1994 Micharl Rendell (michael@panda.cs.mun.ca)
+
+ * made pdksh-4.9+mun.1 distribution
+
+ * finished autoconf'ing source code.
+
+Fri May 20 16:47:06 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * c_ulimit.c(c_ulimit) (was do_ulimit.c(do_ulimit)): major rework;
+ deal with combinations of getrusage and ulimit, use options at&t
+ ksh uses (-SHa..).
+ * misc.c(builtin_getopt): yet another getopt routine for builtin
+ commands
+ * misc.c(parse_args), c_sh.c(c_set), main.c(main): custom option
+ parsing routine for command line/set options. Lots of changes
+ to main() to incorporate this (easier to follow).
+ * c_test.c(c_test): added -e (file exists) test.
+
+Fri May 5 12:16:46 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca)
+
+ * numerous changes from old Notes.4-9 file:
+ - c_ksh.c:
+ - calls to errorf identify what function the error is in
+ (eg, errorf("print: bad -u option");)
+ - added array stuff (lots of places)
+ - table.c
+ - lex.c
+ - var.c: +arraysearch(), +basename()
+ - c_sh.c(c_exit), main.c(shell), sh.h:
+ - added reallyquit flag to deal with stopped jobs at exit time;
+ only two exit commands (or eofs) in a row will work. in
+ shell(), set exstat from execute value.
+ - c_ksh.c(c_print):
+ - handle \\ at end of string better (don't skip the null)
+ - c_ksh.c(c_whence):
+ - stop looking as soon as we get a failure
+ - pass flag[FTRACKALL] to findcom() instead of 0
+ - c_ksh.c(c_alias):
+ - print '%s alias not found' if not found
+ - c_ksh.c(c_jobs):
+ - handle -l option (list pid)
+ - c_ksh.c(c_kill):
+ - two column -l output to try to keep it on one screen
+ - c_sh.c(c_read):
+ - skip IFS at start of line "echo ' a b' |(read a b; echo $a)"
+ should print a, not nothing.
+ - changed EOF check to set all vars to null (same way \n is
+ handled)
+ - eval.c(comsub):
+ - after compile(), return if t is NULL
+ - use setfileno() instead of fileno(x) = .. (see sh.h changes);
+ - exec.c(iosetup):
+ - set exstat if redirect fails
+ - exec.c(fd_clexec):
+ - check that fd >= 0
+ - exec.c:
+ - change SHARPBANG ifdef code - everyone uses scriptexec().
+ ifdef is put around open/read/parse code in scriptexec().
+ [code cleanup]
+ - exec.c(search):
+ - eaccess test out side of loop: must be regular file to exec it
+ (like test in loop)
+ - eval.c(expand):
+ - added while getc() == 0 in XCOM
+ [a null in the output of a file would be treated as eof and
+ waitlast would not be called]
+ - eval.c(trimsub):
+ - '%' subst: use ATEMP, not APERM
+ - eval.c(tilde): use Xinit et. al., instead of fixed length buffer
+ - getopts.c(getopt): rename getopt() to ksh_getopt() to avoid
+ problems with prototypes in system #include files.
+ - io.c(fopenshf):
+ - call clearerr(fd) if already open
+ - use F_GETFL to determine appropriate mode for fdopen() call
+ instead of opening everything for read/write
+ - use ifdef _FSTDIO instead of ifdef _BSDI (_FSTDIO is used in
+ 4.4bsd, NetBSD, FreeBSD and other non BSDI environs)
+ - lex.c(ungetsc): don't decrement if str == null, remove nullstr hack
+ - lex.c(yylex):
+ - \ at eol: call Xfree before goto Again
+ - use memset() to clear ident, instead of while loop
+ - syn.c(get_command): renamed from command() to get_command() to
+ avoid conflicts with command(main.c).
+ - sym.c(get_command):
+ - allow A=B <alias-name> to work (alias was not being expanded)
+ - allow `if .. if .. fi fi' to work (no ; after group terminator
+ (fi , esac, done, ), })
+ - allow `alias FI=fi ; if .. if .. fi FI' to work (alias
+ expansion after group terminator)
+ - do not assume resize returns same pointer
+ - tree.c(pioact):
+ - handle IORDWR case
+ - added leading quote for file in IOHERE case
+ - var.c(special):
+ - add MAILCHECK check
+ - var.c(setspec):
+ - add MAILCHECK case (doesn't do anything yet)
+ - var.c(strint):
+ - handle null vp->val.s
+ - eval.c(sh.h, expand), misc.c(options[]):
+ - enable alternations only if alternations flag set
+ (set -o alternations). This is so (att ksh) scripts that don't
+ expect alternations won't break.
+ - added notify option (asynchronous job completion notification)
+ - added vicomplete to enable tab char as file name completion
+ char in vi (this is likely to go away - exits for historical
+ reasons)
+ - jobs.c, main.c, sh.c, trap.c:
+ - define/use SIG_HDLR instead of void
+ - eval.c(expand): alt_expand() does not return a value so don't test
+ it, just return. Also changed decl of alt_expand() to reflect
+ reality.
+ - emacs.c(x_emacs): first return returns random value (i); change to
+ return 0. Also changed the way interrupts are returned to called
+ (return -2 means interrupt).
+ - c_sh.c(c_brkcont): at&t ksh allows breaks/continues outside of
+ loops, 4.9 prints an error and breaks out of all env's (if, case,
+ etc.). Fixed to act like at&t (ie, allow bogus continues), except
+ a warning message is printed. (Some HP-UX shell scripts actually
+ have continues outside of loops...)
+ - main.c(shell): parameter s should be volatile as it is used after a
+ setjmp.
+ - edit.c(promptlen): handle tabs, backspaces...
+ - cleaner fix to the ^C/source->line problem: in pprompt(lex.c),
+ convert ! to source->line+1 (same in promptlen(edit.c)), increment
+ source->line after a (non-empty, non-eof) line has been read
+ (before call to histsave()). To be pedantic, also adjust
+ position of source->line++ in SHIST in case PS9 is ever used.
+ Remove code in shell(main.c) that does the source->line--,
+ remove the source->line-- for eof and empty line in from
+ getsc_(lex.c).
+ - trap.c(sigtrap[]): do not depend on signal number matching position
+ in initialization array. Use second table in which order does not
+ matter to initialize sigtrap[] array. Easier to read/port, and
+ generally less fragile. requires call to inittraps() in main.c.
+ - trap.c(cleartraps): need to clear Sigact flags/mask after use
+ (actually, declare a local struct sigact and use that instead of
+ Sigact)
+ - exec.c(execute): case TSELECT: no USE_SIGACT code for call to
+ signal(); case TPIPE: don't call waitlast() if XXCOM since
+ waitlast() will be called in expand(); 4.9 code that set and
+ then cleared the XEXEC flag in the TPIPE case not added since
+ it was ifdef'd out, also the code to not exit if XPIPEI flag
+ set was not added; 4.9 XXWHL flag not added (don't flush stdin
+ if in a while loop) since this problem fixed (I hope) by shf stuff.
+ - ttyfd{sh.h/lex.c}: use EXTERN/_I_ to initialize ttyfd. Remove from
+ lex.c
+ - interrupted reads: instead of testing sigchld_caught after reads
+ fail, continue them if errno == EINTR.
+ - edit.c(x_getc): check for EINTR, and continue reading if so.
+ - lex.c(getsc_): check for EINTR, and continue reading if so.
+ (don't check return of x_read() - check has already been done)
+ - exec.c(selread): check for EINTR, and continue reading if so.
+ - history changes:
+ - history.c:
+ - histrpl(): bounds check doesn't take global flag into
+ account - move check into loop and past loop.
+ - c_fc(): if pattern is the empty string, histrpl()
+ goes into infinate loop. Start searching for = after first
+ char (this is what at&t ksh seems to do).
+ - c_fc(): `fc -l first' should list at most 16
+ commands according to at&t manual.
+ - findhist(): re-wrote: shorter, easier to follow. Now
+ returns an int. (used only by vi code)
+ - use COMPLEX_HISTORY's allocated history array in
+ EASY_HISTORY: common init_histvec(), sethistfile() and
+ sethistsize() functions.
+ In the process, hist_open() went away. Use histsize
+ instead of HISTORY in hist_init() and hist_finish()
+ #ifdefs in lex.h, table.h,
+ var.c disappear, history variable definitions in lex.c
+ disappear.
+ - hist_init(COMPLEX_HISTORY): move hstarted = 1 to after the
+ FTALKING test.
+ - hist_finish(): don't open hname if its null
+ - histrpl(): made static, use ARGS in decl
+ - made current and curpos static
+ - changes to allow embedded newlines in commands:
+ - histsave(): trash only trailing newline
+ - hist_init(): read in null terminated lines instead of
+ newline terminated
+ - hist_finish(): write null terminated lines
+ - make multiple line command appear in single history line
+ (EASY_HISTORY only):
+ - added histappend() to append new command to last
+ command
+ - added call to histappend() in getsc_(lex.c) (also: only
+ adjust source->line if not multiline).
+ - lex.h:
+ - remove second decl of history if !EASY_HISTORY
+ - tree.c(ptree): case TCOM: check if t->vars or t->args is 0
+ - vi.c(x_vi): ^D anywhere in command line is eof ($ foobar^D exits).
+ at&t ksh ignores ^D in middle of line
+ - edit.c:
+ - don't need to include string.h - included in stdh.h
+ - init_editmode(): in at&t ksh, VISUAL takes precenence over
+ EDITOR, so put it first. Also, at&t ksh doesn't use FCEDIT so
+ trash it.
+ - moved initialization of ed_* from x_read() to x_init() since
+ thats where they are set from tty structs.
+ - x_init(): set ed_intrc, ed_quitc for _BSD & _POSIX_TERM ifdefs
+ - send output to shlout (instead of stdout - at&t ksh writes to
+ stderr)
+ - made x_do_init static.
+ - exit.c(x_getc), lex.c(yylex): restart interrupted reads in
+ x_getc(), changed read-restart in yylex() to only effect call
+ to read().
+ - syn.c(thenpart): then THEN is not optional - generate a syntax
+ error if no THEN. (ie, `if true ; fi' is not legal).
+ - syn.c(get_command): don't accept keywords after re-directory (eg,
+ `> /dev/null if true ; then echo hi ; fi' is not legal).
+ - vi.c: handle \ and ^[ in command mode ala at&t ksh (filename
+ completion)
+ - syn.c(thenpart), syn.c(elsepart): calling token(0) when they want a
+ keyword (always worked 'cause tpeek() is always called before, with
+ the keyword flag). Fix: call token(KEYWORD|ALIAS) (at&t ksh does
+ alias expansion here). Question: pass CONTIN as well?
+ - syn.c(get_command): LWORD/MPAREN case: do alias expansion when
+ getting open brace ({). CASE case: ditto for `in' and `esac'.
+ IF case: ditto for `fi'. FUNCTION case: dito for open brace ({).
+ - syn.c(dogroup): alias expansion when getting `do' and `done'.
+ - syn.c(wordlist): alias expansion when getting `in'.
+ - syn.c(nested): alias expansion when getting `)', `}'
+ - syn.c(casepart): alias expansion when getting `esac' or `;;'.
+ Also removed use of cf variable - it does nothing.
+ - eval.c(expand): case CSUBST: '#'/'%' - increment st so nested
+ substitutions work.
+ - var.c(typeset): INTEGER && no assignment: memory was being freed
+ and then used (there was even a comment saying it was being
+ done...)
+ - lex.c, lex.h, exec.c, main.c:
+ added shf_{open,fdopen,close,gets}() routines so stdio wasn't
+ used. When reading a command file under osf/1, stdio would
+ mess up the read pointer when a child exited: the exit flushed
+ all open files and flushing a file open for reading changes the
+ current read position (does a seek to where the next char would
+ be read). This position is then used by the parent process,
+ who thinks the read position is still at the end of the buffer
+ it read.
+ - c_sh.c:
+ use shf_*() routines avoids two bugs in read:
+ - on sunos 4.1.3, a read would gobble up a stdio buffer and
+ never put it back (ie, lseek backwards). Neither a
+ fflush() nor a fseek(x, 0L, 1) fixed the problem.
+ (see Bug 26)
+ - on linux, stdio knows its current offset and seeks there
+ before reading a buffer. This causes grief when the shell
+ replaces file descriptors behind stdio's back (ie, all the
+ time).
+ $ read x << EOF
+ hi
+ EOF
+ $ cat > /dev/tty << EOF
+ 1:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+ 2:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+ 3:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+ EOF
+ $
+ the cat picks up reading at the offset where the read
+ command left off (ie, at bcdefg... - the 1:a are skipped).
+ - exec.c(comexec): built-in c_exec && no args: close the saved fd's
+ (were thrown away). Also, set close-on-exec flag for fd's > 2,
+ as per at&t manual.
+ - lex.c(yylex): only accept 1 digit before a redirection (eg, 1> is
+ ok, 1abc> is not)
+ - exec.c(iosetup): only accept 1 digit after a dup-redirection
+ (eg, >&1 is ok, >&1abc is not)
+ - exec.c(iosetup): use O_APPEND flag for >> redirections; use O_CREAT
+ flag for <> redirections. (re-organized to accumulate open() flags
+ and do one open() call); changed error message to know about dup
+ failing.
+ - c_sh.c(c_umask): umask should have leading 0 - doesn't when umask
+ has 3 digits (eg, `umask 222; umask' prints 222 instead of 0222)
+ - c_sh.c(setsig): declare local struct sigaction and use it so
+ sa_flags doesn't have to be cleared (also to try to keep away from
+ global vars).
+ - main.c(main):
+ - don't go set TALKING if name starts with -.
+ - read $HOME/.profile (not .profile)
+ - main.c(main): re-wrote option parsing code
+ - edit.c(init_editmode): don't check FCEDIT
+ - if set -o vi/emacs/gmacs on command line, don't set edit mode from
+ FCEDIT/EDITOR/etc.
+ - var.c(setspec): FCEDIT should not magicly change edit mode; VISUAL
+ and EDITOR should (EDITOR only if VISUAL is not set)
+ - changed noclobber char from ! to | (this is what at&t ksh uses)
+ - exec.c(iosetup): use O_EXCL flag if FNOCLOBBER set and >| not used
+ - c_sh.c(c_set): don't clear FERREXIT if FTALKING is set; although
+ some ksh manuals say -e is ignored for interactive shells, they
+ all seem to honour the -e flag for interactive shells.
+ - vi.c: reset history position to the end after a line is modified
+ - `(( 1 + 2 ))' no longer prints `+: bad expression'!
+ - lex.c(yylex): added parenthesis counting so `(( ((1+2)) ))' no
+ longer generates a syntax error
+ - expr.c(intvar): if strint() fails, give bad number error
+ (`let 1+foo' should fail)
+ - `echo hi 1abc123> /dev/tty' no longer interpreted as
+ `echo hi 1> /dev/tty' (is `echo hi 1abc123 > /dev/tty')
+ - `echo hi 1<> /tmp/does-not-exist' now works (used to say cannot
+ open)
+ - c_sh.c(c_umask): umask now takes symbolic arguments (g-r, +w, etc.)
+ - c_ksh.c(c_whence): pass flag[FTRACKALL] to findcom() instead of 1.
+ - `echo hi >< bar' now produces an error
+ - jobs.c(j_lookup): now checks for ambiguous job specifications;
+ callers now get an error message.
+ - c_ksh.c(c_fgbg): multiple jobs can be specified
+ - emacs.c(x_transpose): move past transposed chars like (gnu) emacs
+ does
+ - main.c(main): don't copy initcoms (messes up memory allocated for
+ shf_iob[] in initio(), should not be necessary)
+ - main.c(main), var.c(import), var.c(typeset), c_ksh.c(c_alias),
+ expr.c(token), misc.c(strnsave): added strnsave() function; use it
+ instead of writing nulls in the middle of strings; main() no longer
+ copies startup commands before executing them.
+ - vi.c: wbuf[] no longer a fixed size (was hard coded to 80 chars).
+ - alloc.c(aresize): if passed a null pointer, don't free it
+ - expand.h: restored usage description and NOTE from previous version
+ - alloc.c, table.h, table.c:
+ - changed struct fields named `free' to nfree (struct table) and
+ freelist (struct Block) to allow memory debugging
+ (involves #defining free)
+ - main.c(main): smart initialization of PWD (ensures it is always
+ valid).
+ - lex.h, table.h, tree.h: removed redundant function declarations
+ (were also in proto.h)
+ - sh.h: increased LINE from 256 to 1024
+ - main.c(include), sh.h(E_INC), c_sh.c(c_return): added hack so a
+ return in a included file returns to the includer instead of
+ exiting the shell. Very useful in $ENV scripts and profiles,
+ eg, so the whole script does not have to be parsed for
+ non-interactive shells.
+ This is not compatible with the at&t ksh, whose man page says
+ return is the same as exit outside of functions. May be modified
+ in the future. (note that at&t ksh parses a .'ed file before
+ executing anything, so a premature return would not speed its
+ parsing, which is why the return hack was added to pdksh)
+ - exec.c(iosetup): don't save fd if already saved (and don't
+ generate an error)
+ - exec.c(findcom): in if (..ALLOC && eaccess), test for
+ ALLOC redundant - always call afree()
diff --git a/IAFA-PACKAGE b/IAFA-PACKAGE
@@ -0,0 +1,18 @@
+$OpenBSD: IAFA-PACKAGE,v 1.7 1999/07/14 13:37:23 millert Exp $
+
+Title: pdksh
+Version: 5.2.14
+Description: A public domain implementation of the Korn shell (ksh88),
+ a UNIX command line interpreter / scripting language; the few
+ missing ksh features are being added and the shell is being
+ POSIXized.
+Author: (Eric Gisin), (Charles Forsyth), (John R MacMillan),
+ sjg@zen.void.oz.au (Simon J. Gerraty),
+ michael@cs.mun.ca (Michael Rendell), (plus many others)
+Maintained-by: michael@cs.mun.ca (Michael Rendell)
+Maintained-at: ftp://ftp.cs.mun.ca:/pub/pdksh/
+Platforms: Written in C, runs on most UNIX boxes (uses GNU autoconf;
+ works best in a POSIX or BSD environment). Also runs on OS/2
+ and (using cygwin32 package) on win95/NT
+Copying-Policy: Freely Redistributable (mostly public domain, some copyrighted)
+Keywords: pdksh, ksh, Korn, shell, command line interpreter
diff --git a/INSTALL b/INSTALL
@@ -0,0 +1,151 @@
+$OpenBSD: INSTALL,v 1.1.1.1 1996/08/14 06:19:10 downsj Exp $
+
+[This file is the generic GNU autoconf/configure installation description,
+ see the README for pdksh specific configuration/installation information]
+
+ This is a generic INSTALL file for utilities distributions.
+If this package does not come with, e.g., installable documentation or
+data files, please ignore the references to them below.
+
+ The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation, and
+creates the Makefile(s) (one in each subdirectory of the source
+directory). In some packages it creates a C header file containing
+system-dependent definitions. It also creates a file `config.status'
+that you can run in the future to recreate the current configuration.
+
+To compile this package:
+
+1. Configure the package for your system.
+
+ Normally, you just `cd' to the directory containing the package's
+source code and type `./configure'. If you're using `csh' on an old
+version of System V, you might need to type `sh configure' instead to
+prevent `csh' from trying to execute `configure' itself.
+
+ Running `configure' takes awhile. While it is running, it
+prints some messages that tell what it is doing. If you don't want to
+see any messages, run `configure' with its standard output redirected
+to `/dev/null'; for example, `./configure >/dev/null'.
+
+ To compile the package in a different directory from the one
+containing the source code, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'. `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script. `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'. If
+for some reason `configure' is not in the source code directory that
+you are configuring, then it will report that it can't find the source
+code. In that case, run `configure' with the option `--srcdir=DIR',
+where DIR is the directory that contains the source code.
+
+ By default, `make install' will install the package's files in
+`/usr/local/bin', `/usr/local/man', etc. You can specify an
+installation prefix other than `/usr/local' by giving `configure' the
+option `--prefix=PATH'. Alternately, you can do so by consistently
+giving a value for the `prefix' variable when you run `make', e.g.,
+ make prefix=/usr/gnu
+ make prefix=/usr/gnu install
+
+ You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files. If you
+give `configure' the option `--exec-prefix=PATH' or set the `make'
+variable `exec_prefix' to PATH, the package will use PATH as the prefix
+for installing programs and libraries. Data files and documentation
+will still use the regular prefix. Normally, all files are installed
+using the same prefix.
+
+ Some packages pay attention to `--with-PACKAGE' options to
+`configure', where PACKAGE is something like `gnu-as' or `x' (for the
+X Window System). They may also pay attention to `--enable-FEATURE'
+options, where FEATURE indicates an optional part of the package. The
+README should mention any `--with-' and `--enable-' options that the
+package recognizes.
+
+ `configure' also recognizes the following options:
+
+`--help'
+ Print a summary of the options to `configure', and exit.
+
+`--quiet'
+`--silent'
+ Do not print messages saying which checks are being made.
+
+`--verbose'
+ Print the results of the checks.
+
+`--version'
+ Print the version of Autoconf used to generate the `configure'
+ script, and exit.
+
+`--x-includes=DIR'
+ X include files are in DIR.
+
+`--x-libraries=DIR'
+ X library files are in DIR.
+
+ `configure' also accepts and ignores some other options.
+
+ On systems that require unusual options for compilation or linking
+that the package's `configure' script does not know about, you can give
+`configure' initial values for variables by setting them in the
+environment. In Bourne-compatible shells, you can do that on the
+command line like this:
+
+ CC='gcc -traditional' LIBS=-lposix ./configure
+
+On systems that have the `env' program, you can do it like this:
+
+ env CC='gcc -traditional' LIBS=-lposix ./configure
+
+ Here are the `make' variables that you might want to override with
+environment variables when running `configure'.
+
+ For these variables, any value given in the environment overrides the
+value that `configure' would choose:
+
+ - Variable: CC
+ C compiler program. The default is `cc'.
+
+ - Variable: INSTALL
+ Program to use to install files. The default is `install' if you
+ have it, `cp' otherwise.
+
+ For these variables, any value given in the environment is added to
+the value that `configure' chooses:
+
+ - Variable: DEFS
+ Configuration options, in the form `-Dfoo -Dbar...'. Do not use
+ this variable in packages that create a configuration header file.
+
+ - Variable: LIBS
+ Libraries to link with, in the form `-lfoo -lbar...'.
+
+ If you need to do unusual things to compile the package, we encourage
+you to figure out how `configure' could check whether to do them, and
+mail diffs or instructions to the address given in the README so we
+can include them in the next release.
+
+2. Type `make' to compile the package. If you want, you can override
+the `make' variables CFLAGS and LDFLAGS like this:
+
+ make CFLAGS=-O2 LDFLAGS=-s
+
+3. If the package comes with self-tests and you want to run them,
+type `make check'. If you're not sure whether there are any, try it;
+if `make' responds with something like
+ make: *** No way to make target `check'. Stop.
+then the package does not come with self-tests.
+
+4. Type `make install' to install programs, data files, and
+documentation.
+
+5. You can remove the program binaries and object files from the
+source directory by typing `make clean'. To also remove the
+Makefile(s), the header file containing system-dependent definitions
+(if the package uses one), and `config.status' (all the files that
+`configure' created), type `make distclean'.
+
+ The file `configure.in' is used to create `configure' by a program
+called `autoconf'. You only need it if you want to regenerate
+`configure' using a newer version of `autoconf'.
diff --git a/LEGAL b/LEGAL
@@ -0,0 +1,12 @@
+$OpenBSD: LEGAL,v 1.2 2003/07/17 20:59:43 deraadt Exp $
+
+pdksh is provided AS IS, with NO WARRANTY, either expressed or implied.
+
+The vast majority of the code that makes pdksh is in the public domain.
+The exceptions are:
+ sigact.c and sigact.h
+ [REMOVED]
+ aclocal.m4
+ [REMOVED]
+
+That's it. Short and simple.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,18 @@
+# $OpenBSD: Makefile,v 1.29 2013/12/02 20:41:01 millert Exp $
+
+PROG= ksh
+SRCS= alloc.c c_ksh.c c_sh.c c_test.c c_ulimit.c edit.c emacs.c eval.c \
+ exec.c expr.c history.c io.c jobs.c lex.c mail.c main.c mknod.c \
+ misc.c path.c shf.c syn.c table.c trap.c tree.c tty.c var.c \
+ version.c vi.c
+
+DEFS= -Wall
+CFLAGS+=${DEFS} -I. -I${.CURDIR} -I${.CURDIR}/../../lib/libc/gen
+LDFLAGS+=-static
+MAN= ksh.1 sh.1
+
+LINKS= ${BINDIR}/ksh ${BINDIR}/rksh
+LINKS+= ${BINDIR}/ksh ${BINDIR}/sh
+MLINKS= ksh.1 rksh.1
+
+.include <bsd.prog.mk>
diff --git a/NEWS b/NEWS
@@ -0,0 +1,662 @@
+Version 5.2.14
+
+* bug fixes
+ * test: -nt(-ot) now succeed if second (first) file doesn't exist and the
+ other does.
+ * time: now accepts the -p (posix) option.
+ * vi: failed redo (.) commands no longer return the line to the shell.
+ * emacs: bind commands in .profile/$ENV no longer overridden by tty
+ settings.
+ * heredocs: now saved in memory to avoid temp files disappear too soon.
+ * time: commands at the end of a pipeline can now be timed.
+ * on startup: MAILCHECK,TMOUT,SECONDS values from environment are honoured.
+ * trap: exit traps now executed in subshells (without explicit exit call).
+ * eval: if given empty command while in non-posix mode, exit status is
+ that of last command substitution (if any).
+ * trap: if first argument is "exit", it is taken as a command not a signal.
+ * pwd: config test & code to work around bug in hpux 10.x getcwd().
+ * RANDOM: seed based on both time and pid; different sequence in sub shells.
+ * typeset -f: now pretty-prints $(..) and $((..)) correctly.
+ * fixed bug in memory allocation which can lead to core dumps.
+ * set -o: no longer prints options that have no names.
+ * emacs: <esc>. in very fist command no longer dumps core.
+ * .: can now dot non-regular files (eg, /dev/null).
+ * parsing: bar(); no longer dumps core when function bar is run.
+ * variable substitution: ${#array[*]} now prints number of set elements
+ in the array (used to print largest index, not what ksh88 did).
+ * job control: resuming suspended gnu su no longer hoses su'd shell.
+ * job control: fixed possible core dump when waiting for jobs.
+
+* LINENO variable now supported.
+* port to cygwin environment on win95/winnt.
+
+
+Version 5.2.13
+
+* bug fixes
+ * functions: $0 in sh-style functions is now the same as the shell's $0.
+ * .: fixed possible core dump on clean up.
+ * test: a lone -t argument now does a isatty(1) test if not in posix mode.
+ * alias: PS2 no longer printed when expanding alias foo="cmd; ".
+ * set/typeset/getopts: can have options prefixed with both + and -.
+ * typeset -f: now reproduces functions correctly ("function F" vs "F()").
+ * alias: options can start with +.
+ * set -A: a -- marking end of options is now skipped.
+ * errexit option (-e) ignored when reading profile and $ENV files.
+ * test: '-x f' now fails for root if f is a file and has no x bits set.
+ * $_: set to last arg in interactive shells only.
+ * PATH: if $PATH not set on startup, it is set to the default path.
+ * extended globbing: allow (pat|pat) within @(...) and ${foo#...} patterns.
+ * emacs: ^[_ now behaves as it does in at&t ksh (word from _last_ command).
+ * MAIL/MAILCHECK: fixed bug that prevented the `new mail' messages.
+ * ${..%..} and ${..#..} now work if compiled as sh.
+ * sh: fd's greater than 2 are passed on to executed commands.
+ * syntax: accepts "if (( 1 )) then" (also [[ ]]): no ; required before then.
+ * substitution: accepts (and ignores) leading : in %, %%, #, ## substitions.
+ * .: doting directories no longer allowed.
+ * editing: completion after "cmd " now completes files (was command).
+
+
+$OpenBSD: NEWS,v 1.15 2007/08/02 11:05:54 fgsch Exp $
+
+Version 5.2.12
+
+* bug fixes
+ * editing: shell recognizes window resizes on Dec alphas (config problem).
+ * alias: no longer dumps core if alias is in a command substitution.
+ * alias: everything after ;\n or \n\n was ignored in aliases.
+ * exec: temp files used by here docs in functions now cleaned up on exec.
+ * possible core dump when cleaning up environment fixed.
+ * vi: set -o vi-show8 now does what it was supposed to do (cat -v like).
+ * job control: process group synchronization (needed on systems with
+ broken setpgrp()) now works when the pipeline contains built in commands.
+ * vi: if set -o vi-tabcomplete, tab works in command mode as well.
+ * set/typeset: unset parameters are only reported if they have attributes.
+
+
+Version 5.2.11
+
+* bug fixes
+ * aliases: expansion was reading an extra character (bug added in 5.2.10).
+
+
+Version 5.2.10
+
+* bug fixes
+ * parsing: handling of backslash-newline fixed (esp. in here documents).
+ * read: prints prompt if non-interactive and input is a tty.
+
+
+Version 5.2.9
+
+* bug fixes
+ * config: using LDSTATIC no longer generates config error.
+ * config: can compile as sh again (--enable-shell=sh).
+ * config: should compile on machines with broken "gcc -g"
+ * config: fixed test for broken S_IFIFO.
+ * config: fixed test for getwd() routine.
+ * config: better NeXT support (signal list generated correctly, clock_t
+ type detected, enable job control in rlogin sessions)
+ * parsing: assignments containing brackets ([]) not treated as commands.
+ * editing: terminal column width determined correctly on startup.
+ * vi: long prompts truncated (more or less) correctly.
+ * file completion: files of the form ~user (no /'s) expanded correctly.
+
+* at&t ksh method for delimiting hidden characters in prompt added (i.e.,
+ start prompt with non-printing char and \r, use char to delimit esc codes).
+
+
+Version 5.2.8
+
+* bug fixes
+ * configuration: handle FreeBSD's strange S_ISSOCK.
+ * test: added == operator.
+ * configuration: fixed opendir/dirent usage.
+ * redirections before subshells handled correctly.
+ * COLUMNS/LINES are no longer exported when they are automatically set.
+ * mail checks and PS1/PS4 expansions removed if compiled as sh.
+ * subcommands in PS1 no longer generate bogus warning messages.
+ * environment variables not longer messed up on 16-bit machines.
+ * unset: now returns non-zero if variable/function isn't set.
+ * select: only prints menu first time, if REPLY is null or on blank line.
+ * check for `cannot execute' improved, error message says why.
+ * typeset: now reports variables with attributes but now value.
+ * vi/emacs file completion: does directory listing on zero length names.
+ * arithmetic: non-numeric parameters expanded recursively.
+ * arithmetic: identifiers in unevaluated part of ?:,&&,|| parsed correctly.
+ * functions: unsetting a function within itself is now safe.
+ * arrays: unsetting element 0 of an array no longer kills the whole array.
+ * co-processes now behave like ksh93 co-processes (and less like ksh88).
+
+* functions declared with "function foo" are treated differently (from those
+ declared with "foo()"): $0 is (not) set to the function name, assignments
+ before function calls aren't (are) kept in the parent shell.
+
+* vi: added vi-esccomplete option for people who want ESC-ESC completion.
+
+* vi/emacs: now notice window size changes (but not while editing a line).
+
+* emacs: <esc># now does the comment/uncomment thing.
+
+* arithmetic: ++, -- and , added.
+
+
+Version 5.2.7
+
+* bug fixes
+ * vi: commands can be longer that 16 chars...
+
+
+Version 5.2.6
+
+* bug fixes
+ * break/continue: if too big a number is given, last enclosing loop is used.
+ * set: set +o now generates a set command that can be saved and executed.
+ * COLUMNS/LINES are now exported when they are automatically set.
+ * emacs: completion: space not added after directory names.
+ * vi: # command inserts # after each newline; # on commented line
+ undoes the commenting.
+ * some regression tests made less sensitive to their environment.
+ * should compile on os/2 again.
+
+
+Version 5.2.5
+
+* bug fixes
+ * configuration: if sig_setjmp() being used, use sigjmp_buf.
+ * configuration: test for times() fixed.
+ * configuration: ANSI usage of setjmp() and offsetof().
+ * echo/print: octal number in \ sequence must start with a 0.
+ * echo: don't treat a lone minus as an option.
+ * typeset -f: correctly prints functions with select statements.
+ * vi: / with no pattern repeats last search.
+ * vi: repeat counts no longer effect file completion/expansion.
+ * vi: tab-completion now also works in command mode.
+ * emacs/vi: ^O key now read as ^O on suns/alphas (was eaten by tty driver).
+ * emacs: now has file expansion (^[*).
+ * emacs: ^O goes to next command, not next next command.
+ * COLUMNS/LINES: environment variables now set on start up.
+ * variables: command line assignments can't change readonly variables.
+ * arithmetic: giving multiple bases (5#4#3) no longer allowed.
+ * arithmetic: when assigning a non-integer variables, base no longer shown.
+ * history: fixed replacement bug introduced in last release.
+ * history: -1 refers to the previous command, not current fc command.
+ * parsing: correctly handles command substitutions starting with a newline.
+
+* full command completion added (both vi and emacs).
+
+
+Version 5.2.4
+
+* bug fixes
+ * PS1 imported from environment again.
+ * vi handles prompts with embedded newlines.
+ * errors redirecting stderr aren't lost.
+ * redirection errors for <&n no longer reported as to >&n.
+ * don't do globbing on re-direction targets if not interactive (POSIX).
+ * pattern matching in [[ foo = foo*bar ]] now works again.
+ * HUP signals are passed on to jobs running in the foreground.
+ * $? now valid (ie, not 0) in trap handlers, `...` expressions, etc.
+ * noclobber doesn't effect redirections to non-regular files (eg, /dev/null)
+ * \newline in here-document delimiters handled correctly.
+ * typeset -f now reports unloaded autoload functions properly.
+ * ~,~+,~- are not expanded if HOME,PWD,OLDPWD are unset.
+ * vi completion/expansion: * not appeded if word contains $.
+ * cd: error message contains correct directory string.
+ * vi expansion list: printed in column form ala at&t ksh.
+ * ^C while reading .profile/$ENV nolonger causes shell to exit.
+ * option errors for build-in commands now include command name.
+ * emacs completion/expansion: ' and " are treated as word delimiters.
+ * fc: replacements (a=b) no longer truncates the command.
+ * alias: alias -t -r now cleans out the tracked alias table.
+
+* compile-time configuration changed: configure script --enable-XXX options
+ replace the old options.h file. Use "configure --help" for information
+ on what the options do (they are basicly the same as what was in the
+ options.h file). Shell can be configured as a (almost) plain bourne
+ shell using the --enable-shell=sh (also generates appropriate man page).
+ Installed name of program (ksh or sh) can be modified using configure's
+ --program-* options.
+
+* ulimit: added -p (maxproc) option.
+
+* case statements can use the old syntax of {,} instead of in,esac.
+
+* extended file globbing added (eg, f*(bar|Bar) matches f, fbar fBarbar, etc).
+
+* trim expressions can be of the form ${parameter#pattern1|pattern2|...}.
+
+* if compiled as sh, $ENV included only if posix option is set.
+
+* vi: U command added (undo all changes on line).
+
+* the Bugs script has been replaced by a new regression testing system, kept
+ in the tests/ directory (contains a perl script which sets up a test
+ environment and runs tests, and a bunch of tests).
+
+
+Version 5.2.3
+
+* bug fixes
+ * arrays: set -A and unset now unset whole array.
+ * history(complex version): fixed core caused by uninitialized hist_source.
+ * getopts: will continue parsing options if called after error.
+ * getopts: doesn't print shell name twice in error message.
+ * posix: if posix option is set, $0 is always the name of the shell.
+ * history: "fc -s foo" now finds foo if it is the most recent command.
+ * let: expression errors no longer cause scripts to exit.
+ * PS1: does not go into infinite loop if there is an expansion error.
+ * configure: memmove/bcopy test has a change of working now.
+ * configure: check for flock(), undefine COMPLEX_HISTORY if not found.
+ * substitution: tilde substitution works in word part of ${var[-+=?]word}.
+ * history: "fc <number>" now edits <number>, not <number> to most recent.
+ * cd: two argument form works again.
+ * special commands taking assignments (alias,set,etc.): field splitting,
+ file globbing, etc. suppressed only for args that look like assignments.
+ * command: -V now finds reserved words.
+
+* added support for Korn's /dev/fd tests
+
+* new compile time option: DEFAULT_ENV - if defined, it names a file to
+ include if $ENV is not set.
+
+* test -o option: if option starts with a !, the test is negated. The test
+ always fails if the option doesn't exist (so [ -o foo -o -o !foo ] is true
+ iff option foo exists).
+
+* new option: set -o nohup (currently on by default) - if set, running jobs
+ are not kill -HUP'd when a login shell exits; if clear, they are. In
+ future, this will be clear by default (to act like at&t ksh) - if you don't
+ (won't) like this, add "[ -o !nohup ] && set -o nohup" to your .profile.
+
+Version 5.2.2
+
+* bug fixes
+ * included c_test.h in distribution (opps).
+
+Version 5.2.1
+
+* bug fixes
+ * emacs: buffer no longer overflowed when completing file names/commands.
+ * emacs: <ESC><tty-erase-char> now bound to delete-back-word (was ...-char).
+ * emacs: ignores a space char after ^V (version), as in at&t ksh.
+ * emacs: ^O bound to newline-and-next, ^X^Y bound to list-file.
+ * emacs: emacs words now include underscore.
+ * vi: set -o markdirs, directories and ^[= now get along.
+ * cd: -P no longer leaves .. and . in PWD.
+ * cd: if CDPATH set and can't cd, error doesn't contain any of CDPATH.
+ * cd: sets PWD properly, on machines without getwd().
+ * configuration: unistd.h test fixed (include sys/types before dirent.h).
+ * configuration: detects memmove/bcopy's that don't handle overlaps.
+ * [[ ... ]] does lazy evaluation (eg, [[ ! -f foo || $(<foo) = bar ]] does
+ not evaluate $(<foo) if foo doesn't exist).
+
+
+Version 5.2.0
+
+* bug fixes
+ * vi: completion now allows globbing characters.
+ * vi: can deal with very long prompts.
+ * vi: . (redo) works after j, k, return.
+ * vi: [dyc]% causing backwards motion now gets correct start/end position.
+ * vi: complete_word (<ESC>\) no longer rings bell on ambiguous matches.
+ * vi: globbing doesn't append * if last component of file has globbing chars.
+ * emacs: most commands now take arguments, arguments can be multi digit.
+ * emacs: newline-and-next command works more correctly.
+ * after set -u, trimming substitutions no longer automatically fail.
+ * set -i no longer reports an internal error.
+ * FPATH: no longer incorrectly complains about function not being defined.
+ by a file; when it connectly complains, shell name in error is correct.
+ * set -a; set -o allexport: these now do something.
+ * shell deals with non-blocking input (clears non-blocking flag).
+ * autoconf: fixed memmove/memcpy tests.
+ * ! translation in prompt now done before parameter substitution.
+ * siglist.sh works around bug in bash 1.4.3.
+ * correct positional parameters accessible in local assignments.
+ * (sleep 100&) no longer waits for sleep to complete.
+
+* fc -s option added (same as -e -).
+
+* vi: ^V command (version) added.
+
+* vi: @<char> macros added (@X executes vi commands in alias _X).
+
+* emacs: bind -l lists all command names.
+
+* emacs: goto-history command added.
+
+* emacs: search-char function changed to search-char-forward;
+ added search-char-backward (bound to <ESC>^]).
+
+* cd and pwd take -L and -P options; added set -o physical option
+ (PWD,OLDPWD no longer readonly).
+
+* new command line -l option tells shell it is a login session.
+
+* os2 changes completed.
+
+* uses autoconf 2.x (was using 1.x).
+
+Version 5.1.3
+
+* bug fixes
+ * fixed bug in arithmetic expression evaluation (||,&& caused core dump).
+ * ulimit code now uses rlim_t or quad_t, if appropriate.
+ * vi: file completion in command mode of single character filename works.
+ * vi: file completion with markdirs set resulted in two trailing /'s.
+ * vi: completion/expansion/listing acts like at&t ksh when expand fails.
+ * vi: ~ takes count.
+ * lines from history file are no longer negative (easy history).
+ * Makefile now uses manual extension consistently.
+ * fc now allows out of range relative (negative) numbers.
+ * functions with elif now printed correctly.
+ * FPATH now searched if PATH search fails, as in at&t ksh.
+
+* typeset -f output is readable (and more correct)
+
+* compiles under SCO unix
+
+* more os/2 changes integrated
+
+Version 5.1.2
+
+* bug fixes
+ * for i; do ...; done now accepted.
+ * leading non-white-space IFS chars no longer ignored (now delimit fields).
+ * fixed globbing code so echo /usr/*/make works.
+
+Version 5.1.1
+
+* bug fixes
+ * { ..;} allowed instead of do ..;done in for/select loops
+ * EOF after ; or & no longer causes syntax error
+ * complex history: when shrinking history file, keeps inside buffer space.
+ * vi editing: `v' on modified line no longer changes command numbering.
+ * ^C in vi/emacs no longer prints two newlines.
+ * long arguments (> 255) with globbing characters don't cause core dumps.
+
+* new (un)option, KSH, which compiles out ksh code (for producing minimal sh).
+
+* os/2 changes partly merged.
+
+Version 5.1.0
+
+* bug fixes
+ * problem caused by _POSIX_VDISABLE on BSDI machines fixed
+ * exit status set to 127 if command file could not be opened
+ * profile files processed if basename argv[0] starts with (was $0)
+ * PWD now imported properly from environment.
+ * emacs code now either uses dynamic buffers or does overflow checking.
+ * emacs forward-word and delete-forward-word now work like other emacs's.
+ * ^C/^\ in vi/emacs work like at&t ksh (prompt reprinted, even if trapped).
+ * history number to command mapping now constant (numbers used to change).
+ * configuration: BSD tty now used on ultrix (avoids type ahead problem)
+ * eof in the middle of multiline commands now ignored if ignoreeof set.
+ * vi space command now works again.
+ * pointer mismatch compiler warning for waitpid() call dealt with.
+ * emacs internal memory error in command completion fixed.
+ * autoloaded functions now work first try.
+ * SECONDS parameter now acts like in at&t ksh.
+
+* sense of vi-show8 option changed: 8-bit characters are printed as is by
+ default; turning on vi-show8 now causes chars with 8th bit set to be
+ prefixed with M-.
+
+* missing sections in man page added (now basicly complete)
+
+* emacs ^V command added: prints ksh version
+
+* vi g command added: moves to most recent history
+
+Version 5.0.10
+
+* bug fixes
+ * [[ ]] construct unbroken.
+ * the newline after a here document marker is now read properly.
+ * blank lines no longer cause $? to be set to 0.
+ * mail checking now uses atime/mtime instead of size.
+ * changing attributes of exported parameters no longer causes core dump.
+ * the last command in a file does not have to end in a newline.
+ * empty expressions now treated as 0 (previously generated an error).
+ * nul bytes stripped from input.
+ * 0241 (M-!) in a command substitution no longer lost.
+ * when read used in startup file, line continuation no longer causes crash.
+ * very long commands in history no longer cause vi to overwrite memory.
+ * easy history: when saving history, avoid going past the end of history.
+ * emacs mode no longer entered if EDITOR/VISUAL set to null string.
+ * command -p disabled in restricted mode.
+ * closed file descriptors are re-closed after a redirection.
+ * lone [ (test command) no longer causes globbing code to search directory.
+ * if TIMES_BROKEN is defined, ksh_times no longer recurses infinitely.
+ * `r r' no longer repeats r command forever.
+ * make depend no longer generates backslash followed by a blank line.
+ * globbing code now deals with symlinks that point to non-existent files.
+ * if the ] is missing in a pattern, the [ matches the [ character.
+ * syntax errors in test no longer have two newlines.
+ * in vi, G now goes to the oldest history (was newest).
+ * configuration: test for sys_siglist now harder for optimizers to break.
+ * configuration: look for clock_t in sys/times.h.
+ * configuration: use _SIGMAX, if available, for # of signals.
+ * SIGHUP now causes builtin read command to exit.
+ * wait builtin now returns whenever a trapped signal occurs as per POSIX.
+
+* v command now works in vi; anchored searches now work in vi mode (/^ptrn);
+ multi-line commands displayed correctly by history.
+
+* echo is now schizophrenic: accepts -n/-e/-E and backslash sequences.
+
+* test -H file added (checks for context dependent files on HPs).
+
+* set -o gmacs and markdirs honoured.
+
+* ansi arrow keys in default emacs key bindings.
+
+* ulimit now takes arithmetic expression (as per Korn book).
+
+* co-processes changed to be more compatible with at&t ksh.
+
+Version 5.0.9
+
+* bug fixes
+ * FOO is put in the environment for FOO=bar exec blah.
+ * compiles under QNX and with dmake.
+ * the file pattern [!a--]* is now invalid (POSIX) (used to match everything)
+ * echo "${foo:-"a"}*" no longer the same as echo a*.
+ * alternation (brace expansion) fixes:
+ * brace expansion done after variable expansion, as in csh/at&t ksh.
+ * `echo a{b,c' no longer gives "Missing }" error (it echos a{b,c).
+ * expansion only done if there is a comma (ie, `echo {a}' prints {a}).
+ * globbing/expansion code passes 0x80 unharmed.
+ * "echo ${XX=a*b}" no longer sets XX to "a\200*b".
+ * "echo ${unset-a*b}" no longer has \200 in the error message.
+ * bad substitution error generated for things like ${x:a}, ${x^a}, etc.
+ * `x="a cdef"; echo ${x#a c}' now prints "def" instead of "a a cdef".
+ * on systems where /etc/passwd//// is a valid name, echo /etc/pass*/ no
+ longer matches /etc/passwd.
+ * trace output (set -x) flushed correctly, PS4 initialized.
+ * ulimit output ungarbled, code to use {set,get}ulimit (if available)
+ enabled.
+ * tilde expansion done in word part of ${foo-~/bar}
+ * when reading stdin (ie, ksh -s), no longer reads too much.
+ * shell handles i/o redirection and errors in builtin commands as per
+ POSIX (still have to sort out variable assignment errors).
+ * starting jobs that save/change/restore tty settings in the background
+ no longer messes up tty settings when job finishes.
+ * the pattern [a'-'z] now matches three characters, not 26, and
+ the pattern [ab']'] also matches three characters.
+
+* a mostly complete man page! (work is still in progress)
+
+* quoting inside $(..) mostly works.
+
+* error reporting has been orthogonalized.
+
+* brace expansion on by default (can be disabled with set +o braceexpand, or
+ set -o posix).
+
+* output of "set -o" now fits on a normal screen.
+
+* co-processes added (|&, read -p, print -p, etc.).
+
+* restricted mode added (for what its worth).
+
+* vi now prints meta characters with M- prefix, unless vi-show8 option is on.
+
+Version 5.0.8
+
+* bug fixes
+ * two problems in fc (introduced in 5.0.7)
+ * install target in Makefile missing a dollar
+
+Version 5.0.7
+
+* POSIX command command added
+
+* a few bug fixes
+ * now compiles with various options undefined (eg, VI, EMACS, JOBS).
+ * fixed typos in Makefile.in (maxext -> manext) and ksh.1 (\f -> \fP).
+ * CLK_TCK defined to correct value for FreeBSD 1.1.5 (and earlier?).
+ * original process group restored when an exec is done.
+ * the exit value of set is that of the last $(...) on the command line.
+ * ditto for a command with no command (eg, x=`false`).
+ * command variable assignments done before path search (so PATH=... x works)
+ and are added as they are processed (so A=1 B=$A works).
+ * variable assignments infront of function calls are exported to programs
+ inside the function.
+ * aliases with trailing space are only honoured in command contexts
+ if in posix mode.
+
+* make depend target added; install target warns if ksh not in /etc/shells.
+
+* set -o bgnice now does something.
+
+* vi mode: ESC is no longer a file completion command (too annoying).
+
+Version 5.0.6
+
+* most reported bugs/problems fixed (all but two).
+
+* temporary files now created in $TMPDIR (if it is a sane path).
+
+Version 5.0.5
+
+* function parsing POSIXized (function bodies can be any compound command,
+ redirections after functions effect function invocation, not the
+ instantiation, the () in a function definition now parsed as two tokens).
+
+* exit bultin now does stopped jobs check.
+
+* set -p/-o priviliged supported.
+
+* test builtin now believed to be completely posix.
+
+* a default path is now used when PATH is not set (defined in options.h).
+
+Version 5.0.4
+
+* configuration checks for buggy opendir()s and setpgrp()s.
+
+* autoloading functions now supported.
+
+* functions can safely redefine themselves.
+
+Version 5.0.3
+
+* hash command changed to "alias -t"; whence -p added; print -s added
+ (all as in at&t ksh); unalias -a added (POSIX).
+
+* test builtin POSIX complient
+
+* TMOUT parameter supported (at&t ksh: timeout interactive shells)
+
+Version 5.0.2
+
+* trap/error handling changed to eliminate longjmp()s from signal handlers;
+ trap ERR added.
+
+* ksh conditional expressions ([[ .. ]]) supported.
+
+* arithmetic expressions (let, $((..)), etc.) now understand full C
+ integer expressions (except ++/-- and sizeof()).
+
+* typeset -L -R -Z -u -l added (as in at&t ksh)
+
+* at&t/posix $(( .. )) arithmetic expansions supported.
+
+Version 5.0.1
+
+* set -e no longer effects commands executed as part of if/while/until/&&/||/!
+ condition.
+
+* posix ! keyword now recognized.
+
+* posix getopts; if not in posix mode, getopts will accept options starting
+ with + (at&t kshism)
+
+* syntax error messages improved (says what was unexpected/unmatched)
+
+Version 4.9+mun.5
+
+* all known bugs related to job control fixed:
+ * fg,bg,jobs,wait,kill commands fully POSIX complient
+ * signals are no longer reported for foreground jobs killed by SIGINT and
+ SIGPIPE
+ * pipeline process groups now created more reliablely (was a problem
+ if first process exited before second process exec'd).
+ * "(: ; cat /etc/termcap) | sleep" nolonger hangs
+
+* save/restore tty mode if command succeeds/fails, respectively. Edit
+ mode (emacs,vi) no longer use old tty mode information
+
+* test command: added -h
+
+* alternations option renamed to braceexpand (eg, use set -o braceexpand).
+ Old usage (set -o alternations) still accepted (will disappear in next
+ version).
+
+* trap/kill now accept upper and lower case signal names.
+
+Version 4.9+mun.3
+
+* here documents in functions now work properly
+
+* read command: added -s option, use REPLY if no variable specified
+
+* don't accept "while command; done" as a valid command
+
+* fg,bg,jobs,wait,kill commands mostly POSIX complient.
+
+* unset command: added POSIX -v option
+
+* set command: added -A option
+
+* handle ${array[@]} and ${array[*]}
+
+* compiles with old bsd 4.2 compiler (pcc)
+
+* new versions of etc/profile and etc/ksh.profile
+
+Version 4.9+mun.2 (versus 4.9)
+
+* directory/file structure has been re-arranged:
+ * moved files from sh directory up a level, deleted sh directory
+ * created misc directory, old ChangeLog,README,.. files moved to misc
+
+* now uses GNU autoconf for compilation.
+
+* no longer uses stdio FILE *'s for I/O redirection (most stdio
+ usage has been removed). Solves many porting problems caused by
+ dup'd file descriptors, forked processes and exiting.
+
+* removed lint from code (compiles with very few warning with gcc -O -Wall
+ -Wno-comment)
+
+* has array support (needs work but is pretty functional).
+
+* ulimit command now more functional on more machines. Compatible with at&t ksh.
+
+* command line and set option parsing cleaned up, POSIXized.
+
+* POSIX IFS handling.
+
+* many many small bug fixes (see ChangeLog)
diff --git a/NOTES b/NOTES
@@ -0,0 +1,545 @@
+$OpenBSD: NOTES,v 1.12 2013/11/28 10:33:37 sobrado Exp $
+
+General features of at&t ksh88 that are not (yet) in pdksh:
+ - exported aliases and functions (not in ksh93).
+ - set -t.
+ - signals/traps not cleared during functions.
+ - trap DEBUG, local ERR and EXIT traps in functions.
+ - ERRNO parameter.
+ - doesn't have posix file globbing (eg, [[:alpha:]], etc.).
+ - use of an `agent' to execute unreadable/setuid/setgid shell scripts
+ (don't ask).
+ - read/select aren't hooked in to the command line editor
+ - the last command of a pipeline is not run in the parent shell
+
+Known bugs (see also BUG-REPORTS and PROJECTS files):
+ Variable parsing, Expansion:
+ - some specials behave differently when unset (eg, IFS behaves like
+ " \t\n") others lose their special meaning. IFS/PATH taken care of,
+ still need to sort out some others (eg, TMOUT).
+ Parsing,Lexing:
+ - line numbers in errors are wrong for nested constructs. Need to
+ keep track of the line a command started on (can use for LINENO
+ parameter as well).
+ - a $(..) expression nested inside double quotes inside another $(..)
+ isn't parsed correctly (eg, $(echo "foo$(echo ")")") )
+ Commands,Execution:
+ - setting special parameters that have side effects when
+ changed/restored (ie, HISTFILE, OPTIND, RANDOM) in front
+ of a command (eg, HISTFILE=/foo/bar echo hi) effects the parent
+ shell. Note that setting other (not so special) parameters
+ does not effect the parent shell.
+ - `echo hi | exec cat -n' causes at&t to exit, `exec echo hi | cat -n'
+ does not. pdksh exits for neither. Don't think POSIX requires
+ an exit, but not sure.
+ - `echo foo | read bar; echo $bar' prints foo in at&t ksh, nothing
+ in pdksh (ie, the read is done in a separate process in pdksh).
+ Misc:
+
+Known problems not caused by ksh:
+ - after stoping a job, emacs/vi is not re-entered. Hitting return
+ prints the prompt and everything is fine again. Problem (often
+ involving a pager like less) is related to order of process
+ scheduling (shell runs before `stop'ed (sub) processes have had a chance
+ to clean up the screen/terminal).
+
+Known differences between pdksh & at&t ksh (that may change)
+ - vi:
+ - `^U': at&t: kills only what has been inserted, pdksh: kills to
+ start of line
+ - at&t ksh login shells say "Warning: you have running jobs" if you
+ try to exit when there are running jobs. An immediate second attempt
+ to exit will kill the jobs and exit. pdksh does not print a warning,
+ nor does it kill running jobs when it exits (it does warn/kill for
+ stopped jobs).
+ - TMOUT: at&t prints warning, then waits another 60 seconds. If on screwed
+ up serial line, the output could cause more input, so pdksh just
+ prints a message and exits. (Also, in at&t ksh, setting TMOUT has no
+ effect after the sequence "TMOUT=60; unset TMOUT", which could be
+ useful - pdksh may do this in the future).
+ - in pdksh, if the last command of a pipeline is a shell builtin, it is
+ not executed in the parent shell, so "echo a b | read foo bar" does not
+ set foo and bar in the parent shell (at&t ksh will).
+ This may get fixed in the future, but it may take a while.
+ - in pdksh, set +o lists the options that are currently set, in at&t ksh
+ it is the same as set -o.
+ - in pdksh emacs mode, ^T does what gnu emacs does, not what at&t ksh
+ does.
+ - in ksh93, `. name' calls a function (defined with function) with POSIX
+ semantics (instead of ksh semantics). in pdksh, . does not call
+ functions.
+ - test: "test -f foo bar blah" is the same as "test -f foo" (the extra
+ arguments, of which there must be at least 2, are ignored) - pdksh
+ generates an error message (unexpected operator/operand "bar") as it
+ should. Sometimes used to test file globs (e.g., if test -f *.o; ...).
+ - if the command 'sleep 5 && /bin/echo blah' is run interactively and
+ is the sleep is stopped (^Z), the echo is run immediately in pdksh.
+ In at&t ksh, the whole thing is stopped.
+ - LINENO:
+ - in ksh88 variable is always 1 (can't be changed) in interac mode;
+ in pdksh it changes.
+ - Value of LINENO after it has been set by the script in one file
+ is bizarre when used in another file.
+
+Known differences between pdksh & at&t ksh (that are not likely to change)
+ - at&t ksh seems to catch or ignore SIGALRM - pdksh dies upon receipt
+ (unless it's traped of course)
+ - typeset:
+ - at&t ksh overloads -u/-l options: for integers, means unsigned/long,
+ for strings means uppercase/lowercase; pdksh just has the
+ upper/lower case (which can be useful for integers when base > 10).
+ unsigned/long really should have their own options.
+ - at&t ksh can't have justified integer variables
+ (eg, typeset -iR5 j=10), pdksh can.
+ - in pdksh, number arguments for -L/-R/-Z/-i must follow the option
+ character, at&t allows it at the end of the option group (eg,
+ at&t ksh likes "typeset -iu5 j", pdksh wants "typeset -i5 -u j"
+ or "typeset -ui5 j"). Also, pdksh allows "typeset -i 5 j" (same
+ as "typeset -i5 j"), at&t ksh does not allow this.
+ - typeset -R: pdksh strips trailing space type characters (ie,
+ uses isspace()), at&t ksh only skips blanks.
+ - at&t ksh allows attributes of read-only variables to be changed,
+ pdksh allows only the export attribute to be set.
+ - (some) at&t ksh allows set -A of readonly variables, pdksh does not.
+ - at&t ksh allows command assignments of readonly variables (eg, YY=2 cat),
+ pdksh does not.
+ - at&t ksh does not exit scripts when an implicit assignment to an integer
+ variable fails due to an expression error: eg,
+ echo 2+ > /tmp/x
+ unset x; typeset -i x
+ read x < /tmp/x
+ echo still here
+ prints an error and then prints "still here", similarly for
+ unset x; typeset -i x
+ set +A x 1 2+ 3
+ echo still here
+ and
+ unset x y; typeset -i x y; set +A y 10 20 30
+ set +A x 1 1+y[2+] 3
+ echo still here
+ pdksh exits a script in all the above cases. (note that both shells
+ exit for:
+ unset x; typeset -i x
+ for x in 1 2+ 3; do echo x=$x; done
+ echo still here
+ ).
+ - at&t ksh seems to allow function calls inside expressions
+ (eg, typeset -i x='y(2)') but they do not seem to be regular functions
+ nor math functions (eg, pow, exp) - anyone known anything about this?
+ - `set -o nounset; unset foo; echo ${#foo}`: at&t ksh prints 0; pdksh
+ generates error. Same for ${#foo[*]} and ${#foo[@]}.
+ - . file: at&t ksh parses the whole file before executing anything,
+ pdksh executes as it parses. This means aliases defined in the file
+ will affect how pdksh parses the file, but won't affect how at&t ksh
+ parses the file. Also means pdksh will not parse statements occurring
+ after a (executed) return statement.
+ - a return in $ENV in at&t ksh will cause the shell to exit, while in
+ pdksh it will stop executing the script (this is consistent with
+ what a return in .profile does in both shells).
+ - at&t ksh does file globbing for `echo "${foo:-"*"}"`, pdksh does not
+ (POSIX would seem to indicate pdksh is right).
+ - at&t ksh thinks ${a:##foo} is ok, pdksh doesn't.
+ - at&t does tilde expansion on here-document delimiters, pdksh does
+ not. eg.
+ $ cat << ~michael
+ ~michael
+ $
+ works for pdksh, not for at&t ksh (POSIX seems to agree with pdksh).
+ - in at&t ksh, tracked aliases have the export flag implicitly set
+ and tracked aliases and normal aliases live in the same name space
+ (eg, "alias" will list both tracked and normal aliases).
+ in pdksh, -t does not imply -x (since -x doesn't do anything yet), and
+ tracked/normal aliases live in separate name spaces.
+ in at&t ksh, alias accepts + options (eg, +x, +t) - pdksh does not.
+ in pdksh, alias has a -d option to allow examination/changing of
+ cached ~ entries, also unalias has -d and -t options (unalias -d
+ is useful if the ~ cache gets out of date - not sure how at&t deals
+ with this problem (it does cache ~ entries)).
+ - at&t ksh will stop a recursive function after about 60 calls; pdksh
+ will not since the limit is arbitrary and can't be controlled
+ by the user (hit ^C if you get in trouble).
+ - the wait command (with and without arguments) in at&t ksh will wait for
+ stopped jobs when job control is enabled. pdksh doesn't.
+ - at&t ksh automatically sets the bgnice option for interactive shells;
+ pdksh does not.
+ - in at&t ksh, "eval `false`; echo $?" prints 1, pdksh prints 0 (which
+ is what POSIX says it should). Same goes for "wait `false`; echo $?".
+ (same goes for "set `false`; echo $?" if posix option is set - some
+ scripts that use the old getopt depend on this, so be careful about
+ setting the posix option).
+ - in at&t ksh, print -uX and read -uX are interrperted as -u with no
+ argument (defaults to 1 and 0 respectively) and -X (which may or
+ may not be a valid flag). In pdksh, -uX is interpreted as file
+ descriptor X.
+ - in at&t ksh, some signals (HUP, INT, QUIT) cause the read to exit, others
+ (ie, everything else) do not. When it does cause exiting, anything read
+ to that point is used (usually an empty line) and read returns with 0
+ status. pdksh currently does similar things, but for TERM as well and
+ the exit status is 128+<signal-number> - in future, pdksh's read will
+ do this for all signals that are normally fatal as required by POSIX.
+ (POSIX does not require the setting of variables to null so applications
+ shouldn't rely on this).
+ - in pdksh, ! substitution done before variable substitution; in at&t ksh
+ it is done after substitution (and therefor may do ! substitutions on
+ the result of variable substitutions). POSIX doesn't say which is to be
+ done.
+ - pwd: in at&t ksh, it ignores arguments; in pdksh, it complains when given
+ arguments.
+ - the at&t ksh does not do command substition on PS1, pdksh does.
+ - ksh93 allows ". foo" to run the function foo if there is no file
+ called foo (go figure).
+ - field splitting (IFS): ksh88/ksh93 strip leading non-white space IFS
+ chars, pdksh (and POSIX, I think) leave them intact. e.g.
+ $ IFS="$IFS:"; read x; echo "<$x>"
+ ::
+ prints "<>" in at&t ksh, "<::>" in pdksh.
+ - command completion: at&t ksh will do completion on a blank line (matching
+ all commands), pdksh does not (as this isn't very useful - use * if
+ you really want the list).
+ - co-processes: if ksh93, the write portion of the co-process output is
+ closed when the most recently started co-process exits. pdksh closes
+ it when all the co-processes using it have exited.
+ - pdksh accepts empty command lists for while and for statements, while
+ at&t ksh (and sh) don't. Eg., pdksh likes
+ while false ; do done
+ but ksh88 doesn't like it.
+ - pdksh bumps RANDOM in parent after a fork, at&t ksh bumps it in both
+ parent and child:
+ RANDOM=1
+ echo child: `echo $RANDOM`
+ echo parent: $RANDOM
+ will produce "child: 16838 parent: 5758" in pdksh, while at&t ksh
+ will produce "child: 5758 parent: 5758".
+
+Oddities in ksh (pd & at&t):
+ - array references inside (())/$(()) are strange:
+ $(( x[2] )) does the expected, $(( $x[2] )) doesn't.
+ - `typeset -R3 X='x '; echo "($X)"` produces ( x) - trailing
+ spaces are stripped.
+ - typeset -R turns off Z flag.
+ - both shells have the following mis-feature:
+ $ x='function xx {
+ cat -n <<- EOF
+ here we are in xx
+ EOF
+ }'
+ $ (eval "$x"; (sleep 2; xx) & echo bye)
+ [1] 1234
+ bye
+ $ xx: /tmp/sh1234.1: cannot open
+ - bizarre special handling of alias/export/readonly/typeset arguments
+ $ touch a=a; typeset a=[ab]; echo "$a"
+ a=[ab]
+ $ x=typeset; $x a=[ab]; echo "$a"
+ a=a
+ $
+ - both ignore SIGTSTP,SIGTTIN,SIGTTOU in exec'd processes when talking
+ and not monitoring (at&t ksh kind of does this). Doesn't really make
+ sense.
+ (Note that ksh.att -ic 'set +m; check-sigs' shows TSTP et al aren't
+ ignored, while ksh.att -ic 'set +m^J check-sigs' does... very strange)
+ - when tracing (set -x), and a command's stderr is redirected, the trace
+ output is also redirected. so "set -x; echo foo 2> /tmp/O > /dev/null"
+ will create /tmp/foo with the lines "+ > /dev/null" and "+ echo foo".
+ - undocumented at&t ksh feature: FPATH is searched after PATH if no
+ executable is found, even if typeset -uf wasn't used.
+
+at&t ksh bugs:
+ [various versions:
+ MIPS m120 RISC/os 5.0: Version 11/16/88d
+ Dec alpha osf/1 v1.3: OSF/1 Version 11/16/88d NLS
+ HP pa HP-UX 9.01: Version 11/16/88
+ ]
+ - (only hpux)
+ $ _[2]=hi
+ Bus error (core dumped)
+ - (only riscos, hpux)
+ $ typeset x[
+ $
+ - (only osf/1)
+ $ A=B cat << EOF
+ .$A.
+ EOF
+ Segmentation fault(coredump)
+ $
+ - (only osf/1)
+ $ read "?foo "
+ foo Foo
+ $ set | grep Foo
+ =Foo
+ $
+ - (all)
+ $ typeset -i A
+ $ typeset -L3 A
+ $ typeset -l A
+ Illegal instruction (core dumped)
+ - (all)
+ $ for i in a b c ; do echo $i, ${i[2]}, ${i[10]} ; done
+ a, ,
+ a, , b
+ a, , c
+ $
+ - (all)
+ $ echo ${abc:-G { I } K }
+ G { I K }
+ $
+ $ abc=hi
+ $ echo ${abc:-G { I } K }
+ hi K }
+ $
+ The second echo should only have printed `hi'.
+ - (all)
+ $ echo ${abc:- > foo}
+ syntax error: > unexpected
+ $
+ - (all? hpux) read reads too much from pipe (when pipe isn't stdin)
+ print 'hi\nthere' | ksh 8<&0 0< /dev/tty
+ $ read -u8 x
+ $ print $x
+ hi
+ $ cat 0<&8
+ $ read -u8 y
+ $ print $y
+ there
+ $
+ - (all)
+ $ umask 0
+ $ umask
+ 00
+ $
+ - (osf, mips, !hpux)
+ $ exec alias
+ alias: not found
+ (shell dead)
+ - (all) non-white space IFS in non-substitution not preserved
+ $ IFS="$IFS:"
+ $ echo : "$@" # this is ok
+ :
+ $ echo :"$@" # this should print : too (me thinks)
+
+ $
+ - (only osf/1)
+ $ set +m
+ $ sleep 1 & # wait for a sec or two
+ $ jobs
+ Memory fault (core dumped)
+ - (all)
+ $ (sleep 1 & echo hi) &
+ [1] 123
+ $ [1] 234
+ hi
+ - (osf/1, mips)
+ $ getopts abc optc -a -b -c
+ $ getopts abc optc -a -b -c
+ $ getopts abc optc -a
+ Memory fault (core dumped)
+ - (osf/1) POSIX says OPTIND shall be initialized to 1
+ $ echo $OPTIND
+ 0
+ $
+ - (osf/1 + others?)
+ $ typeset -ri r=10
+ $ let r=12
+ $ echo $r
+ 12
+ $
+ - (osf/1 + others?)
+ $ typeset -i a
+ $ typeset -L3 a
+ Memory fault (core dumped)
+ - (osf/1 + others?): -L strips leading \ \t\n\r, -R only strips trailing
+ spaces
+ $ typeset -L3 x
+ $ x=' ^I^J^M 2'
+ $ echo "($x)"
+ (2 )
+ $ typeset -R3 y
+ $ x='2^I^J^M '
+ $ echo "($x)"
+ (^I^J^M)
+ $
+ - (osf/1 + others?)
+ $ typeset +i RANDOM
+ Memory fault (core dumped)
+ - (osf/1 + others?): -L/-R/-Z clear -l/-u after assignment and vise versa
+ $ typeset -u x=ab
+ $ echo "($x)"
+ (AB)
+ $ typeset -L4 x=def
+ $ echo "($x)"
+ (DEF )
+ $ typeset | grep ' x$'
+ leftjust 4 x
+ $
+ $ typeset -L4 x=def
+ $ echo "($x)"
+ (def )
+ $ typeset -u x=ab
+ $ echo "($x)"
+ (AB )
+ $ typeset | grep ' x$'
+ uppercase x
+ $
+ $ typeset -i x
+ $ x='2()'
+ $ x='()'
+ $ x='2(4)'
+ - (osf/1, others?)
+ $ unset foo
+ $ echo "${foo:-"*"}"
+ <results of * expansion>
+ $
+ - (osf/1, others?)
+ $ alias blah
+ blah: alias not found
+ $ alias -x blah | grep blah
+ blah
+ $ type blah
+ Memory fault (core dumped)
+ - (osf/1, others?)
+ $ trap 'echo hi; false' ERR
+ $ false
+ hi
+ hi
+ ....
+ Memory fault (core dumped)
+ - (osf/1, others?)
+ $ typeset +i ERRNO
+ Memory fault (core dumped)
+ - (osf/1, others?)
+ $ X=abcdef
+ $ echo ${X#a{b,c}e} # does not match {} inside word part of ${..#..}
+ abcdefe}
+ $
+ - (osf/1, others?)
+ $ x=f=abcdef
+ $ echo ${f#a|abc}
+ def
+ $ echo ${f#abc|a}
+ bcdef
+ $ echo ${f#abc|a|d}
+ abcdef
+ $
+ - (osf/1, hp-ux, others?)
+ $ i() echo hi
+ $ typeset -f
+ function i
+ {
+ hi
+ $
+ - (osf/1, others?)
+ $ function X {
+ echo start of X
+ function Y {
+ echo in Y
+ }
+ echo end of X
+ }
+ $ X
+ start of X
+ end of X
+ $ typeset -f
+ function X
+ {
+ echo start of X
+ function Y {
+ echo in Y
+ }
+ echo end of X
+ }
+ function Y
+ {
+ echo in Y
+ echo end of X
+ }
+ }
+ $
+ - (osf/1, others?)
+ $ while read x; do print -r "A $x"; done |&
+ [1] 18212
+ $ exec 8<&p
+ $ kill %1
+ Memory fault
+ - (osf/1, others?) Error only happens for builtin commands (/bin/echo works)
+ $ while read x; do print -r "A $x"; done |&
+ [1] 18212
+ $ echo hi <&p
+ hi
+ $ echo hi <&p
+ ksh: p: bad file unit number
+ $ while read x; do print -r "A $x"; done |&
+ ksh: process already exists
+ $
+ - (osf/1, others?) in restricted shells, command -p should not work.
+ $ PATH=/tmp ksh -r
+ $ print hi | command -p cat -n
+ 1 hi
+ $
+ - (osf/1, others?) error message wrong for autoload files that don't define
+ functions
+ $ FPATH=/tmp
+ $ echo echo hi there > /tmp/aja
+ $ aja
+ hi there
+ ksh: echo: not found
+ $
+ - (SunOS M-12/28/93d):
+ $ cat -n << X $(
+ > echo foo
+ > )
+ > X
+ > echo bar
+ )
+ ./ksh93: X: cannot open [No such file or directory]
+ Memory fault (core dumped)
+
+POSIX sh questions (references are to POSIX 1003.2-1992)
+ - arithmetic expressions: how are empty expressions treated?
+ (eg, echo $(( ))). at&t ksh (and now pdksh) echo 0.
+ Same question goes for `test "" -eq 0' - does this generate an error
+ or, if not, what is the exit code?
+ - should tilde expansion occur after :'s in the word part of ${..=..}?
+ (me thinks it should)
+ - if a signal is received during the execution of a built-in,
+ does the builtin command exit or the whole shell?
+ - is it legal to execute last command of pipeline in current
+ execution environment (eg, can "echo foo | read bar" set
+ bar?)
+ - what action should be taken if there is an error doing a dup due
+ to system limits (eg, not enough feil destriptors): is this
+ a "redirection error" (in which case a script will exit iff the
+ error occured while executing a special built-in)?
+ IMHO, shell should exit script. Couldn't find a blanket statement
+ like "if shell encounters an unexpected system error, it shall
+ exit non-interactive scripts"...
+
+POSIX sh bugs (references are to POSIX 1003.2-1992)
+ - in vi insert mode, ^W deletes to beginning of line or to the first
+ blank/punct character (para at line 9124, section 3). This means
+ "foo ^W" will do nothing. This is inconsistent with the vi
+ spec, which says delete preceding word including and interceding
+ blanks (para at line 5189, section 5).
+ - parameter expansion, section 3.6.2, line 391: `in each case that a
+ value of word is needed (..), word shall be subjected to tilde
+ expansion, parameter expansion, ...'. Various expansions should not
+ be performed if parameter is in double quotes.
+ - the getopts description says assigning OPTIND a value other than 1
+ produces undefined results, while the rationale for getopts suggests
+ saving/restoring the OPTIND value inside functions (since POSIX
+ functions don't do the save/restore automatically). Restoring
+ OPTIND is kind of dumb since getopts may have been in the middle
+ of parsing a group of flags (eg, -abc).
+ - unclear whether arithmetic expressions (eg, $((..))) should
+ understand C integer constants (ie, 0x123, 0177). at&t ksh doesn't
+ and neither does pdksh.
+ - `...` definition (3.6.3) says nothing about backslash followed by
+ a newline, which sh and at&t ksh strip out completely. e.g.,
+ $ show-args `echo 'X
+ Y'`
+ Number of args: 1
+ 1: <XY>
+ $
+ POSIX would indicate the backslash-newline would be preserved.
+ - does not say how "cat << ''" is to be treated (illegal, read 'til
+ blank line, or read 'til eof). at&t ksh reads til eof, bourne shell
+ reads 'til blank line. pdksh reads 'til blank line.
diff --git a/PROJECTS b/PROJECTS
@@ -0,0 +1,114 @@
+$OpenBSD: PROJECTS,v 1.7 2013/11/28 10:33:37 sobrado Exp $
+
+Things to be done in pdksh (see also the NOTES file):
+
+ * builtin utilities:
+ pdksh has most if not all POSIX/at&t ksh builtins, but they need to
+ be checked that they conform to POSIX/at&t manual. Part of the
+ process is changing the builtins to use the ksh_getopt() routine.
+
+ The following builtins, which are defined by POSIX, haven't been
+ examined:
+ eval
+
+ The first pass has been done on the following commands:
+ . : alias bg break cd continue echo exec exit export false fc fg
+ getopts jobs kill pwd read readonly return set shift time trap true
+ umask unalias unset wait
+
+ The second pass (ie, believed to be completely POSIX) has been done on
+ the following commands:
+ test
+
+ (ulimit also needs to be examined to check that it fits the posix style)
+
+ * test suite
+ Ideally, as the builtin utilities are being POSIXized, short tests
+ should be written to be used in regression testing. The tests
+ directory contains some tests, but many more need to be written.
+
+ * internationalization
+ Need to handle with the LANG and LC_* environment variables. This
+ involves changes to ensure <ctype.h> macros are being used (currently
+ uses its own macros in many places), figuring out how to deal with
+ bases (for integer arithmetic, eg, 12#1A), and (the nasty one) doing
+ string look ups for error messages, etc.. It probably isn't worth
+ translating strings to other languages yet as the code is likely
+ to change a lot in the near future, but it would be good to have the
+ code set up so string tables can be used.
+
+ * trap code
+ * add the DEBUG trap.
+ * fix up signal handling code. In particular, fatal vs tty signals,
+ have signal routine to call to check for pending/fatal traps, etc.
+
+ * parsing
+ * the time keyword needs to be hacked to accept options (!) since
+ POSIX says it shall accept the -p option and must skip a -- argument
+ (end of options). Yuck.
+
+ * lexing
+ the lexing may need a re-write since it currently doesn't parse $( .. ),
+ $(( .. )), (( ... )) properly.
+ * need to ignore contents of quoted strings (and escaped chars?)
+ inside $( .. ) and $(( .. )) when counting parentheses.
+ * need to put bounds check on states[] array (if it still exists after
+ the re-write)
+
+ * variables
+ * The "struct tbl" that is currently used for variables needs work since
+ more information (eg, array stuff, fields) are needed for variables
+ but not for the other things that use "struct tbl".
+ * Arrays need to be implemented differently: currently does a linear
+ search of a linked list to find element i; the linked list is not
+ freed when a variable is unset.
+
+ * functions
+ finish the differences between function x and x(): trap EXIT, traps
+ in general, treatment of OPTIND/OPTARG,
+
+ * history
+ There are two versions of the history code, COMPLEX_HISTORY and
+ EASY_HISTORY, which need to be merged. COMPLEX does at&t style history
+ where the history file is written after each command and checked when
+ ever looking through the history (in case another shell has added
+ something). EASY simply reads the history file at startup and writes
+ it before exiting.
+ * re-write the COMPLEX_HISTORY code so mmap() not needed (currently
+ can't be used on machines without mmap()).
+ * Add multiline knowledge to COMPLEX_HISTORY (see EASY_HISTORY
+ stuff).
+ * change COMPLEX_HISTORY code so concurrent history files are
+ controlled by an option (set -o history-concurrent?). Delete
+ the EASY_HISTORY code.
+ * bring history code up to POSIX standards (see POSIX description
+ of fc, etc.).
+
+ * documentation
+ Some sort of tutorial with examples would be good. Texinfo is probably
+ the best medium for this. Also, the man page could be converted to
+ texinfo (if the tutorial and man page are put in the same texinfo
+ page, they should be somewhat distinct - i.e., the tutorial should
+ be a separate thread - but there should be cross references between the
+ two).
+
+ * miscellaneous
+ * POSIX specifies what happens when various kinds of errors occur
+ in special built-ins commands vs regular commands (builtin or
+ otherwise) (see POSIX.2:3.8.1). Some of this has been taken
+ care of, but more needs doing.
+
+ * remove static limits created by fixed sized arrays
+ (eg, ident[], heres[], PATH, buffer size in emacs/vi code)
+
+ * merge the emacs and vi code (should reduce the size of the shell and
+ make maintenance easier); handle SIGWINCH while editing a line.
+ [John Rochester is working on the merge]
+
+ * add POSIX globbing (eg, [[:alnum:]]), see POSIX.2:2.8.3.2.
+
+ * teach shf_vfprintf() about long long's (%lld); also make %p use
+ long longs if appropriate.
+
+ * decide wether to keep currently disabled FP stuff in shf.c; if
+ not, eliminate ksh_limval.h (moving BITS to var.c).
diff --git a/README.md b/README.md
@@ -0,0 +1,25 @@
+oksh
+====
+ksh(1) from OpenBSD ported to FreeBSD.
+Might work on other operating systems too but not tested.
+
+Why?
+----
+Sometimes I have to use FreeBSD. I don't like tcsh.
+OpenBSD's ksh is much better. Now FreeBSD users can use it too.
+Designed to be as minimally invasive as possible.
+
+Changes
+-------
+* Added #include <time.h> in lex.c
+* Added #include <sys/param.h> and changed _PW_NAME_LEN to MAXLOGNAME in main.c
+* Added #define srand_deterministic block in var.c
+* Added charclass.h from OpenBSD libc
+* Added LDFLAGS+=-static line in Makefile
+* Renamed README to README.pdksh
+* Added README.md (this file)
+
+License
+-------
+Most files are public domain (see LEGAL).
+A couple are BSD licensed (alloc.c and mknod.c).
diff --git a/README.pdksh b/README.pdksh
@@ -0,0 +1,197 @@
+$OpenBSD: README,v 1.10 2003/03/10 03:48:16 david Exp $
+
+Last updated Jul '99 for pdksh-5.2.14.
+ (check ftp://ftp.cs.mun.ca:/pub/pdksh/ or
+ http://www.cs.mun.ca/~michael/pdksh/ for new versions/patches)
+
+PD-ksh is a mostly complete AT&T ksh look-alike (see NOTES file for a list
+of things not supported). Work is mostly finished to make it fully
+compatible with both POSIX and AT&T ksh (when the two don't conflict).
+
+Since pdksh is free and compiles and runs on most common unix systems, it
+is very useful in creating a consistent user interface across multiple
+machines. For example, in the CS dept. of MUN, pdksh is installed on a
+variety of machines including Suns, HPs, DecStations, pcs running Linux,
+etc., and is the login shell of ~5200 users.
+
+PDksh is currently being maintained by Michael Rendell (michael@cs.mun.ca),
+who took over from Simon J. Gerraty (sjg@zen.void.oz.au) at the later's
+suggestion. A short list of things that have been added since the last
+public pdksh release (4.9) are auto-configuration, arrays, $(( .. )),
+[[ .. ]], variable attributes, co-processes, extended file globbing,
+many POSIXisms and many bug fixes. See the NEWS and ChangeLog files for
+other features added and bugs fixed.
+
+Note that pdksh is provided AS IS, with NO WARRANTY, either expressed or
+implied. Also note that although the bulk of the code in pdksh is in the
+public domain, some files are copyrighten (but freely distributable) and
+subject to certain conditions (eg, don't remove copyright, document any
+changes, etc.). See the LEGAL file for details.
+
+If you would like to be notified via email of new releases as they become
+available, send mail to pdksh-request@cs.mun.ca with subject
+"send release notifications" (or "don't send release notifications" to stop
+them).
+
+
+Files of interest:
+ NEWS short list of noticeable changes in various versions.
+ CONTRIBUTORS short history of pdksh, people who contributed, etc.
+ NOTES lists of known bugs in pdksh, at&t ksh, and posix.
+ PROJECTS list of things that need to be done in pdksh.
+ BUG-REPORTS list of recently reported bugs that have been fixed
+ and all reported bugs that haven't been fixed.
+ LEGAL A file detailing legal issues concerning pdksh.
+ etc/* system profile and kshrc files used by Simon J. Gerraty.
+ misc/README* readme files from previous versions.
+ misc/Changes* changelog files from previous versions.
+ os2/* files and info needed to compile ksh on os/2.
+ tests/* pdksh's regression testing system.
+
+
+Compiling/Installing:
+
+ The quick way:
+ ./configure
+ make
+ make check # optional
+ make install # will install /usr/local/bin/ksh
+ # and /usr/local/man/man1/ksh.1
+ [add path-to-installed-pdksh to /etc/shells]
+
+ The more detailed description:
+ * run "configure --help | your-favorite-pager" and look at the
+ --enable-* and --disable-* options (they are at the end).
+ Select any you options you wish to enable/disable
+ (most people can skip this step).
+ * run configure: this is a GNU autoconf configure script that will generate
+ a Makefile and a config.h. Some of the useful options to configure are:
+ --prefix=PATH indicates the directory tree under which the binary
+ and man page are installed (ie, PATH/bin/ksh and
+ PATH/man/man1/ksh.1).
+ The default prefix is /usr/local.
+ --exec-prefix=PATH overrides --prefix for machine dependent files
+ (ie, the ksh binary)
+ --program-prefix=pd install binary and man page as pdksh and pdksh.1
+ --verbose show what is being defined as script runs
+ Note that you don't have to build in the source directory. To build
+ in a separate directory, do something like:
+ $ mkdir objs
+ $ cd objs
+ $ ../configure --verbose
+ ....
+ $ make
+ See the file INSTALL for a more complete description of configure and its
+ generic options (ksh specific options are documented in the --help output)
+ * miscellaneous configuration notes:
+ * If your make doesn't understand VPATH, you must compile in
+ the source directory.
+ * On DecStations, MIPS and SONY machines with older C compilers that
+ can't handle "int * volatile x", you should use gcc or turn off
+ optimization. The problem is configure defines volatile to nothing
+ since the compiler can't handle it properly, but the compiler does
+ optimizations that the volatile is meant to prevent. So. Use gcc.
+ * On MIPS RISC/os 5.0 systems, sysv environment, <signal.h> is
+ messed up - it defines sigset_t, but not any of the rest of
+ the posix signals (the sigset_t typedef should be in the
+ ifdef KERNEL section) - also doesn't have waitpid() or wait3().
+ Things compile up ok in the svr4 environment, but it dumps core
+ in __start (perhaps our system doesn't have the full svr4
+ environ?). Try compiling in the bsd43 environ instead (still not
+ perfect - see BUG-REPORTS file), using gcc - cc has problems with
+ macro expansions in the argument of a macro (in this case, the ARGS
+ macro).
+ * On TitanOS (Stardent/Titan), use `CC="cc -43" configure ...'.
+ When configure finishes, edit config.h, undef HAVE_DIRENT_H and
+ define HAVE_SYS_DIR_H (the dirent.h header file is broken).
+ * On Linux (red hat distribution), check that /dev/tty has mode 0666
+ (not mode 0644). If it has the wrong permissions, ksh will print
+ warnings about not being able to do job control.
+ * on NeXT machines (3.2, probably other releases), the siglist.out file
+ won't be generated correctly if you try to use the system's compiler
+ (it has a broken cc -E and strange header files). There are two
+ ways to make it work:
+ 1) if you have gcc, use it (for everything). Alternatively,
+ force configure to use it for CPP, i.e., use
+ CPP="gcc -E" configure ...
+ 2) Force configure to use some extra CPPFLAGS, using
+ CPPFLAGS="XXX" configure ...
+ where XXX is obtained from running "cc -v YYY.c" on some
+ C file. Look at the options passed to cpp (there are lots
+ of them...) and replace the XXX above with them.
+ Make sure you do a "make distclean" (or "rm config.cache") if
+ you re-run configure with a difference CPP or CPPFLAGS.
+ Also note that if you are building multiple arch binaries, you
+ will have to specify both CC and CPP.
+ * run make: everything should compile and link without problems.
+ * run make check: this fires up a perl script that checks for some known
+ and some fixed bugs. The script prints pass/fail for tests it expected
+ to pass/fail, and PASS/FAIL for tests it expected to fail/pass. If you
+ don't have perl, or if your perl doesn't work (most common problem is
+ the .ph header files are missing or broken), you can run
+ ENV= path-to-pdksh-executable misc/Bugs path-to-pdksh-executable
+ instead.
+ * run make install: this installs ksh (in /usr/local/bin/ksh by default,
+ or where ever you told configure to put things).
+ * add path-to-installed-pdksh to /etc/shells if it's not already there.
+ This is only needed if you intend to use pdksh as a login shell (things
+ like ftp won't allow users to connect in if their shell isn't in this
+ file).
+
+The following is a list of machines that pdksh is reported to work on:
+ -/PC Linux 1.x,2.x
+ -/PC NetBSD 0.9a
+ -/PC BSDI 1.1
+ -/PC FreeBSD 2.x, 3.x
+ -/PC OpenBSD
+ -/PC Interactive/Sunsoft 3.0.1 and 4.1 (note that problems have been
+ reported with isc3.2 - see the BUG-REPORTS file)
+ -/PC OS/2
+ Commodore/Amiga NetBSD 1.0
+ Dec/alpha OSF/1 v2.x, v3.x
+ Dec/alpha NetBSD 1.1B
+ Dec/pmax Ultrix 4.2
+ Dec/vax Ultrix 2.2 (not tested recently :-))
+ Dec/vax 4.3BSD+NFS (MtXinu) (not tested recently :-))
+ HP/pa HP-UX 9.01
+ IBM/RS/6000 AIX 3.2.5
+ MIPS/m120 RISC/os 5.0 (bsd43 environ)
+ NeXT NeXTStep 3.2
+ SGI/IRIX 6.2
+ Sun/sun4 SunOS 4.1.3, 4.1.4
+ Sun/sun4 Solaris 2.x
+ Sun/sun386i SunOS 4.0.2
+ Sun/sun3 SunOS 4.0.3, 4.1.1_U1
+ Stardent/TitanOS 4.2
+
+
+Newer versions of pdksh may be available from
+ ftp://ftp.cs.mun.ca:/pub/pdksh/
+you may want to check for one if you run into any problems, as the problem may
+already be fixed (you can get new release notifications automatically - see
+above). The file pdksh-unstable-XXX.tar.gz has the very latest version which
+may not compile (it is generated automatically when changes are detected
+in the main source repository) - it is for those who want to follow
+changes as they are made.
+
+You can send bug reports, fixes, and enhancements to pdksh@cs.mun.ca (please
+don't assume I will see bug reports that are posted to some newsgroup or
+mailing list - I probably won't).
+If you are reporting a bug (with or without a fix), please include
+ * the version of pdksh you are using (see version.c, or, if you are
+ running pdksh, try echo $KSH_VERSION),
+ * the machine, operating system and compiler you are using,
+ * and a description of how to repeat the bug (a small shell
+ script that demonstrates the bug is best).
+as well as the following, if relevant (if you aren't sure, include them)
+ * what options you are using (both configure options and set -o options)
+ * the output of configure, with the verbose flag
+ (eg, make distclean; ./configure --verbose)
+ * the contents of config.log (this is created by the configure script)
+ * if you are using gcc (the GNU C compiler), which version it is.
+
+BTW, THE MOST FREQUENTLY REPORTED BUG IS
+ echo hi | read a; echo $a # Does not print hi
+I'm aware of this and there is no need to report it.
+
+Michael Rendell, michael@cs.mun.ca
diff --git a/alloc.c b/alloc.c
@@ -0,0 +1,127 @@
+/* $OpenBSD: alloc.c,v 1.8 2008/07/21 17:30:08 millert Exp $ */
+/*
+ * Copyright (c) 2002 Marc Espie.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT 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 OPENBSD
+ * PROJECT 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.
+ */
+
+/*
+ * area-based allocation built on malloc/free
+ */
+
+#include "sh.h"
+
+struct link {
+ struct link *prev;
+ struct link *next;
+};
+
+Area *
+ainit(Area *ap)
+{
+ ap->freelist = NULL;
+ return ap;
+}
+
+void
+afreeall(Area *ap)
+{
+ struct link *l, *l2;
+
+ for (l = ap->freelist; l != NULL; l = l2) {
+ l2 = l->next;
+ free(l);
+ }
+ ap->freelist = NULL;
+}
+
+#define L2P(l) ( (void *)(((char *)(l)) + sizeof(struct link)) )
+#define P2L(p) ( (struct link *)(((char *)(p)) - sizeof(struct link)) )
+
+void *
+alloc(size_t size, Area *ap)
+{
+ struct link *l;
+
+ l = malloc(sizeof(struct link) + size);
+ if (l == NULL)
+ internal_errorf(1, "unable to allocate memory");
+ l->next = ap->freelist;
+ l->prev = NULL;
+ if (ap->freelist)
+ ap->freelist->prev = l;
+ ap->freelist = l;
+
+ return L2P(l);
+}
+
+void *
+aresize(void *ptr, size_t size, Area *ap)
+{
+ struct link *l, *l2, *lprev, *lnext;
+
+ if (ptr == NULL)
+ return alloc(size, ap);
+
+ l = P2L(ptr);
+ lprev = l->prev;
+ lnext = l->next;
+
+ l2 = realloc(l, sizeof(struct link) + size);
+ if (l2 == NULL)
+ internal_errorf(1, "unable to allocate memory");
+ if (lprev)
+ lprev->next = l2;
+ else
+ ap->freelist = l2;
+ if (lnext)
+ lnext->prev = l2;
+
+ return L2P(l2);
+}
+
+void
+afree(void *ptr, Area *ap)
+{
+ struct link *l, *l2;
+
+ if (!ptr)
+ return;
+
+ l = P2L(ptr);
+
+ for (l2 = ap->freelist; l2 != NULL; l2 = l2->next) {
+ if (l == l2)
+ break;
+ }
+ if (l2 == NULL)
+ internal_errorf(1, "afree: %p not present in area %p", ptr, ap);
+
+ if (l->prev)
+ l->prev->next = l->next;
+ else
+ ap->freelist = l->next;
+ if (l->next)
+ l->next->prev = l->prev;
+
+ free(l);
+}
diff --git a/c_ksh.c b/c_ksh.c
@@ -0,0 +1,1412 @@
+/* $OpenBSD: c_ksh.c,v 1.34 2013/12/17 16:37:05 deraadt Exp $ */
+
+/*
+ * built-in Korn commands: c_*
+ */
+
+#include "sh.h"
+#include <sys/stat.h>
+#include <ctype.h>
+
+
+int
+c_cd(char **wp)
+{
+ int optc;
+ int physical = Flag(FPHYSICAL);
+ int cdnode; /* was a node from cdpath added in? */
+ int printpath = 0; /* print where we cd'd? */
+ int rval;
+ struct tbl *pwd_s, *oldpwd_s;
+ XString xs;
+ char *xp;
+ char *dir, *try, *pwd;
+ int phys_path;
+ char *cdpath;
+ char *fdir = NULL;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1)
+ switch (optc) {
+ case 'L':
+ physical = 0;
+ break;
+ case 'P':
+ physical = 1;
+ break;
+ case '?':
+ return 1;
+ }
+ wp += builtin_opt.optind;
+
+ if (Flag(FRESTRICTED)) {
+ bi_errorf("restricted shell - can't cd");
+ return 1;
+ }
+
+ pwd_s = global("PWD");
+ oldpwd_s = global("OLDPWD");
+
+ if (!wp[0]) {
+ /* No arguments - go home */
+ if ((dir = str_val(global("HOME"))) == null) {
+ bi_errorf("no home directory (HOME not set)");
+ return 1;
+ }
+ } else if (!wp[1]) {
+ /* One argument: - or dir */
+ dir = wp[0];
+ if (strcmp(dir, "-") == 0) {
+ dir = str_val(oldpwd_s);
+ if (dir == null) {
+ bi_errorf("no OLDPWD");
+ return 1;
+ }
+ printpath++;
+ }
+ } else if (!wp[2]) {
+ /* Two arguments - substitute arg1 in PWD for arg2 */
+ int ilen, olen, nlen, elen;
+ char *cp;
+
+ if (!current_wd[0]) {
+ bi_errorf("don't know current directory");
+ return 1;
+ }
+ /* substitute arg1 for arg2 in current path.
+ * if the first substitution fails because the cd fails
+ * we could try to find another substitution. For now
+ * we don't
+ */
+ if ((cp = strstr(current_wd, wp[0])) == (char *) 0) {
+ bi_errorf("bad substitution");
+ return 1;
+ }
+ ilen = cp - current_wd;
+ olen = strlen(wp[0]);
+ nlen = strlen(wp[1]);
+ elen = strlen(current_wd + ilen + olen) + 1;
+ fdir = dir = alloc(ilen + nlen + elen, ATEMP);
+ memcpy(dir, current_wd, ilen);
+ memcpy(dir + ilen, wp[1], nlen);
+ memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen);
+ printpath++;
+ } else {
+ bi_errorf("too many arguments");
+ return 1;
+ }
+
+ Xinit(xs, xp, PATH, ATEMP);
+ /* xp will have a bogus value after make_path() - set it to 0
+ * so that if it's used, it will cause a dump
+ */
+ xp = (char *) 0;
+
+ cdpath = str_val(global("CDPATH"));
+ do {
+ cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path);
+ if (physical)
+ rval = chdir(try = Xstring(xs, xp) + phys_path);
+ else {
+ simplify_path(Xstring(xs, xp));
+ rval = chdir(try = Xstring(xs, xp));
+ }
+ } while (rval < 0 && cdpath != (char *) 0);
+
+ if (rval < 0) {
+ if (cdnode)
+ bi_errorf("%s: bad directory", dir);
+ else
+ bi_errorf("%s - %s", try, strerror(errno));
+ if (fdir)
+ afree(fdir, ATEMP);
+ return 1;
+ }
+
+ /* Clear out tracked aliases with relative paths */
+ flushcom(0);
+
+ /* Set OLDPWD (note: unsetting OLDPWD does not disable this
+ * setting in at&t ksh)
+ */
+ if (current_wd[0])
+ /* Ignore failure (happens if readonly or integer) */
+ setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR);
+
+ if (Xstring(xs, xp)[0] != '/') {
+ pwd = (char *) 0;
+ } else
+ if (!physical || !(pwd = get_phys_path(Xstring(xs, xp))))
+ pwd = Xstring(xs, xp);
+
+ /* Set PWD */
+ if (pwd) {
+ char *ptmp = pwd;
+ set_current_wd(ptmp);
+ /* Ignore failure (happens if readonly or integer) */
+ setstr(pwd_s, ptmp, KSH_RETURN_ERROR);
+ } else {
+ set_current_wd(null);
+ pwd = Xstring(xs, xp);
+ /* XXX unset $PWD? */
+ }
+ if (printpath || cdnode)
+ shprintf("%s\n", pwd);
+
+ if (fdir)
+ afree(fdir, ATEMP);
+
+ return 0;
+}
+
+int
+c_pwd(char **wp)
+{
+ int optc;
+ int physical = Flag(FPHYSICAL);
+ char *p, *freep = NULL;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1)
+ switch (optc) {
+ case 'L':
+ physical = 0;
+ break;
+ case 'P':
+ physical = 1;
+ break;
+ case '?':
+ return 1;
+ }
+ wp += builtin_opt.optind;
+
+ if (wp[0]) {
+ bi_errorf("too many arguments");
+ return 1;
+ }
+ p = current_wd[0] ? (physical ? get_phys_path(current_wd) : current_wd) :
+ (char *) 0;
+ if (p && access(p, R_OK) < 0)
+ p = (char *) 0;
+ if (!p) {
+ freep = p = ksh_get_wd((char *) 0, 0);
+ if (!p) {
+ bi_errorf("can't get current directory - %s",
+ strerror(errno));
+ return 1;
+ }
+ }
+ shprintf("%s\n", p);
+ if (freep)
+ afree(freep, ATEMP);
+ return 0;
+}
+
+int
+c_print(char **wp)
+{
+#define PO_NL BIT(0) /* print newline */
+#define PO_EXPAND BIT(1) /* expand backslash sequences */
+#define PO_PMINUSMINUS BIT(2) /* print a -- argument */
+#define PO_HIST BIT(3) /* print to history instead of stdout */
+#define PO_COPROC BIT(4) /* printing to coprocess: block SIGPIPE */
+ int fd = 1;
+ int flags = PO_EXPAND|PO_NL;
+ char *s;
+ const char *emsg;
+ XString xs;
+ char *xp;
+
+ if (wp[0][0] == 'e') { /* echo command */
+ int nflags = flags;
+
+ /* A compromise between sysV and BSD echo commands:
+ * escape sequences are enabled by default, and
+ * -n, -e and -E are recognized if they appear
+ * in arguments with no illegal options (ie, echo -nq
+ * will print -nq).
+ * Different from sysV echo since options are recognized,
+ * different from BSD echo since escape sequences are enabled
+ * by default.
+ */
+ wp += 1;
+ if (Flag(FPOSIX)) {
+ if (*wp && strcmp(*wp, "-n") == 0) {
+ flags &= ~PO_NL;
+ wp++;
+ }
+ } else {
+ while ((s = *wp) && *s == '-' && s[1]) {
+ while (*++s)
+ if (*s == 'n')
+ nflags &= ~PO_NL;
+ else if (*s == 'e')
+ nflags |= PO_EXPAND;
+ else if (*s == 'E')
+ nflags &= ~PO_EXPAND;
+ else
+ /* bad option: don't use
+ * nflags, print argument
+ */
+ break;
+ if (*s)
+ break;
+ wp++;
+ flags = nflags;
+ }
+ }
+ } else {
+ int optc;
+ const char *options = "Rnprsu,";
+ while ((optc = ksh_getopt(wp, &builtin_opt, options)) != -1)
+ switch (optc) {
+ case 'R': /* fake BSD echo command */
+ flags |= PO_PMINUSMINUS;
+ flags &= ~PO_EXPAND;
+ options = "ne";
+ break;
+ case 'e':
+ flags |= PO_EXPAND;
+ break;
+ case 'n':
+ flags &= ~PO_NL;
+ break;
+ case 'p':
+ if ((fd = coproc_getfd(W_OK, &emsg)) < 0) {
+ bi_errorf("-p: %s", emsg);
+ return 1;
+ }
+ break;
+ case 'r':
+ flags &= ~PO_EXPAND;
+ break;
+ case 's':
+ flags |= PO_HIST;
+ break;
+ case 'u':
+ if (!*(s = builtin_opt.optarg))
+ fd = 0;
+ else if ((fd = check_fd(s, W_OK, &emsg)) < 0) {
+ bi_errorf("-u: %s: %s", s, emsg);
+ return 1;
+ }
+ break;
+ case '?':
+ return 1;
+ }
+ if (!(builtin_opt.info & GI_MINUSMINUS)) {
+ /* treat a lone - like -- */
+ if (wp[builtin_opt.optind] &&
+ strcmp(wp[builtin_opt.optind], "-") == 0)
+ builtin_opt.optind++;
+ } else if (flags & PO_PMINUSMINUS)
+ builtin_opt.optind--;
+ wp += builtin_opt.optind;
+ }
+
+ Xinit(xs, xp, 128, ATEMP);
+
+ while (*wp != NULL) {
+ int c;
+ s = *wp;
+ while ((c = *s++) != '\0') {
+ Xcheck(xs, xp);
+ if ((flags & PO_EXPAND) && c == '\\') {
+ int i;
+
+ switch ((c = *s++)) {
+ /* Oddly enough, \007 seems more portable than
+ * \a (due to HP-UX cc, Ultrix cc, old pcc's,
+ * etc.).
+ */
+ case 'a': c = '\007'; break;
+ case 'b': c = '\b'; break;
+ case 'c': flags &= ~PO_NL;
+ continue; /* AT&T brain damage */
+ case 'f': c = '\f'; break;
+ case 'n': c = '\n'; break;
+ case 'r': c = '\r'; break;
+ case 't': c = '\t'; break;
+ case 'v': c = 0x0B; break;
+ case '0':
+ /* Look for an octal number: can have
+ * three digits (not counting the
+ * leading 0). Truly burnt.
+ */
+ c = 0;
+ for (i = 0; i < 3; i++) {
+ if (*s >= '0' && *s <= '7')
+ c = c*8 + *s++ - '0';
+ else
+ break;
+ }
+ break;
+ case '\0': s--; c = '\\'; break;
+ case '\\': break;
+ default:
+ Xput(xs, xp, '\\');
+ }
+ }
+ Xput(xs, xp, c);
+ }
+ if (*++wp != NULL)
+ Xput(xs, xp, ' ');
+ }
+ if (flags & PO_NL)
+ Xput(xs, xp, '\n');
+
+ if (flags & PO_HIST) {
+ Xput(xs, xp, '\0');
+ source->line++;
+ histsave(source->line, Xstring(xs, xp), 1);
+ Xfree(xs, xp);
+ } else {
+ int n, len = Xlength(xs, xp);
+ int opipe = 0;
+
+ /* Ensure we aren't killed by a SIGPIPE while writing to
+ * a coprocess. at&t ksh doesn't seem to do this (seems
+ * to just check that the co-process is alive, which is
+ * not enough).
+ */
+ if (coproc.write >= 0 && coproc.write == fd) {
+ flags |= PO_COPROC;
+ opipe = block_pipe();
+ }
+ for (s = Xstring(xs, xp); len > 0; ) {
+ n = write(fd, s, len);
+ if (n < 0) {
+ if (flags & PO_COPROC)
+ restore_pipe(opipe);
+ if (errno == EINTR) {
+ /* allow user to ^C out */
+ intrcheck();
+ if (flags & PO_COPROC)
+ opipe = block_pipe();
+ continue;
+ }
+ /* This doesn't really make sense - could
+ * break scripts (print -p generates
+ * error message).
+ *if (errno == EPIPE)
+ * coproc_write_close(fd);
+ */
+ return 1;
+ }
+ s += n;
+ len -= n;
+ }
+ if (flags & PO_COPROC)
+ restore_pipe(opipe);
+ }
+
+ return 0;
+}
+
+int
+c_whence(char **wp)
+{
+ struct tbl *tp;
+ char *id;
+ int pflag = 0, vflag = 0, Vflag = 0;
+ int ret = 0;
+ int optc;
+ int iam_whence = wp[0][0] == 'w';
+ int fcflags;
+ const char *options = iam_whence ? "pv" : "pvV";
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, options)) != -1)
+ switch (optc) {
+ case 'p':
+ pflag = 1;
+ break;
+ case 'v':
+ vflag = 1;
+ break;
+ case 'V':
+ Vflag = 1;
+ break;
+ case '?':
+ return 1;
+ }
+ wp += builtin_opt.optind;
+
+
+ fcflags = FC_BI | FC_PATH | FC_FUNC;
+ if (!iam_whence) {
+ /* Note that -p on its own is deal with in comexec() */
+ if (pflag)
+ fcflags |= FC_DEFPATH;
+ /* Convert command options to whence options - note that
+ * command -pV uses a different path search than whence -v
+ * or whence -pv. This should be considered a feature.
+ */
+ vflag = Vflag;
+ }
+ if (pflag)
+ fcflags &= ~(FC_BI | FC_FUNC);
+
+ while ((vflag || ret == 0) && (id = *wp++) != NULL) {
+ tp = NULL;
+ if ((iam_whence || vflag) && !pflag)
+ tp = ktsearch(&keywords, id, hash(id));
+ if (!tp && !pflag) {
+ tp = ktsearch(&aliases, id, hash(id));
+ if (tp && !(tp->flag & ISSET))
+ tp = NULL;
+ }
+ if (!tp)
+ tp = findcom(id, fcflags);
+ if (vflag || (tp->type != CALIAS && tp->type != CEXEC &&
+ tp->type != CTALIAS))
+ shprintf("%s", id);
+ switch (tp->type) {
+ case CKEYWD:
+ if (vflag)
+ shprintf(" is a reserved word");
+ break;
+ case CALIAS:
+ if (vflag)
+ shprintf(" is an %salias for ",
+ (tp->flag & EXPORT) ? "exported " :
+ null);
+ if (!iam_whence && !vflag)
+ shprintf("alias %s=", id);
+ print_value_quoted(tp->val.s);
+ break;
+ case CFUNC:
+ if (vflag) {
+ shprintf(" is a");
+ if (tp->flag & EXPORT)
+ shprintf("n exported");
+ if (tp->flag & TRACE)
+ shprintf(" traced");
+ if (!(tp->flag & ISSET)) {
+ shprintf(" undefined");
+ if (tp->u.fpath)
+ shprintf(" (autoload from %s)",
+ tp->u.fpath);
+ }
+ shprintf(" function");
+ }
+ break;
+ case CSHELL:
+ if (vflag)
+ shprintf(" is a%s shell builtin",
+ (tp->flag & SPEC_BI) ? " special" : null);
+ break;
+ case CTALIAS:
+ case CEXEC:
+ if (tp->flag & ISSET) {
+ if (vflag) {
+ shprintf(" is ");
+ if (tp->type == CTALIAS)
+ shprintf("a tracked %salias for ",
+ (tp->flag & EXPORT) ?
+ "exported " : null);
+ }
+ shprintf("%s", tp->val.s);
+ } else {
+ if (vflag)
+ shprintf(" not found");
+ ret = 1;
+ }
+ break;
+ default:
+ shprintf("%s is *GOK*", id);
+ break;
+ }
+ if (vflag || !ret)
+ shprintf(newline);
+ }
+ return ret;
+}
+
+/* Deal with command -vV - command -p dealt with in comexec() */
+int
+c_command(char **wp)
+{
+ /* Let c_whence do the work. Note that c_command() must be
+ * a distinct function from c_whence() (tested in comexec()).
+ */
+ return c_whence(wp);
+}
+
+/* typeset, export, and readonly */
+int
+c_typeset(char **wp)
+{
+ struct block *l = e->loc;
+ struct tbl *vp, **p;
+ Tflag fset = 0, fclr = 0;
+ int thing = 0, func = 0, local = 0;
+ const char *options = "L#R#UZ#fi#lprtux"; /* see comment below */
+ char *fieldstr, *basestr;
+ int field, base;
+ int optc;
+ Tflag flag;
+ int pflag = 0;
+
+ switch (**wp) {
+ case 'e': /* export */
+ fset |= EXPORT;
+ options = "p";
+ break;
+ case 'r': /* readonly */
+ fset |= RDONLY;
+ options = "p";
+ break;
+ case 's': /* set */
+ /* called with 'typeset -' */
+ break;
+ case 't': /* typeset */
+ local = 1;
+ break;
+ }
+
+ fieldstr = basestr = (char *) 0;
+ builtin_opt.flags |= GF_PLUSOPT;
+ /* at&t ksh seems to have 0-9 as options, which are multiplied
+ * to get a number that is used with -L, -R, -Z or -i (eg, -1R2
+ * sets right justify in a field of 12). This allows options
+ * to be grouped in an order (eg, -Lu12), but disallows -i8 -L3 and
+ * does not allow the number to be specified as a separate argument
+ * Here, the number must follow the RLZi option, but is optional
+ * (see the # kludge in ksh_getopt()).
+ */
+ while ((optc = ksh_getopt(wp, &builtin_opt, options)) != -1) {
+ flag = 0;
+ switch (optc) {
+ case 'L':
+ flag = LJUST;
+ fieldstr = builtin_opt.optarg;
+ break;
+ case 'R':
+ flag = RJUST;
+ fieldstr = builtin_opt.optarg;
+ break;
+ case 'U':
+ /* at&t ksh uses u, but this conflicts with
+ * upper/lower case. If this option is changed,
+ * need to change the -U below as well
+ */
+ flag = INT_U;
+ break;
+ case 'Z':
+ flag = ZEROFIL;
+ fieldstr = builtin_opt.optarg;
+ break;
+ case 'f':
+ func = 1;
+ break;
+ case 'i':
+ flag = INTEGER;
+ basestr = builtin_opt.optarg;
+ break;
+ case 'l':
+ flag = LCASEV;
+ break;
+ case 'p':
+ /* posix export/readonly -p flag.
+ * typeset -p is the same as typeset (in pdksh);
+ * here for compatibility with ksh93.
+ */
+ pflag = 1;
+ break;
+ case 'r':
+ flag = RDONLY;
+ break;
+ case 't':
+ flag = TRACE;
+ break;
+ case 'u':
+ flag = UCASEV_AL; /* upper case / autoload */
+ break;
+ case 'x':
+ flag = EXPORT;
+ break;
+ case '?':
+ return 1;
+ }
+ if (builtin_opt.info & GI_PLUS) {
+ fclr |= flag;
+ fset &= ~flag;
+ thing = '+';
+ } else {
+ fset |= flag;
+ fclr &= ~flag;
+ thing = '-';
+ }
+ }
+
+ field = 0;
+ if (fieldstr && !bi_getn(fieldstr, &field))
+ return 1;
+ base = 0;
+ if (basestr && !bi_getn(basestr, &base))
+ return 1;
+
+ if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind] &&
+ (wp[builtin_opt.optind][0] == '-' ||
+ wp[builtin_opt.optind][0] == '+') &&
+ wp[builtin_opt.optind][1] == '\0') {
+ thing = wp[builtin_opt.optind][0];
+ builtin_opt.optind++;
+ }
+
+ if (func && ((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT))) {
+ bi_errorf("only -t, -u and -x options may be used with -f");
+ return 1;
+ }
+ if (wp[builtin_opt.optind]) {
+ /* Take care of exclusions.
+ * At this point, flags in fset are cleared in fclr and vise
+ * versa. This property should be preserved.
+ */
+ if (fset & LCASEV) /* LCASEV has priority over UCASEV_AL */
+ fset &= ~UCASEV_AL;
+ if (fset & LJUST) /* LJUST has priority over RJUST */
+ fset &= ~RJUST;
+ if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { /* -Z implies -ZR */
+ fset |= RJUST;
+ fclr &= ~RJUST;
+ }
+ /* Setting these attributes clears the others, unless they
+ * are also set in this command
+ */
+ if (fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV |
+ INTEGER | INT_U | INT_L))
+ fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL |
+ LCASEV | INTEGER | INT_U | INT_L);
+ }
+
+ /* set variables and attributes */
+ if (wp[builtin_opt.optind]) {
+ int i;
+ int rval = 0;
+ struct tbl *f;
+
+ if (local && !func)
+ fset |= LOCAL;
+ for (i = builtin_opt.optind; wp[i]; i++) {
+ if (func) {
+ f = findfunc(wp[i], hash(wp[i]),
+ (fset&UCASEV_AL) ? true : false);
+ if (!f) {
+ /* at&t ksh does ++rval: bogus */
+ rval = 1;
+ continue;
+ }
+ if (fset | fclr) {
+ f->flag |= fset;
+ f->flag &= ~fclr;
+ } else
+ fptreef(shl_stdout, 0,
+ f->flag & FKSH ?
+ "function %s %T\n" :
+ "%s() %T\n", wp[i], f->val.t);
+ } else if (!typeset(wp[i], fset, fclr, field, base)) {
+ bi_errorf("%s: not identifier", wp[i]);
+ return 1;
+ }
+ }
+ return rval;
+ }
+
+ /* list variables and attributes */
+ flag = fset | fclr; /* no difference at this point.. */
+ if (func) {
+ for (l = e->loc; l; l = l->next) {
+ for (p = ktsort(&l->funs); (vp = *p++); ) {
+ if (flag && (vp->flag & flag) == 0)
+ continue;
+ if (thing == '-')
+ fptreef(shl_stdout, 0, vp->flag & FKSH ?
+ "function %s %T\n" : "%s() %T\n",
+ vp->name, vp->val.t);
+ else
+ shprintf("%s\n", vp->name);
+ }
+ }
+ } else {
+ for (l = e->loc; l; l = l->next) {
+ for (p = ktsort(&l->vars); (vp = *p++); ) {
+ struct tbl *tvp;
+ int any_set = 0;
+ /*
+ * See if the parameter is set (for arrays, if any
+ * element is set).
+ */
+ for (tvp = vp; tvp; tvp = tvp->u.array)
+ if (tvp->flag & ISSET) {
+ any_set = 1;
+ break;
+ }
+
+ /*
+ * Check attributes - note that all array elements
+ * have (should have?) the same attributes, so checking
+ * the first is sufficient.
+ *
+ * Report an unset param only if the user has
+ * explicitly given it some attribute (like export);
+ * otherwise, after "echo $FOO", we would report FOO...
+ */
+ if (!any_set && !(vp->flag & USERATTRIB))
+ continue;
+ if (flag && (vp->flag & flag) == 0)
+ continue;
+ for (; vp; vp = vp->u.array) {
+ /* Ignore array elements that aren't
+ * set unless there are no set elements,
+ * in which case the first is reported on */
+ if ((vp->flag&ARRAY) && any_set &&
+ !(vp->flag & ISSET))
+ continue;
+ /* no arguments */
+ if (thing == 0 && flag == 0) {
+ /* at&t ksh prints things
+ * like export, integer,
+ * leftadj, zerofill, etc.,
+ * but POSIX says must
+ * be suitable for re-entry...
+ */
+ shprintf("typeset ");
+ if ((vp->flag&INTEGER))
+ shprintf("-i ");
+ if ((vp->flag&EXPORT))
+ shprintf("-x ");
+ if ((vp->flag&RDONLY))
+ shprintf("-r ");
+ if ((vp->flag&TRACE))
+ shprintf("-t ");
+ if ((vp->flag&LJUST))
+ shprintf("-L%d ", vp->u2.field);
+ if ((vp->flag&RJUST))
+ shprintf("-R%d ", vp->u2.field);
+ if ((vp->flag&ZEROFIL))
+ shprintf("-Z ");
+ if ((vp->flag&LCASEV))
+ shprintf("-l ");
+ if ((vp->flag&UCASEV_AL))
+ shprintf("-u ");
+ if ((vp->flag&INT_U))
+ shprintf("-U ");
+ shprintf("%s\n", vp->name);
+ if (vp->flag&ARRAY)
+ break;
+ } else {
+ if (pflag)
+ shprintf("%s ",
+ (flag & EXPORT) ?
+ "export" : "readonly");
+ if ((vp->flag&ARRAY) && any_set)
+ shprintf("%s[%d]",
+ vp->name, vp->index);
+ else
+ shprintf("%s", vp->name);
+ if (thing == '-' && (vp->flag&ISSET)) {
+ char *s = str_val(vp);
+
+ shprintf("=");
+ /* at&t ksh can't have
+ * justified integers.. */
+ if ((vp->flag &
+ (INTEGER|LJUST|RJUST)) ==
+ INTEGER)
+ shprintf("%s", s);
+ else
+ print_value_quoted(s);
+ }
+ shprintf(newline);
+ }
+ /* Only report first `element' of an array with
+ * no set elements.
+ */
+ if (!any_set)
+ break;
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+int
+c_alias(char **wp)
+{
+ struct table *t = &aliases;
+ int rv = 0, rflag = 0, tflag, Uflag = 0, pflag = 0;
+ int prefix = 0;
+ Tflag xflag = 0;
+ int optc;
+
+ builtin_opt.flags |= GF_PLUSOPT;
+ while ((optc = ksh_getopt(wp, &builtin_opt, "dprtUx")) != -1) {
+ prefix = builtin_opt.info & GI_PLUS ? '+' : '-';
+ switch (optc) {
+ case 'd':
+ t = &homedirs;
+ break;
+ case 'p':
+ pflag = 1;
+ break;
+ case 'r':
+ rflag = 1;
+ break;
+ case 't':
+ t = &taliases;
+ break;
+ case 'U':
+ /*
+ * kludge for tracked alias initialization
+ * (don't do a path search, just make an entry)
+ */
+ Uflag = 1;
+ break;
+ case 'x':
+ xflag = EXPORT;
+ break;
+ case '?':
+ return 1;
+ }
+ }
+ wp += builtin_opt.optind;
+
+ if (!(builtin_opt.info & GI_MINUSMINUS) && *wp &&
+ (wp[0][0] == '-' || wp[0][0] == '+') && wp[0][1] == '\0') {
+ prefix = wp[0][0];
+ wp++;
+ }
+
+ tflag = t == &taliases;
+
+ /* "hash -r" means reset all the tracked aliases.. */
+ if (rflag) {
+ static const char *const args[] = {
+ "unalias", "-ta", (const char *) 0
+ };
+
+ if (!tflag || *wp) {
+ shprintf("alias: -r flag can only be used with -t"
+ " and without arguments\n");
+ return 1;
+ }
+ ksh_getopt_reset(&builtin_opt, GF_ERROR);
+ return c_unalias((char **) args);
+ }
+
+ if (*wp == NULL) {
+ struct tbl *ap, **p;
+
+ for (p = ktsort(t); (ap = *p++) != NULL; )
+ if ((ap->flag & (ISSET|xflag)) == (ISSET|xflag)) {
+ if (pflag)
+ shf_puts("alias ", shl_stdout);
+ shf_puts(ap->name, shl_stdout);
+ if (prefix != '+') {
+ shf_putc('=', shl_stdout);
+ print_value_quoted(ap->val.s);
+ }
+ shprintf(newline);
+ }
+ }
+
+ for (; *wp != NULL; wp++) {
+ char *alias = *wp;
+ char *val = strchr(alias, '=');
+ char *newval;
+ struct tbl *ap;
+ int h;
+
+ if (val)
+ alias = str_nsave(alias, val++ - alias, ATEMP);
+ h = hash(alias);
+ if (val == NULL && !tflag && !xflag) {
+ ap = ktsearch(t, alias, h);
+ if (ap != NULL && (ap->flag&ISSET)) {
+ if (pflag)
+ shf_puts("alias ", shl_stdout);
+ shf_puts(ap->name, shl_stdout);
+ if (prefix != '+') {
+ shf_putc('=', shl_stdout);
+ print_value_quoted(ap->val.s);
+ }
+ shprintf(newline);
+ } else {
+ shprintf("%s alias not found\n", alias);
+ rv = 1;
+ }
+ continue;
+ }
+ ap = ktenter(t, alias, h);
+ ap->type = tflag ? CTALIAS : CALIAS;
+ /* Are we setting the value or just some flags? */
+ if ((val && !tflag) || (!val && tflag && !Uflag)) {
+ if (ap->flag&ALLOC) {
+ ap->flag &= ~(ALLOC|ISSET);
+ afree((void*)ap->val.s, APERM);
+ }
+ /* ignore values for -t (at&t ksh does this) */
+ newval = tflag ? search(alias, path, X_OK, (int *) 0) :
+ val;
+ if (newval) {
+ ap->val.s = str_save(newval, APERM);
+ ap->flag |= ALLOC|ISSET;
+ } else
+ ap->flag &= ~ISSET;
+ }
+ ap->flag |= DEFINED;
+ if (prefix == '+')
+ ap->flag &= ~xflag;
+ else
+ ap->flag |= xflag;
+ if (val)
+ afree(alias, ATEMP);
+ }
+
+ return rv;
+}
+
+int
+c_unalias(char **wp)
+{
+ struct table *t = &aliases;
+ struct tbl *ap;
+ int rv = 0, all = 0;
+ int optc;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "adt")) != -1)
+ switch (optc) {
+ case 'a':
+ all = 1;
+ break;
+ case 'd':
+ t = &homedirs;
+ break;
+ case 't':
+ t = &taliases;
+ break;
+ case '?':
+ return 1;
+ }
+ wp += builtin_opt.optind;
+
+ for (; *wp != NULL; wp++) {
+ ap = ktsearch(t, *wp, hash(*wp));
+ if (ap == NULL) {
+ rv = 1; /* POSIX */
+ continue;
+ }
+ if (ap->flag&ALLOC) {
+ ap->flag &= ~(ALLOC|ISSET);
+ afree((void*)ap->val.s, APERM);
+ }
+ ap->flag &= ~(DEFINED|ISSET|EXPORT);
+ }
+
+ if (all) {
+ struct tstate ts;
+
+ for (ktwalk(&ts, t); (ap = ktnext(&ts)); ) {
+ if (ap->flag&ALLOC) {
+ ap->flag &= ~(ALLOC|ISSET);
+ afree((void*)ap->val.s, APERM);
+ }
+ ap->flag &= ~(DEFINED|ISSET|EXPORT);
+ }
+ }
+
+ return rv;
+}
+
+int
+c_let(char **wp)
+{
+ int rv = 1;
+ long val;
+
+ if (wp[1] == (char *) 0) /* at&t ksh does this */
+ bi_errorf("no arguments");
+ else
+ for (wp++; *wp; wp++)
+ if (!evaluate(*wp, &val, KSH_RETURN_ERROR, true)) {
+ rv = 2; /* distinguish error from zero result */
+ break;
+ } else
+ rv = val == 0;
+ return rv;
+}
+
+int
+c_jobs(char **wp)
+{
+ int optc;
+ int flag = 0;
+ int nflag = 0;
+ int rv = 0;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "lpnz")) != -1)
+ switch (optc) {
+ case 'l':
+ flag = 1;
+ break;
+ case 'p':
+ flag = 2;
+ break;
+ case 'n':
+ nflag = 1;
+ break;
+ case 'z': /* debugging: print zombies */
+ nflag = -1;
+ break;
+ case '?':
+ return 1;
+ }
+ wp += builtin_opt.optind;
+ if (!*wp) {
+ if (j_jobs((char *) 0, flag, nflag))
+ rv = 1;
+ } else {
+ for (; *wp; wp++)
+ if (j_jobs(*wp, flag, nflag))
+ rv = 1;
+ }
+ return rv;
+}
+
+#ifdef JOBS
+int
+c_fgbg(char **wp)
+{
+ int bg = strcmp(*wp, "bg") == 0;
+ int rv = 0;
+
+ if (!Flag(FMONITOR)) {
+ bi_errorf("job control not enabled");
+ return 1;
+ }
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return 1;
+ wp += builtin_opt.optind;
+ if (*wp)
+ for (; *wp; wp++)
+ rv = j_resume(*wp, bg);
+ else
+ rv = j_resume("%%", bg);
+ /* POSIX says fg shall return 0 (unless an error occurs).
+ * at&t ksh returns the exit value of the job...
+ */
+ return (bg || Flag(FPOSIX)) ? 0 : rv;
+}
+#endif
+
+struct kill_info {
+ int num_width;
+ int name_width;
+};
+static char *kill_fmt_entry(void *arg, int i, char *buf, int buflen);
+
+/* format a single kill item */
+static char *
+kill_fmt_entry(void *arg, int i, char *buf, int buflen)
+{
+ struct kill_info *ki = (struct kill_info *) arg;
+
+ i++;
+ if (sigtraps[i].name)
+ shf_snprintf(buf, buflen, "%*d %*s %s",
+ ki->num_width, i,
+ ki->name_width, sigtraps[i].name,
+ sigtraps[i].mess);
+ else
+ shf_snprintf(buf, buflen, "%*d %*d %s",
+ ki->num_width, i,
+ ki->name_width, sigtraps[i].signal,
+ sigtraps[i].mess);
+ return buf;
+}
+
+
+int
+c_kill(char **wp)
+{
+ Trap *t = (Trap *) 0;
+ char *p;
+ int lflag = 0;
+ int i, n, rv, sig;
+
+ /* assume old style options if -digits or -UPPERCASE */
+ if ((p = wp[1]) && *p == '-' &&
+ (digit(p[1]) || isupper((unsigned char)p[1]))) {
+ if (!(t = gettrap(p + 1, true))) {
+ bi_errorf("bad signal `%s'", p + 1);
+ return 1;
+ }
+ i = (wp[2] && strcmp(wp[2], "--") == 0) ? 3 : 2;
+ } else {
+ int optc;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "ls:")) != -1)
+ switch (optc) {
+ case 'l':
+ lflag = 1;
+ break;
+ case 's':
+ if (!(t = gettrap(builtin_opt.optarg, true))) {
+ bi_errorf("bad signal `%s'",
+ builtin_opt.optarg);
+ return 1;
+ }
+ break;
+ case '?':
+ return 1;
+ }
+ i = builtin_opt.optind;
+ }
+ if ((lflag && t) || (!wp[i] && !lflag)) {
+ shf_fprintf(shl_out,
+ "usage: kill [-s signame | -signum | -signame] { job | pid | pgrp } ...\n"
+ " kill -l [exit_status ...]\n");
+ bi_errorf(null);
+ return 1;
+ }
+
+ if (lflag) {
+ if (wp[i]) {
+ for (; wp[i]; i++) {
+ if (!bi_getn(wp[i], &n))
+ return 1;
+ if (n > 128 && n < 128 + NSIG)
+ n -= 128;
+ if (n > 0 && n < NSIG && sigtraps[n].name)
+ shprintf("%s\n", sigtraps[n].name);
+ else
+ shprintf("%d\n", n);
+ }
+ } else if (Flag(FPOSIX)) {
+ p = null;
+ for (i = 1; i < NSIG; i++, p = space)
+ if (sigtraps[i].name)
+ shprintf("%s%s", p, sigtraps[i].name);
+ shprintf(newline);
+ } else {
+ int w, i;
+ int mess_width;
+ struct kill_info ki;
+
+ for (i = NSIG, ki.num_width = 1; i >= 10; i /= 10)
+ ki.num_width++;
+ ki.name_width = mess_width = 0;
+ for (i = 0; i < NSIG; i++) {
+ w = sigtraps[i].name ? strlen(sigtraps[i].name) :
+ ki.num_width;
+ if (w > ki.name_width)
+ ki.name_width = w;
+ w = strlen(sigtraps[i].mess);
+ if (w > mess_width)
+ mess_width = w;
+ }
+
+ print_columns(shl_stdout, NSIG - 1,
+ kill_fmt_entry, (void *) &ki,
+ ki.num_width + ki.name_width + mess_width + 3, 1);
+ }
+ return 0;
+ }
+ rv = 0;
+ sig = t ? t->signal : SIGTERM;
+ for (; (p = wp[i]); i++) {
+ if (*p == '%') {
+ if (j_kill(p, sig))
+ rv = 1;
+ } else if (!getn(p, &n)) {
+ bi_errorf("%s: arguments must be jobs or process IDs",
+ p);
+ rv = 1;
+ } else {
+ /* use killpg if < -1 since -1 does special things for
+ * some non-killpg-endowed kills
+ */
+ if ((n < -1 ? killpg(-n, sig) : kill(n, sig)) < 0) {
+ bi_errorf("%s: %s", p, strerror(errno));
+ rv = 1;
+ }
+ }
+ }
+ return rv;
+}
+
+void
+getopts_reset(int val)
+{
+ if (val >= 1) {
+ ksh_getopt_reset(&user_opt,
+ GF_NONAME | (Flag(FPOSIX) ? 0 : GF_PLUSOPT));
+ user_opt.optind = user_opt.uoptind = val;
+ }
+}
+
+int
+c_getopts(char **wp)
+{
+ int argc;
+ const char *options;
+ const char *var;
+ int optc;
+ int ret;
+ char buf[3];
+ struct tbl *vq, *voptarg;
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return 1;
+ wp += builtin_opt.optind;
+
+ options = *wp++;
+ if (!options) {
+ bi_errorf("missing options argument");
+ return 1;
+ }
+
+ var = *wp++;
+ if (!var) {
+ bi_errorf("missing name argument");
+ return 1;
+ }
+ if (!*var || *skip_varname(var, true)) {
+ bi_errorf("%s: is not an identifier", var);
+ return 1;
+ }
+
+ if (e->loc->next == (struct block *) 0) {
+ internal_errorf(0, "c_getopts: no argv");
+ return 1;
+ }
+ /* Which arguments are we parsing... */
+ if (*wp == (char *) 0)
+ wp = e->loc->next->argv;
+ else
+ *--wp = e->loc->next->argv[0];
+
+ /* Check that our saved state won't cause a core dump... */
+ for (argc = 0; wp[argc]; argc++)
+ ;
+ if (user_opt.optind > argc ||
+ (user_opt.p != 0 &&
+ user_opt.p > strlen(wp[user_opt.optind - 1]))) {
+ bi_errorf("arguments changed since last call");
+ return 1;
+ }
+
+ user_opt.optarg = (char *) 0;
+ optc = ksh_getopt(wp, &user_opt, options);
+
+ if (optc >= 0 && optc != '?' && (user_opt.info & GI_PLUS)) {
+ buf[0] = '+';
+ buf[1] = optc;
+ buf[2] = '\0';
+ } else {
+ /* POSIX says var is set to ? at end-of-options, at&t ksh
+ * sets it to null - we go with POSIX...
+ */
+ buf[0] = optc < 0 ? '?' : optc;
+ buf[1] = '\0';
+ }
+
+ /* at&t ksh does not change OPTIND if it was an unknown option.
+ * Scripts counting on this are prone to break... (ie, don't count
+ * on this staying).
+ */
+ if (optc != '?') {
+ user_opt.uoptind = user_opt.optind;
+ }
+
+ voptarg = global("OPTARG");
+ voptarg->flag &= ~RDONLY; /* at&t ksh clears ro and int */
+ /* Paranoia: ensure no bizarre results. */
+ if (voptarg->flag & INTEGER)
+ typeset("OPTARG", 0, INTEGER, 0, 0);
+ if (user_opt.optarg == (char *) 0)
+ unset(voptarg, 0);
+ else
+ /* This can't fail (have cleared readonly/integer) */
+ setstr(voptarg, user_opt.optarg, KSH_RETURN_ERROR);
+
+ ret = 0;
+
+ vq = global(var);
+ /* Error message already printed (integer, readonly) */
+ if (!setstr(vq, buf, KSH_RETURN_ERROR))
+ ret = 1;
+ if (Flag(FEXPORT))
+ typeset(var, EXPORT, 0, 0, 0);
+
+ return optc < 0 ? 1 : ret;
+}
+
+#ifdef EMACS
+int
+c_bind(char **wp)
+{
+ int optc, rv = 0, macro = 0, list = 0;
+ char *cp;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "lm")) != -1)
+ switch (optc) {
+ case 'l':
+ list = 1;
+ break;
+ case 'm':
+ macro = 1;
+ break;
+ case '?':
+ return 1;
+ }
+ wp += builtin_opt.optind;
+
+ if (*wp == NULL) /* list all */
+ rv = x_bind((char*)NULL, (char*)NULL, 0, list);
+
+ for (; *wp != NULL; wp++) {
+ cp = strchr(*wp, '=');
+ if (cp != NULL)
+ *cp++ = '\0';
+ if (x_bind(*wp, cp, macro, 0))
+ rv = 1;
+ }
+
+ return rv;
+}
+#endif
+
+/* A leading = means assignments before command are kept;
+ * a leading * means a POSIX special builtin;
+ * a leading + means a POSIX regular builtin
+ * (* and + should not be combined).
+ */
+const struct builtin kshbuiltins [] = {
+ {"+alias", c_alias}, /* no =: at&t manual wrong */
+ {"+cd", c_cd},
+ {"+command", c_command},
+ {"echo", c_print},
+ {"*=export", c_typeset},
+#ifdef HISTORY
+ {"+fc", c_fc},
+#endif /* HISTORY */
+ {"+getopts", c_getopts},
+ {"+jobs", c_jobs},
+ {"+kill", c_kill},
+ {"let", c_let},
+ {"print", c_print},
+ {"pwd", c_pwd},
+ {"*=readonly", c_typeset},
+ {"=typeset", c_typeset},
+ {"+unalias", c_unalias},
+ {"whence", c_whence},
+#ifdef JOBS
+ {"+bg", c_fgbg},
+ {"+fg", c_fgbg},
+#endif
+#ifdef EMACS
+ {"bind", c_bind},
+#endif
+ {NULL, NULL}
+};
diff --git a/c_sh.c b/c_sh.c
@@ -0,0 +1,935 @@
+/* $OpenBSD: c_sh.c,v 1.45 2014/08/27 08:26:04 jmc Exp $ */
+
+/*
+ * built-in Bourne commands
+ */
+
+#include "sh.h"
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+static void p_time(struct shf *, int, struct timeval *, int, char *, char *);
+
+/* :, false and true */
+int
+c_label(char **wp)
+{
+ return wp[0][0] == 'f' ? 1 : 0;
+}
+
+int
+c_shift(char **wp)
+{
+ struct block *l = e->loc;
+ int n;
+ long val;
+ char *arg;
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return 1;
+ arg = wp[builtin_opt.optind];
+
+ if (arg) {
+ evaluate(arg, &val, KSH_UNWIND_ERROR, false);
+ n = val;
+ } else
+ n = 1;
+ if (n < 0) {
+ bi_errorf("%s: bad number", arg);
+ return (1);
+ }
+ if (l->argc < n) {
+ bi_errorf("nothing to shift");
+ return (1);
+ }
+ l->argv[n] = l->argv[0];
+ l->argv += n;
+ l->argc -= n;
+ return 0;
+}
+
+int
+c_umask(char **wp)
+{
+ int i;
+ char *cp;
+ int symbolic = 0;
+ mode_t old_umask;
+ int optc;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "S")) != -1)
+ switch (optc) {
+ case 'S':
+ symbolic = 1;
+ break;
+ case '?':
+ return 1;
+ }
+ cp = wp[builtin_opt.optind];
+ if (cp == NULL) {
+ old_umask = umask(0);
+ umask(old_umask);
+ if (symbolic) {
+ char buf[18];
+ int j;
+
+ old_umask = ~old_umask;
+ cp = buf;
+ for (i = 0; i < 3; i++) {
+ *cp++ = "ugo"[i];
+ *cp++ = '=';
+ for (j = 0; j < 3; j++)
+ if (old_umask & (1 << (8 - (3*i + j))))
+ *cp++ = "rwx"[j];
+ *cp++ = ',';
+ }
+ cp[-1] = '\0';
+ shprintf("%s\n", buf);
+ } else
+ shprintf("%#3.3o\n", old_umask);
+ } else {
+ mode_t new_umask;
+
+ if (digit(*cp)) {
+ for (new_umask = 0; *cp >= '0' && *cp <= '7'; cp++)
+ new_umask = new_umask * 8 + (*cp - '0');
+ if (*cp) {
+ bi_errorf("bad number");
+ return 1;
+ }
+ } else {
+ /* symbolic format */
+ int positions, new_val;
+ char op;
+
+ old_umask = umask(0);
+ umask(old_umask); /* in case of error */
+ old_umask = ~old_umask;
+ new_umask = old_umask;
+ positions = 0;
+ while (*cp) {
+ while (*cp && strchr("augo", *cp))
+ switch (*cp++) {
+ case 'a':
+ positions |= 0111;
+ break;
+ case 'u':
+ positions |= 0100;
+ break;
+ case 'g':
+ positions |= 0010;
+ break;
+ case 'o':
+ positions |= 0001;
+ break;
+ }
+ if (!positions)
+ positions = 0111; /* default is a */
+ if (!strchr("=+-", op = *cp))
+ break;
+ cp++;
+ new_val = 0;
+ while (*cp && strchr("rwxugoXs", *cp))
+ switch (*cp++) {
+ case 'r': new_val |= 04; break;
+ case 'w': new_val |= 02; break;
+ case 'x': new_val |= 01; break;
+ case 'u': new_val |= old_umask >> 6;
+ break;
+ case 'g': new_val |= old_umask >> 3;
+ break;
+ case 'o': new_val |= old_umask >> 0;
+ break;
+ case 'X': if (old_umask & 0111)
+ new_val |= 01;
+ break;
+ case 's': /* ignored */
+ break;
+ }
+ new_val = (new_val & 07) * positions;
+ switch (op) {
+ case '-':
+ new_umask &= ~new_val;
+ break;
+ case '=':
+ new_umask = new_val |
+ (new_umask & ~(positions * 07));
+ break;
+ case '+':
+ new_umask |= new_val;
+ }
+ if (*cp == ',') {
+ positions = 0;
+ cp++;
+ } else if (!strchr("=+-", *cp))
+ break;
+ }
+ if (*cp) {
+ bi_errorf("bad mask");
+ return 1;
+ }
+ new_umask = ~new_umask;
+ }
+ umask(new_umask);
+ }
+ return 0;
+}
+
+int
+c_dot(char **wp)
+{
+ char *file, *cp;
+ char **argv;
+ int argc;
+ int i;
+ int err;
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return 1;
+
+ if ((cp = wp[builtin_opt.optind]) == NULL)
+ return 0;
+ file = search(cp, path, R_OK, &err);
+ if (file == NULL) {
+ bi_errorf("%s: %s", cp, err ? strerror(err) : "not found");
+ return 1;
+ }
+
+ /* Set positional parameters? */
+ if (wp[builtin_opt.optind + 1]) {
+ argv = wp + builtin_opt.optind;
+ argv[0] = e->loc->argv[0]; /* preserve $0 */
+ for (argc = 0; argv[argc + 1]; argc++)
+ ;
+ } else {
+ argc = 0;
+ argv = (char **) 0;
+ }
+ i = include(file, argc, argv, 0);
+ if (i < 0) { /* should not happen */
+ bi_errorf("%s: %s", cp, strerror(errno));
+ return 1;
+ }
+ return i;
+}
+
+int
+c_wait(char **wp)
+{
+ int rv = 0;
+ int sig;
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return 1;
+ wp += builtin_opt.optind;
+ if (*wp == (char *) 0) {
+ while (waitfor((char *) 0, &sig) >= 0)
+ ;
+ rv = sig;
+ } else {
+ for (; *wp; wp++)
+ rv = waitfor(*wp, &sig);
+ if (rv < 0)
+ rv = sig ? sig : 127; /* magic exit code: bad job-id */
+ }
+ return rv;
+}
+
+int
+c_read(char **wp)
+{
+ int c = 0;
+ int expand = 1, history = 0;
+ int expanding;
+ int ecode = 0;
+ char *cp;
+ int fd = 0;
+ struct shf *shf;
+ int optc;
+ const char *emsg;
+ XString cs, xs;
+ struct tbl *vp;
+ char *xp = NULL;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "prsu,")) != -1)
+ switch (optc) {
+ case 'p':
+ if ((fd = coproc_getfd(R_OK, &emsg)) < 0) {
+ bi_errorf("-p: %s", emsg);
+ return 1;
+ }
+ break;
+ case 'r':
+ expand = 0;
+ break;
+ case 's':
+ history = 1;
+ break;
+ case 'u':
+ if (!*(cp = builtin_opt.optarg))
+ fd = 0;
+ else if ((fd = check_fd(cp, R_OK, &emsg)) < 0) {
+ bi_errorf("-u: %s: %s", cp, emsg);
+ return 1;
+ }
+ break;
+ case '?':
+ return 1;
+ }
+ wp += builtin_opt.optind;
+
+ if (*wp == NULL)
+ *--wp = "REPLY";
+
+ /* Since we can't necessarily seek backwards on non-regular files,
+ * don't buffer them so we can't read too much.
+ */
+ shf = shf_reopen(fd, SHF_RD | SHF_INTERRUPT | can_seek(fd), shl_spare);
+
+ if ((cp = strchr(*wp, '?')) != NULL) {
+ *cp = 0;
+ if (isatty(fd)) {
+ /* at&t ksh says it prints prompt on fd if it's open
+ * for writing and is a tty, but it doesn't do it
+ * (it also doesn't check the interactive flag,
+ * as is indicated in the Kornshell book).
+ */
+ shellf("%s", cp+1);
+ }
+ }
+
+ /* If we are reading from the co-process for the first time,
+ * make sure the other side of the pipe is closed first. This allows
+ * the detection of eof.
+ *
+ * This is not compatible with at&t ksh... the fd is kept so another
+ * coproc can be started with same output, however, this means eof
+ * can't be detected... This is why it is closed here.
+ * If this call is removed, remove the eof check below, too.
+ * coproc_readw_close(fd);
+ */
+
+ if (history)
+ Xinit(xs, xp, 128, ATEMP);
+ expanding = 0;
+ Xinit(cs, cp, 128, ATEMP);
+ for (; *wp != NULL; wp++) {
+ for (cp = Xstring(cs, cp); ; ) {
+ if (c == '\n' || c == EOF)
+ break;
+ while (1) {
+ c = shf_getc(shf);
+ if (c == '\0')
+ continue;
+ if (c == EOF && shf_error(shf) &&
+ shf_errno(shf) == EINTR) {
+ /* Was the offending signal one that
+ * would normally kill a process?
+ * If so, pretend the read was killed.
+ */
+ ecode = fatal_trap_check();
+
+ /* non fatal (eg, CHLD), carry on */
+ if (!ecode) {
+ shf_clearerr(shf);
+ continue;
+ }
+ }
+ break;
+ }
+ if (history) {
+ Xcheck(xs, xp);
+ Xput(xs, xp, c);
+ }
+ Xcheck(cs, cp);
+ if (expanding) {
+ expanding = 0;
+ if (c == '\n') {
+ c = 0;
+ if (Flag(FTALKING_I) && isatty(fd)) {
+ /* set prompt in case this is
+ * called from .profile or $ENV
+ */
+ set_prompt(PS2, (Source *) 0);
+ pprompt(prompt, 0);
+ }
+ } else if (c != EOF)
+ Xput(cs, cp, c);
+ continue;
+ }
+ if (expand && c == '\\') {
+ expanding = 1;
+ continue;
+ }
+ if (c == '\n' || c == EOF)
+ break;
+ if (ctype(c, C_IFS)) {
+ if (Xlength(cs, cp) == 0 && ctype(c, C_IFSWS))
+ continue;
+ if (wp[1])
+ break;
+ }
+ Xput(cs, cp, c);
+ }
+ /* strip trailing IFS white space from last variable */
+ if (!wp[1])
+ while (Xlength(cs, cp) && ctype(cp[-1], C_IFS) &&
+ ctype(cp[-1], C_IFSWS))
+ cp--;
+ Xput(cs, cp, '\0');
+ vp = global(*wp);
+ /* Must be done before setting export. */
+ if (vp->flag & RDONLY) {
+ shf_flush(shf);
+ bi_errorf("%s is read only", *wp);
+ return 1;
+ }
+ if (Flag(FEXPORT))
+ typeset(*wp, EXPORT, 0, 0, 0);
+ if (!setstr(vp, Xstring(cs, cp), KSH_RETURN_ERROR)) {
+ shf_flush(shf);
+ return 1;
+ }
+ }
+
+ shf_flush(shf);
+ if (history) {
+ Xput(xs, xp, '\0');
+ source->line++;
+ histsave(source->line, Xstring(xs, xp), 1);
+ Xfree(xs, xp);
+ }
+ /* if this is the co-process fd, close the file descriptor
+ * (can get eof if and only if all processes are have died, ie,
+ * coproc.njobs is 0 and the pipe is closed).
+ */
+ if (c == EOF && !ecode)
+ coproc_read_close(fd);
+
+ return ecode ? ecode : c == EOF;
+}
+
+int
+c_eval(char **wp)
+{
+ struct source *s;
+ int rv;
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return 1;
+ s = pushs(SWORDS, ATEMP);
+ s->u.strv = wp + builtin_opt.optind;
+ if (!Flag(FPOSIX)) {
+ /*
+ * Handle case where the command is empty due to failed
+ * command substitution, eg, eval "$(false)".
+ * In this case, shell() will not set/change exstat (because
+ * compiled tree is empty), so will use this value.
+ * subst_exstat is cleared in execute(), so should be 0 if
+ * there were no substitutions.
+ *
+ * A strict reading of POSIX says we don't do this (though
+ * it is traditionally done). [from 1003.2-1992]
+ * 3.9.1: Simple Commands
+ * ... If there is a command name, execution shall
+ * continue as described in 3.9.1.1. If there
+ * is no command name, but the command contained a command
+ * substitution, the command shall complete with the exit
+ * status of the last command substitution
+ * 3.9.1.1: Command Search and Execution
+ * ...(1)...(a) If the command name matches the name of
+ * a special built-in utility, that special built-in
+ * utility shall be invoked.
+ * 3.14.5: Eval
+ * ... If there are no arguments, or only null arguments,
+ * eval shall return an exit status of zero.
+ */
+ exstat = subst_exstat;
+ }
+
+ rv = shell(s, false);
+ afree(s, ATEMP);
+ return (rv);
+}
+
+int
+c_trap(char **wp)
+{
+ int i;
+ char *s;
+ Trap *p;
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return 1;
+ wp += builtin_opt.optind;
+
+ if (*wp == NULL) {
+ for (p = sigtraps, i = NSIG+1; --i >= 0; p++) {
+ if (p->trap != NULL) {
+ shprintf("trap -- ");
+ print_value_quoted(p->trap);
+ shprintf(" %s\n", p->name);
+ }
+ }
+ return 0;
+ }
+
+ /*
+ * Use case sensitive lookup for first arg so the
+ * command 'exit' isn't confused with the pseudo-signal
+ * 'EXIT'.
+ */
+ s = (gettrap(*wp, false) == NULL) ? *wp++ : NULL; /* get command */
+ if (s != NULL && s[0] == '-' && s[1] == '\0')
+ s = NULL;
+
+ /* set/clear traps */
+ while (*wp != NULL) {
+ p = gettrap(*wp++, true);
+ if (p == NULL) {
+ bi_errorf("bad signal %s", wp[-1]);
+ return 1;
+ }
+ settrap(p, s);
+ }
+ return 0;
+}
+
+int
+c_exitreturn(char **wp)
+{
+ int how = LEXIT;
+ int n;
+ char *arg;
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return 1;
+ arg = wp[builtin_opt.optind];
+
+ if (arg) {
+ if (!getn(arg, &n)) {
+ exstat = 1;
+ warningf(true, "%s: bad number", arg);
+ } else
+ exstat = n;
+ }
+ if (wp[0][0] == 'r') { /* return */
+ struct env *ep;
+
+ /* need to tell if this is exit or return so trap exit will
+ * work right (POSIX)
+ */
+ for (ep = e; ep; ep = ep->oenv)
+ if (STOP_RETURN(ep->type)) {
+ how = LRETURN;
+ break;
+ }
+ }
+
+ if (how == LEXIT && !really_exit && j_stopped_running()) {
+ really_exit = 1;
+ how = LSHELL;
+ }
+
+ quitenv(NULL); /* get rid of any i/o redirections */
+ unwind(how);
+ /* NOTREACHED */
+ return 0;
+}
+
+int
+c_brkcont(char **wp)
+{
+ int n, quit;
+ struct env *ep, *last_ep = (struct env *) 0;
+ char *arg;
+
+ if (ksh_getopt(wp, &builtin_opt, null) == '?')
+ return 1;
+ arg = wp[builtin_opt.optind];
+
+ if (!arg)
+ n = 1;
+ else if (!bi_getn(arg, &n))
+ return 1;
+ quit = n;
+ if (quit <= 0) {
+ /* at&t ksh does this for non-interactive shells only - weird */
+ bi_errorf("%s: bad value", arg);
+ return 1;
+ }
+
+ /* Stop at E_NONE, E_PARSE, E_FUNC, or E_INCL */
+ for (ep = e; ep && !STOP_BRKCONT(ep->type); ep = ep->oenv)
+ if (ep->type == E_LOOP) {
+ if (--quit == 0)
+ break;
+ ep->flags |= EF_BRKCONT_PASS;
+ last_ep = ep;
+ }
+
+ if (quit) {
+ /* at&t ksh doesn't print a message - just does what it
+ * can. We print a message 'cause it helps in debugging
+ * scripts, but don't generate an error (ie, keep going).
+ */
+ if (n == quit) {
+ warningf(true, "%s: cannot %s", wp[0], wp[0]);
+ return 0;
+ }
+ /* POSIX says if n is too big, the last enclosing loop
+ * shall be used. Doesn't say to print an error but we
+ * do anyway 'cause the user messed up.
+ */
+ if (last_ep)
+ last_ep->flags &= ~EF_BRKCONT_PASS;
+ warningf(true, "%s: can only %s %d level(s)",
+ wp[0], wp[0], n - quit);
+ }
+
+ unwind(*wp[0] == 'b' ? LBREAK : LCONTIN);
+ /* NOTREACHED */
+}
+
+int
+c_set(char **wp)
+{
+ int argi, setargs;
+ struct block *l = e->loc;
+ char **owp = wp;
+
+ if (wp[1] == NULL) {
+ static const char *const args [] = { "set", "-", NULL };
+ return c_typeset((char **) args);
+ }
+
+ argi = parse_args(wp, OF_SET, &setargs);
+ if (argi < 0)
+ return 1;
+ /* set $# and $* */
+ if (setargs) {
+ owp = wp += argi - 1;
+ wp[0] = l->argv[0]; /* save $0 */
+ while (*++wp != NULL)
+ *wp = str_save(*wp, &l->area);
+ l->argc = wp - owp - 1;
+ l->argv = (char **) alloc(sizeofN(char *, l->argc+2), &l->area);
+ for (wp = l->argv; (*wp++ = *owp++) != NULL; )
+ ;
+ }
+ /* POSIX says set exit status is 0, but old scripts that use
+ * getopt(1), use the construct: set -- `getopt ab:c "$@"`
+ * which assumes the exit value set will be that of the ``
+ * (subst_exstat is cleared in execute() so that it will be 0
+ * if there are no command substitutions).
+ */
+ return Flag(FPOSIX) ? 0 : subst_exstat;
+}
+
+int
+c_unset(char **wp)
+{
+ char *id;
+ int optc, unset_var = 1;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "fv")) != -1)
+ switch (optc) {
+ case 'f':
+ unset_var = 0;
+ break;
+ case 'v':
+ unset_var = 1;
+ break;
+ case '?':
+ return 1;
+ }
+ wp += builtin_opt.optind;
+ for (; (id = *wp) != NULL; wp++)
+ if (unset_var) { /* unset variable */
+ struct tbl *vp = global(id);
+
+ if ((vp->flag&RDONLY)) {
+ bi_errorf("%s is read only", vp->name);
+ return 1;
+ }
+ unset(vp, strchr(id, '[') ? 1 : 0);
+ } else { /* unset function */
+ define(id, (struct op *) NULL);
+ }
+ return 0;
+}
+
+static void
+p_time(struct shf *shf, int posix, struct timeval *tv, int width, char *prefix,
+ char *suffix)
+{
+ if (posix)
+ shf_fprintf(shf, "%s%*lld.%02ld%s", prefix ? prefix : "",
+ width, (long long)tv->tv_sec, tv->tv_usec / 10000, suffix);
+ else
+ shf_fprintf(shf, "%s%*lldm%lld.%02lds%s", prefix ? prefix : "",
+ width, (long long)tv->tv_sec / 60,
+ (long long)tv->tv_sec % 60,
+ tv->tv_usec / 10000, suffix);
+}
+
+int
+c_times(char **wp)
+{
+ struct rusage usage;
+
+ (void) getrusage(RUSAGE_SELF, &usage);
+ p_time(shl_stdout, 0, &usage.ru_utime, 0, NULL, " ");
+ p_time(shl_stdout, 0, &usage.ru_stime, 0, NULL, "\n");
+
+ (void) getrusage(RUSAGE_CHILDREN, &usage);
+ p_time(shl_stdout, 0, &usage.ru_utime, 0, NULL, " ");
+ p_time(shl_stdout, 0, &usage.ru_stime, 0, NULL, "\n");
+
+ return 0;
+}
+
+/*
+ * time pipeline (really a statement, not a built-in command)
+ */
+int
+timex(struct op *t, int f, volatile int *xerrok)
+{
+#define TF_NOARGS BIT(0)
+#define TF_NOREAL BIT(1) /* don't report real time */
+#define TF_POSIX BIT(2) /* report in posix format */
+ int rv = 0;
+ struct rusage ru0, ru1, cru0, cru1;
+ struct timeval usrtime, systime, tv0, tv1;
+ int tf = 0;
+ extern struct timeval j_usrtime, j_systime; /* computed by j_wait */
+
+ gettimeofday(&tv0, NULL);
+ getrusage(RUSAGE_SELF, &ru0);
+ getrusage(RUSAGE_CHILDREN, &cru0);
+ if (t->left) {
+ /*
+ * Two ways of getting cpu usage of a command: just use t0
+ * and t1 (which will get cpu usage from other jobs that
+ * finish while we are executing t->left), or get the
+ * cpu usage of t->left. at&t ksh does the former, while
+ * pdksh tries to do the later (the j_usrtime hack doesn't
+ * really work as it only counts the last job).
+ */
+ timerclear(&j_usrtime);
+ timerclear(&j_systime);
+ rv = execute(t->left, f | XTIME, xerrok);
+ if (t->left->type == TCOM)
+ tf |= t->left->str[0];
+ gettimeofday(&tv1, NULL);
+ getrusage(RUSAGE_SELF, &ru1);
+ getrusage(RUSAGE_CHILDREN, &cru1);
+ } else
+ tf = TF_NOARGS;
+
+ if (tf & TF_NOARGS) { /* ksh93 - report shell times (shell+kids) */
+ tf |= TF_NOREAL;
+ timeradd(&ru0.ru_utime, &cru0.ru_utime, &usrtime);
+ timeradd(&ru0.ru_stime, &cru0.ru_stime, &systime);
+ } else {
+ timersub(&ru1.ru_utime, &ru0.ru_utime, &usrtime);
+ timeradd(&usrtime, &j_usrtime, &usrtime);
+ timersub(&ru1.ru_stime, &ru0.ru_stime, &systime);
+ timeradd(&systime, &j_systime, &systime);
+ }
+
+ if (!(tf & TF_NOREAL)) {
+ timersub(&tv1, &tv0, &tv1);
+ if (tf & TF_POSIX)
+ p_time(shl_out, 1, &tv1, 5, "real ", "\n");
+ else
+ p_time(shl_out, 0, &tv1, 5, NULL, " real ");
+ }
+ if (tf & TF_POSIX)
+ p_time(shl_out, 1, &usrtime, 5, "user ", "\n");
+ else
+ p_time(shl_out, 0, &usrtime, 5, NULL, " user ");
+ if (tf & TF_POSIX)
+ p_time(shl_out, 1, &systime, 5, "sys ", "\n");
+ else
+ p_time(shl_out, 0, &systime, 5, NULL, " system\n");
+ shf_flush(shl_out);
+
+ return rv;
+}
+
+void
+timex_hook(struct op *t, char **volatile *app)
+{
+ char **wp = *app;
+ int optc;
+ int i, j;
+ Getopt opt;
+
+ ksh_getopt_reset(&opt, 0);
+ opt.optind = 0; /* start at the start */
+ while ((optc = ksh_getopt(wp, &opt, ":p")) != -1)
+ switch (optc) {
+ case 'p':
+ t->str[0] |= TF_POSIX;
+ break;
+ case '?':
+ errorf("time: -%s unknown option", opt.optarg);
+ case ':':
+ errorf("time: -%s requires an argument",
+ opt.optarg);
+ }
+ /* Copy command words down over options. */
+ if (opt.optind != 0) {
+ for (i = 0; i < opt.optind; i++)
+ afree(wp[i], ATEMP);
+ for (i = 0, j = opt.optind; (wp[i] = wp[j]); i++, j++)
+ ;
+ }
+ if (!wp[0])
+ t->str[0] |= TF_NOARGS;
+ *app = wp;
+}
+
+/* exec with no args - args case is taken care of in comexec() */
+int
+c_exec(char **wp)
+{
+ int i;
+
+ /* make sure redirects stay in place */
+ if (e->savefd != NULL) {
+ for (i = 0; i < NUFILE; i++) {
+ if (e->savefd[i] > 0)
+ close(e->savefd[i]);
+ /*
+ * For ksh keep anything > 2 private,
+ * for sh, let them be (POSIX says what
+ * happens is unspecified and the bourne shell
+ * keeps them open).
+ */
+ if (!Flag(FSH) && i > 2 && e->savefd[i])
+ fcntl(i, F_SETFD, FD_CLOEXEC);
+ }
+ e->savefd = NULL;
+ }
+ return 0;
+}
+
+static int
+c_mknod(char **wp)
+{
+ int argc, optc, ismkfifo = 0, ret;
+ char **argv;
+ void *set = NULL;
+ mode_t mode = 0, oldmode = 0;
+
+ while ((optc = ksh_getopt(wp, &builtin_opt, "m:")) != -1) {
+ switch (optc) {
+ case 'm':
+ set = setmode(builtin_opt.optarg);
+ if (set == NULL) {
+ bi_errorf("invalid file mode");
+ return 1;
+ }
+ mode = getmode(set, DEFFILEMODE);
+ free(set);
+ break;
+ default:
+ goto usage;
+ }
+ }
+ argv = &wp[builtin_opt.optind];
+ if (argv[0] == NULL)
+ goto usage;
+ for (argc = 0; argv[argc]; argc++)
+ ;
+ if (argc == 2 && argv[1][0] == 'p') {
+ ismkfifo = 1;
+ argc--;
+ } else if (argc != 4)
+ goto usage;
+
+ if (set)
+ oldmode = umask(0);
+ else
+ mode = DEFFILEMODE;
+
+ if (ismkfifo)
+ ret = domkfifo(argc, argv, mode);
+ else
+ ret = domknod(argc, argv, mode);
+
+ if (set)
+ umask(oldmode);
+ return ret;
+usage:
+ builtin_argv0 = NULL;
+ bi_errorf("usage: mknod [-m mode] name b|c major minor");
+ bi_errorf("usage: mknod [-m mode] name p");
+ return 1;
+}
+
+static int
+c_suspend(char **wp)
+{
+ if (wp[1] != NULL) {
+ bi_errorf("too many arguments");
+ return 1;
+ }
+ if (Flag(FLOGIN)) {
+ /* Can't suspend an orphaned process group. */
+ pid_t parent = getppid();
+ if (getpgid(parent) == getpgid(0) ||
+ getsid(parent) != getsid(0)) {
+ bi_errorf("can't suspend a login shell");
+ return 1;
+ }
+ }
+ j_suspend();
+ return 0;
+}
+
+/* dummy function, special case in comexec() */
+int
+c_builtin(char **wp)
+{
+ return 0;
+}
+
+extern int c_test(char **wp); /* in c_test.c */
+extern int c_ulimit(char **wp); /* in c_ulimit.c */
+
+/* A leading = means assignments before command are kept;
+ * a leading * means a POSIX special builtin;
+ * a leading + means a POSIX regular builtin
+ * (* and + should not be combined).
+ */
+const struct builtin shbuiltins [] = {
+ {"*=.", c_dot},
+ {"*=:", c_label},
+ {"[", c_test},
+ {"*=break", c_brkcont},
+ {"=builtin", c_builtin},
+ {"*=continue", c_brkcont},
+ {"*=eval", c_eval},
+ {"*=exec", c_exec},
+ {"*=exit", c_exitreturn},
+ {"+false", c_label},
+ {"*=return", c_exitreturn},
+ {"*=set", c_set},
+ {"*=shift", c_shift},
+ {"*=times", c_times},
+ {"*=trap", c_trap},
+ {"+=wait", c_wait},
+ {"+read", c_read},
+ {"test", c_test},
+ {"+true", c_label},
+ {"ulimit", c_ulimit},
+ {"+umask", c_umask},
+ {"*=unset", c_unset},
+ {"mknod", c_mknod},
+ {"suspend", c_suspend},
+ {NULL, NULL}
+};
diff --git a/c_test.c b/c_test.c
@@ -0,0 +1,560 @@
+/* $OpenBSD: c_test.c,v 1.18 2009/03/01 20:11:06 otto 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 Michael Rendell to add Korn's [[ .. ]] expressions.
+ * modified by J.T. Conklin to add POSIX compatibility.
+ */
+
+#include "sh.h"
+#include <sys/stat.h>
+#include "c_test.h"
+
+/* test(1) accepts the following grammar:
+ oexpr ::= aexpr | aexpr "-o" oexpr ;
+ aexpr ::= nexpr | nexpr "-a" aexpr ;
+ nexpr ::= primary | "!" nexpr ;
+ primary ::= unary-operator operand
+ | operand binary-operator operand
+ | operand
+ | "(" oexpr ")"
+ ;
+
+ unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"|
+ "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|
+ "-L"|"-h"|"-S"|"-H";
+
+ binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
+ "-nt"|"-ot"|"-ef"|
+ "<"|">" # rules used for [[ .. ]] expressions
+ ;
+ operand ::= <any thing>
+*/
+
+#define T_ERR_EXIT 2 /* POSIX says > 1 for errors */
+
+struct t_op {
+ char op_text[4];
+ Test_op op_num;
+};
+static const struct t_op u_ops [] = {
+ {"-a", TO_FILAXST },
+ {"-b", TO_FILBDEV },
+ {"-c", TO_FILCDEV },
+ {"-d", TO_FILID },
+ {"-e", TO_FILEXST },
+ {"-f", TO_FILREG },
+ {"-G", TO_FILGID },
+ {"-g", TO_FILSETG },
+ {"-h", TO_FILSYM },
+ {"-H", TO_FILCDF },
+ {"-k", TO_FILSTCK },
+ {"-L", TO_FILSYM },
+ {"-n", TO_STNZE },
+ {"-O", TO_FILUID },
+ {"-o", TO_OPTION },
+ {"-p", TO_FILFIFO },
+ {"-r", TO_FILRD },
+ {"-s", TO_FILGZ },
+ {"-S", TO_FILSOCK },
+ {"-t", TO_FILTT },
+ {"-u", TO_FILSETU },
+ {"-w", TO_FILWR },
+ {"-x", TO_FILEX },
+ {"-z", TO_STZER },
+ {"", TO_NONOP }
+};
+static const struct t_op b_ops [] = {
+ {"=", TO_STEQL },
+ {"==", TO_STEQL },
+ {"!=", TO_STNEQ },
+ {"<", TO_STLT },
+ {">", TO_STGT },
+ {"-eq", TO_INTEQ },
+ {"-ne", TO_INTNE },
+ {"-gt", TO_INTGT },
+ {"-ge", TO_INTGE },
+ {"-lt", TO_INTLT },
+ {"-le", TO_INTLE },
+ {"-ef", TO_FILEQ },
+ {"-nt", TO_FILNT },
+ {"-ot", TO_FILOT },
+ {"", TO_NONOP }
+};
+
+static int test_stat(const char *, struct stat *);
+static int test_eaccess(const char *, int);
+static int test_oexpr(Test_env *, int);
+static int test_aexpr(Test_env *, int);
+static int test_nexpr(Test_env *, int);
+static int test_primary(Test_env *, int);
+static int ptest_isa(Test_env *, Test_meta);
+static const char *ptest_getopnd(Test_env *, Test_op, int);
+static int ptest_eval(Test_env *, Test_op, const char *,
+ const char *, int);
+static void ptest_error(Test_env *, int, const char *);
+
+int
+c_test(char **wp)
+{
+ int argc;
+ int res;
+ Test_env te;
+
+ te.flags = 0;
+ te.isa = ptest_isa;
+ te.getopnd = ptest_getopnd;
+ te.eval = ptest_eval;
+ te.error = ptest_error;
+
+ for (argc = 0; wp[argc]; argc++)
+ ;
+
+ if (strcmp(wp[0], "[") == 0) {
+ if (strcmp(wp[--argc], "]") != 0) {
+ bi_errorf("missing ]");
+ return T_ERR_EXIT;
+ }
+ }
+
+ te.pos.wp = wp + 1;
+ te.wp_end = wp + argc;
+
+ /*
+ * Handle the special cases from POSIX.2, section 4.62.4.
+ * Implementation of all the rules isn't necessary since
+ * our parser does the right thing for the omitted steps.
+ */
+ if (argc <= 5) {
+ char **owp = wp;
+ int invert = 0;
+ Test_op op;
+ const char *opnd1, *opnd2;
+
+ while (--argc >= 0) {
+ if ((*te.isa)(&te, TM_END))
+ return !0;
+ if (argc == 3) {
+ opnd1 = (*te.getopnd)(&te, TO_NONOP, 1);
+ if ((op = (Test_op) (*te.isa)(&te, TM_BINOP))) {
+ opnd2 = (*te.getopnd)(&te, op, 1);
+ res = (*te.eval)(&te, op, opnd1,
+ opnd2, 1);
+ if (te.flags & TEF_ERROR)
+ return T_ERR_EXIT;
+ if (invert & 1)
+ res = !res;
+ return !res;
+ }
+ /* back up to opnd1 */
+ te.pos.wp--;
+ }
+ if (argc == 1) {
+ opnd1 = (*te.getopnd)(&te, TO_NONOP, 1);
+ /* Historically, -t by itself test if fd 1
+ * is a file descriptor, but POSIX says its
+ * a string test...
+ */
+ if (!Flag(FPOSIX) && strcmp(opnd1, "-t") == 0)
+ break;
+ res = (*te.eval)(&te, TO_STNZE, opnd1,
+ (char *) 0, 1);
+ if (invert & 1)
+ res = !res;
+ return !res;
+ }
+ if ((*te.isa)(&te, TM_NOT)) {
+ invert++;
+ } else
+ break;
+ }
+ te.pos.wp = owp + 1;
+ }
+
+ return test_parse(&te);
+}
+
+/*
+ * Generic test routines.
+ */
+
+Test_op
+test_isop(Test_env *te, Test_meta meta, const char *s)
+{
+ char sc1;
+ const struct t_op *otab;
+
+ otab = meta == TM_UNOP ? u_ops : b_ops;
+ if (*s) {
+ sc1 = s[1];
+ for (; otab->op_text[0]; otab++)
+ if (sc1 == otab->op_text[1] &&
+ strcmp(s, otab->op_text) == 0 &&
+ ((te->flags & TEF_DBRACKET) ||
+ (otab->op_num != TO_STLT && otab->op_num != TO_STGT)))
+ return otab->op_num;
+ }
+ return TO_NONOP;
+}
+
+int
+test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
+ int do_eval)
+{
+ int res;
+ int not;
+ struct stat b1, b2;
+
+ if (!do_eval)
+ return 0;
+
+ switch ((int) op) {
+ /*
+ * Unary Operators
+ */
+ case TO_STNZE: /* -n */
+ return *opnd1 != '\0';
+ case TO_STZER: /* -z */
+ return *opnd1 == '\0';
+ case TO_OPTION: /* -o */
+ if ((not = *opnd1 == '!'))
+ opnd1++;
+ if ((res = option(opnd1)) < 0)
+ res = 0;
+ else {
+ res = Flag(res);
+ if (not)
+ res = !res;
+ }
+ return res;
+ case TO_FILRD: /* -r */
+ return test_eaccess(opnd1, R_OK) == 0;
+ case TO_FILWR: /* -w */
+ return test_eaccess(opnd1, W_OK) == 0;
+ case TO_FILEX: /* -x */
+ return test_eaccess(opnd1, X_OK) == 0;
+ case TO_FILAXST: /* -a */
+ return test_stat(opnd1, &b1) == 0;
+ case TO_FILEXST: /* -e */
+ /* at&t ksh does not appear to do the /dev/fd/ thing for
+ * this (unless the os itself handles it)
+ */
+ return stat(opnd1, &b1) == 0;
+ case TO_FILREG: /* -r */
+ return test_stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode);
+ case TO_FILID: /* -d */
+ return test_stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode);
+ case TO_FILCDEV: /* -c */
+ return test_stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode);
+ case TO_FILBDEV: /* -b */
+ return test_stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode);
+ case TO_FILFIFO: /* -p */
+ return test_stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode);
+ case TO_FILSYM: /* -h -L */
+ return lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode);
+ case TO_FILSOCK: /* -S */
+ return test_stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode);
+ case TO_FILCDF:/* -H HP context dependent files (directories) */
+ return 0;
+ case TO_FILSETU: /* -u */
+ return test_stat(opnd1, &b1) == 0 &&
+ (b1.st_mode & S_ISUID) == S_ISUID;
+ case TO_FILSETG: /* -g */
+ return test_stat(opnd1, &b1) == 0 &&
+ (b1.st_mode & S_ISGID) == S_ISGID;
+ case TO_FILSTCK: /* -k */
+ return test_stat(opnd1, &b1) == 0 &&
+ (b1.st_mode & S_ISVTX) == S_ISVTX;
+ case TO_FILGZ: /* -s */
+ return test_stat(opnd1, &b1) == 0 && b1.st_size > 0L;
+ case TO_FILTT: /* -t */
+ if (opnd1 && !bi_getn(opnd1, &res)) {
+ te->flags |= TEF_ERROR;
+ res = 0;
+ } else {
+ /* generate error if in FPOSIX mode? */
+ res = isatty(opnd1 ? res : 0);
+ }
+ return res;
+ case TO_FILUID: /* -O */
+ return test_stat(opnd1, &b1) == 0 && b1.st_uid == ksheuid;
+ case TO_FILGID: /* -G */
+ return test_stat(opnd1, &b1) == 0 && b1.st_gid == getegid();
+ /*
+ * Binary Operators
+ */
+ case TO_STEQL: /* = */
+ if (te->flags & TEF_DBRACKET)
+ return gmatch(opnd1, opnd2, false);
+ return strcmp(opnd1, opnd2) == 0;
+ case TO_STNEQ: /* != */
+ if (te->flags & TEF_DBRACKET)
+ return !gmatch(opnd1, opnd2, false);
+ return strcmp(opnd1, opnd2) != 0;
+ case TO_STLT: /* < */
+ return strcmp(opnd1, opnd2) < 0;
+ case TO_STGT: /* > */
+ return strcmp(opnd1, opnd2) > 0;
+ case TO_INTEQ: /* -eq */
+ case TO_INTNE: /* -ne */
+ case TO_INTGE: /* -ge */
+ case TO_INTGT: /* -gt */
+ case TO_INTLE: /* -le */
+ case TO_INTLT: /* -lt */
+ {
+ long v1, v2;
+
+ if (!evaluate(opnd1, &v1, KSH_RETURN_ERROR, false) ||
+ !evaluate(opnd2, &v2, KSH_RETURN_ERROR, false)) {
+ /* error already printed.. */
+ te->flags |= TEF_ERROR;
+ return 1;
+ }
+ switch ((int) op) {
+ case TO_INTEQ:
+ return v1 == v2;
+ case TO_INTNE:
+ return v1 != v2;
+ case TO_INTGE:
+ return v1 >= v2;
+ case TO_INTGT:
+ return v1 > v2;
+ case TO_INTLE:
+ return v1 <= v2;
+ case TO_INTLT:
+ return v1 < v2;
+ }
+ }
+ case TO_FILNT: /* -nt */
+ {
+ int s2;
+ /* ksh88/ksh93 succeed if file2 can't be stated
+ * (subtly different from `does not exist').
+ */
+ return stat(opnd1, &b1) == 0 &&
+ (((s2 = stat(opnd2, &b2)) == 0 &&
+ b1.st_mtime > b2.st_mtime) || s2 < 0);
+ }
+ case TO_FILOT: /* -ot */
+ {
+ int s1;
+ /* ksh88/ksh93 succeed if file1 can't be stated
+ * (subtly different from `does not exist').
+ */
+ return stat(opnd2, &b2) == 0 &&
+ (((s1 = stat(opnd1, &b1)) == 0 &&
+ b1.st_mtime < b2.st_mtime) || s1 < 0);
+ }
+ case TO_FILEQ: /* -ef */
+ return stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 &&
+ b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino;
+ }
+ (*te->error)(te, 0, "internal error: unknown op");
+ return 1;
+}
+
+/* Nasty kludge to handle Korn's bizarre /dev/fd hack */
+static int
+test_stat(const char *path, struct stat *statb)
+{
+ return stat(path, statb);
+}
+
+/* Routine to handle Korn's /dev/fd hack, and to deal with X_OK on
+ * non-directories when running as root.
+ */
+static int
+test_eaccess(const char *path, int mode)
+{
+ int res;
+
+ res = access(path, mode);
+ /*
+ * On most (all?) unixes, access() says everything is executable for
+ * root - avoid this on files by using stat().
+ */
+ if (res == 0 && ksheuid == 0 && (mode & X_OK)) {
+ struct stat statb;
+
+ if (stat(path, &statb) < 0)
+ res = -1;
+ else if (S_ISDIR(statb.st_mode))
+ res = 0;
+ else
+ res = (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) ?
+ 0 : -1;
+ }
+
+ return res;
+}
+
+int
+test_parse(Test_env *te)
+{
+ int res;
+
+ res = test_oexpr(te, 1);
+
+ if (!(te->flags & TEF_ERROR) && !(*te->isa)(te, TM_END))
+ (*te->error)(te, 0, "unexpected operator/operand");
+
+ return (te->flags & TEF_ERROR) ? T_ERR_EXIT : !res;
+}
+
+static int
+test_oexpr(Test_env *te, int do_eval)
+{
+ int res;
+
+ res = test_aexpr(te, do_eval);
+ if (res)
+ do_eval = 0;
+ if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_OR))
+ return test_oexpr(te, do_eval) || res;
+ return res;
+}
+
+static int
+test_aexpr(Test_env *te, int do_eval)
+{
+ int res;
+
+ res = test_nexpr(te, do_eval);
+ if (!res)
+ do_eval = 0;
+ if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_AND))
+ return test_aexpr(te, do_eval) && res;
+ return res;
+}
+
+static int
+test_nexpr(Test_env *te, int do_eval)
+{
+ if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_NOT))
+ return !test_nexpr(te, do_eval);
+ return test_primary(te, do_eval);
+}
+
+static int
+test_primary(Test_env *te, int do_eval)
+{
+ const char *opnd1, *opnd2;
+ int res;
+ Test_op op;
+
+ if (te->flags & TEF_ERROR)
+ return 0;
+ if ((*te->isa)(te, TM_OPAREN)) {
+ res = test_oexpr(te, do_eval);
+ if (te->flags & TEF_ERROR)
+ return 0;
+ if (!(*te->isa)(te, TM_CPAREN)) {
+ (*te->error)(te, 0, "missing closing paren");
+ return 0;
+ }
+ return res;
+ }
+ /*
+ * Binary should have precedence over unary in this case
+ * so that something like test \( -f = -f \) is accepted
+ */
+ if ((te->flags & TEF_DBRACKET) || (&te->pos.wp[1] < te->wp_end &&
+ !test_isop(te, TM_BINOP, te->pos.wp[1]))) {
+ if ((op = (Test_op) (*te->isa)(te, TM_UNOP))) {
+ /* unary expression */
+ opnd1 = (*te->getopnd)(te, op, do_eval);
+ if (!opnd1) {
+ (*te->error)(te, -1, "missing argument");
+ return 0;
+ }
+
+ return (*te->eval)(te, op, opnd1, (const char *) 0,
+ do_eval);
+ }
+ }
+ opnd1 = (*te->getopnd)(te, TO_NONOP, do_eval);
+ if (!opnd1) {
+ (*te->error)(te, 0, "expression expected");
+ return 0;
+ }
+ if ((op = (Test_op) (*te->isa)(te, TM_BINOP))) {
+ /* binary expression */
+ opnd2 = (*te->getopnd)(te, op, do_eval);
+ if (!opnd2) {
+ (*te->error)(te, -1, "missing second argument");
+ return 0;
+ }
+
+ return (*te->eval)(te, op, opnd1, opnd2, do_eval);
+ }
+ if (te->flags & TEF_DBRACKET) {
+ (*te->error)(te, -1, "missing expression operator");
+ return 0;
+ }
+ return (*te->eval)(te, TO_STNZE, opnd1, (const char *) 0, do_eval);
+}
+
+/*
+ * Plain test (test and [ .. ]) specific routines.
+ */
+
+/* Test if the current token is a whatever. Accepts the current token if
+ * it is. Returns 0 if it is not, non-zero if it is (in the case of
+ * TM_UNOP and TM_BINOP, the returned value is a Test_op).
+ */
+static int
+ptest_isa(Test_env *te, Test_meta meta)
+{
+ /* Order important - indexed by Test_meta values */
+ static const char *const tokens[] = {
+ "-o", "-a", "!", "(", ")"
+ };
+ int ret;
+
+ if (te->pos.wp >= te->wp_end)
+ return meta == TM_END;
+
+ if (meta == TM_UNOP || meta == TM_BINOP)
+ ret = (int) test_isop(te, meta, *te->pos.wp);
+ else if (meta == TM_END)
+ ret = 0;
+ else
+ ret = strcmp(*te->pos.wp, tokens[(int) meta]) == 0;
+
+ /* Accept the token? */
+ if (ret)
+ te->pos.wp++;
+
+ return ret;
+}
+
+static const char *
+ptest_getopnd(Test_env *te, Test_op op, int do_eval)
+{
+ if (te->pos.wp >= te->wp_end)
+ return op == TO_FILTT ? "1" : (const char *) 0;
+ return *te->pos.wp++;
+}
+
+static int
+ptest_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
+ int do_eval)
+{
+ return test_eval(te, op, opnd1, opnd2, do_eval);
+}
+
+static void
+ptest_error(Test_env *te, int offset, const char *msg)
+{
+ const char *op = te->pos.wp + offset >= te->wp_end ?
+ (const char *) 0 : te->pos.wp[offset];
+
+ te->flags |= TEF_ERROR;
+ if (op)
+ bi_errorf("%s: %s", op, msg);
+ else
+ bi_errorf("%s", msg);
+}
diff --git a/c_test.h b/c_test.h
@@ -0,0 +1,53 @@
+/* $OpenBSD: c_test.h,v 1.4 2004/12/20 11:34:26 otto Exp $ */
+
+/* Various types of operations. Keeping things grouped nicely
+ * (unary,binary) makes switch() statements more efficient.
+ */
+enum Test_op {
+ TO_NONOP = 0, /* non-operator */
+ /* unary operators */
+ TO_STNZE, TO_STZER, TO_OPTION,
+ TO_FILAXST,
+ TO_FILEXST,
+ TO_FILREG, TO_FILBDEV, TO_FILCDEV, TO_FILSYM, TO_FILFIFO, TO_FILSOCK,
+ TO_FILCDF, TO_FILID, TO_FILGID, TO_FILSETG, TO_FILSTCK, TO_FILUID,
+ TO_FILRD, TO_FILGZ, TO_FILTT, TO_FILSETU, TO_FILWR, TO_FILEX,
+ /* binary operators */
+ TO_STEQL, TO_STNEQ, TO_STLT, TO_STGT, TO_INTEQ, TO_INTNE, TO_INTGT,
+ TO_INTGE, TO_INTLT, TO_INTLE, TO_FILEQ, TO_FILNT, TO_FILOT
+};
+typedef enum Test_op Test_op;
+
+/* Used by Test_env.isa() (order important - used to index *_tokens[] arrays) */
+enum Test_meta {
+ TM_OR, /* -o or || */
+ TM_AND, /* -a or && */
+ TM_NOT, /* ! */
+ TM_OPAREN, /* ( */
+ TM_CPAREN, /* ) */
+ TM_UNOP, /* unary operator */
+ TM_BINOP, /* binary operator */
+ TM_END /* end of input */
+};
+typedef enum Test_meta Test_meta;
+
+#define TEF_ERROR BIT(0) /* set if we've hit an error */
+#define TEF_DBRACKET BIT(1) /* set if [[ .. ]] test */
+
+typedef struct test_env Test_env;
+struct test_env {
+ int flags; /* TEF_* */
+ union {
+ char **wp; /* used by ptest_* */
+ XPtrV *av; /* used by dbtestp_* */
+ } pos;
+ char **wp_end; /* used by ptest_* */
+ int (*isa)(Test_env *, Test_meta);
+ const char *(*getopnd) (Test_env *, Test_op, int);
+ int (*eval)(Test_env *, Test_op, const char *, const char *, int);
+ void (*error)(Test_env *, int, const char *);
+};
+
+Test_op test_isop(Test_env *, Test_meta, const char *);
+int test_eval(Test_env *, Test_op, const char *, const char *, int);
+int test_parse(Test_env *);
diff --git a/c_ulimit.c b/c_ulimit.c
@@ -0,0 +1,201 @@
+/* $OpenBSD: c_ulimit.c,v 1.19 2013/11/28 10:33:37 sobrado Exp $ */
+
+/*
+ ulimit -- handle "ulimit" builtin
+
+ Reworked to use getrusage() and ulimit() at once (as needed on
+ some schizophrenic systems, eg, HP-UX 9.01), made argument parsing
+ conform to at&t ksh, added autoconf support. Michael Rendell, May, '94
+
+ Eric Gisin, September 1988
+ Adapted to PD KornShell. Removed AT&T code.
+
+ last edit: 06-Jun-1987 D A Gwyn
+
+ This started out as the BRL UNIX System V system call emulation
+ for 4.nBSD, and was later extended by Doug Kingston to handle
+ the extended 4.nBSD resource limits. It now includes the code
+ that was originally under case SYSULIMIT in source file "xec.c".
+*/
+
+#include "sh.h"
+#include <sys/resource.h>
+
+#define SOFT 0x1
+#define HARD 0x2
+
+struct limits {
+ const char *name;
+ int resource; /* resource to get/set */
+ int factor; /* multiply by to get rlim_{cur,max} values */
+ char option; /* option character (-d, -f, ...) */
+};
+
+static void print_ulimit(const struct limits *, int);
+static int set_ulimit(const struct limits *, const char *, int);
+
+int
+c_ulimit(char **wp)
+{
+ static const struct limits limits[] = {
+ /* Do not use options -H, -S or -a or change the order. */
+ { "time(cpu-seconds)", RLIMIT_CPU, 1, 't' },
+ { "file(blocks)", RLIMIT_FSIZE, 512, 'f' },
+ { "coredump(blocks)", RLIMIT_CORE, 512, 'c' },
+ { "data(kbytes)", RLIMIT_DATA, 1024, 'd' },
+ { "stack(kbytes)", RLIMIT_STACK, 1024, 's' },
+ { "lockedmem(kbytes)", RLIMIT_MEMLOCK, 1024, 'l' },
+ { "memory(kbytes)", RLIMIT_RSS, 1024, 'm' },
+ { "nofiles(descriptors)", RLIMIT_NOFILE, 1, 'n' },
+ { "processes", RLIMIT_NPROC, 1, 'p' },
+#ifdef RLIMIT_VMEM
+ { "vmemory(kbytes)", RLIMIT_VMEM, 1024, 'v' },
+#endif /* RLIMIT_VMEM */
+ { (char *) 0 }
+ };
+ static char options[4 + NELEM(limits) * 2];
+ int how = SOFT | HARD;
+ const struct limits *l;
+ int optc, all = 0;
+
+ if (!options[0]) {
+ /* build options string on first call - yuck */
+ char *p = options;
+
+ *p++ = 'H'; *p++ = 'S'; *p++ = 'a';
+ for (l = limits; l->name; l++) {
+ *p++ = l->option;
+ *p++ = '#';
+ }
+ *p = '\0';
+ }
+ /* First check for -a, -H and -S. */
+ while ((optc = ksh_getopt(wp, &builtin_opt, options)) != -1)
+ switch (optc) {
+ case 'H':
+ how = HARD;
+ break;
+ case 'S':
+ how = SOFT;
+ break;
+ case 'a':
+ all = 1;
+ break;
+ case '?':
+ return 1;
+ default:
+ break;
+ }
+
+ if (wp[builtin_opt.optind] != NULL) {
+ bi_errorf("usage: ulimit [-acdfHlmnpSst] [value]");
+ return 1;
+ }
+
+ /* Then parse and act on the actual limits, one at a time */
+ ksh_getopt_reset(&builtin_opt, GF_ERROR);
+ while ((optc = ksh_getopt(wp, &builtin_opt, options)) != -1)
+ switch (optc) {
+ case 'a':
+ case 'H':
+ case 'S':
+ break;
+ case '?':
+ return 1;
+ default:
+ for (l = limits; l->name && l->option != optc; l++)
+ ;
+ if (!l->name) {
+ internal_errorf(0, "ulimit: %c", optc);
+ return 1;
+ }
+ if (builtin_opt.optarg) {
+ if (set_ulimit(l, builtin_opt.optarg, how))
+ return 1;
+ } else
+ print_ulimit(l, how);
+ break;
+ }
+
+ wp += builtin_opt.optind;
+
+ if (all) {
+ for (l = limits; l->name; l++) {
+ shprintf("%-20s ", l->name);
+ print_ulimit(l, how);
+ }
+ } else if (builtin_opt.optind == 1) {
+ /* No limit specified, use file size */
+ l = &limits[1];
+ if (wp[0] != NULL) {
+ if (set_ulimit(l, wp[0], how))
+ return 1;
+ wp++;
+ } else {
+ print_ulimit(l, how);
+ }
+ }
+
+ return 0;
+}
+
+static int
+set_ulimit(const struct limits *l, const char *v, int how)
+{
+ rlim_t val = 0;
+ struct rlimit limit;
+
+ if (strcmp(v, "unlimited") == 0)
+ val = RLIM_INFINITY;
+ else {
+ long rval;
+
+ if (!evaluate(v, &rval, KSH_RETURN_ERROR, false))
+ return 1;
+ /*
+ * Avoid problems caused by typos that evaluate misses due
+ * to evaluating unset parameters to 0...
+ * If this causes problems, will have to add parameter to
+ * evaluate() to control if unset params are 0 or an error.
+ */
+ if (!rval && !digit(v[0])) {
+ bi_errorf("invalid limit: %s", v);
+ return 1;
+ }
+ val = (rlim_t)rval * l->factor;
+ }
+
+ getrlimit(l->resource, &limit);
+ if (how & SOFT)
+ limit.rlim_cur = val;
+ if (how & HARD)
+ limit.rlim_max = val;
+ if (setrlimit(l->resource, &limit) < 0) {
+ if (errno == EPERM)
+ bi_errorf("-%c exceeds allowable limit", l->option);
+ else
+ bi_errorf("bad -%c limit: %s", l->option,
+ strerror(errno));
+ return 1;
+ }
+ return 0;
+}
+
+static void
+print_ulimit(const struct limits *l, int how)
+{
+ rlim_t val = 0;
+ struct rlimit limit;
+
+ getrlimit(l->resource, &limit);
+ if (how & SOFT)
+ val = limit.rlim_cur;
+ else if (how & HARD)
+ val = limit.rlim_max;
+ if (val == RLIM_INFINITY)
+ shprintf("unlimited\n");
+ else {
+ val /= l->factor;
+ shprintf("%ld\n", (long) val);
+ }
+}
diff --git a/charclass.h b/charclass.h
@@ -0,0 +1,29 @@
+/*
+ * Public domain, 2008, Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * $OpenBSD: charclass.h,v 1.1 2008/10/01 23:04:13 millert Exp $
+ */
+
+/*
+ * POSIX character class support for fnmatch() and glob().
+ */
+static struct cclass {
+ const char *name;
+ int (*isctype)(int);
+} cclasses[] = {
+ { "alnum", isalnum },
+ { "alpha", isalpha },
+ { "blank", isblank },
+ { "cntrl", iscntrl },
+ { "digit", isdigit },
+ { "graph", isgraph },
+ { "lower", islower },
+ { "print", isprint },
+ { "punct", ispunct },
+ { "space", isspace },
+ { "upper", isupper },
+ { "xdigit", isxdigit },
+ { NULL, NULL }
+};
+
+#define NCCLASSES (sizeof(cclasses) / sizeof(cclasses[0]) - 1)
diff --git a/config.h b/config.h
@@ -0,0 +1,60 @@
+/* $OpenBSD: config.h,v 1.14 2011/03/14 21:20:00 okan Exp $ */
+
+/* config.h. NOT generated automatically. */
+
+/*
+ * This file, config.h, which is a part of pdksh (the public domain ksh),
+ * is placed in the public domain. It comes with no licence, warranty
+ * or guarantee of any kind (i.e., at your own risk).
+ */
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+/* Include emacs editing? */
+#define EMACS 1
+
+/* Include vi editing? */
+#define VI 1
+
+/* Include job control? */
+#define JOBS 1
+
+/* Include brace-expansion? */
+#define BRACE_EXPAND 1
+
+/* Include any history? */
+#define HISTORY 1
+
+/* Strict POSIX behaviour? */
+/* #undef POSIXLY_CORRECT */
+
+/* Specify default $ENV? */
+/* #undef DEFAULT_ENV */
+
+/* The number of bytes in a int. */
+#define SIZEOF_INT 4
+
+/*
+ * End of configuration stuff for PD ksh.
+ */
+
+#if defined(EMACS) || defined(VI)
+# define EDIT
+#else
+# undef EDIT
+#endif
+
+/* Super small configuration-- no editing. */
+#if defined(EDIT) && defined(NOEDIT)
+# undef EDIT
+# undef EMACS
+# undef VI
+#endif
+
+/* Editing implies history */
+#if defined(EDIT) && !defined(HISTORY)
+# define HISTORY
+#endif /* EDIT */
+
+#endif /* CONFIG_H */
diff --git a/edit.c b/edit.c
@@ -0,0 +1,829 @@
+/* $OpenBSD: edit.c,v 1.39 2013/12/17 16:37:05 deraadt Exp $ */
+
+/*
+ * Command line editing - common code
+ *
+ */
+
+#include "config.h"
+#ifdef EDIT
+
+#include "sh.h"
+#include "tty.h"
+#define EXTERN
+#include "edit.h"
+#undef EXTERN
+#include <sys/ioctl.h>
+#include <ctype.h>
+#include <libgen.h>
+#include <sys/stat.h>
+
+
+static void x_sigwinch(int);
+volatile sig_atomic_t got_sigwinch;
+static void check_sigwinch(void);
+
+static int x_file_glob(int, const char *, int, char ***);
+static int x_command_glob(int, const char *, int, char ***);
+static int x_locate_word(const char *, int, int, int *, int *);
+
+
+/* Called from main */
+void
+x_init(void)
+{
+ /* set to -2 to force initial binding */
+ edchars.erase = edchars.kill = edchars.intr = edchars.quit =
+ edchars.eof = -2;
+ /* default value for deficient systems */
+ edchars.werase = 027; /* ^W */
+
+ if (setsig(&sigtraps[SIGWINCH], x_sigwinch, SS_RESTORE_ORIG|SS_SHTRAP))
+ sigtraps[SIGWINCH].flags |= TF_SHELL_USES;
+ got_sigwinch = 1; /* force initial check */
+ check_sigwinch();
+
+#ifdef EMACS
+ x_init_emacs();
+#endif /* EMACS */
+}
+
+/* ARGSUSED */
+static void
+x_sigwinch(int sig)
+{
+ got_sigwinch = 1;
+}
+
+static void
+check_sigwinch(void)
+{
+ if (got_sigwinch) {
+ struct winsize ws;
+
+ got_sigwinch = 0;
+ if (procpid == kshpid && ioctl(tty_fd, TIOCGWINSZ, &ws) >= 0) {
+ struct tbl *vp;
+
+ /* Do NOT export COLUMNS/LINES. Many applications
+ * check COLUMNS/LINES before checking ws.ws_col/row,
+ * so if the app is started with C/L in the environ
+ * and the window is then resized, the app won't
+ * see the change cause the environ doesn't change.
+ */
+ if (ws.ws_col) {
+ x_cols = ws.ws_col < MIN_COLS ? MIN_COLS :
+ ws.ws_col;
+
+ if ((vp = typeset("COLUMNS", 0, 0, 0, 0)))
+ setint(vp, (long) ws.ws_col);
+ }
+ if (ws.ws_row && (vp = typeset("LINES", 0, 0, 0, 0)))
+ setint(vp, (long) ws.ws_row);
+ }
+ }
+}
+
+/*
+ * read an edited command line
+ */
+int
+x_read(char *buf, size_t len)
+{
+ int i;
+
+ x_mode(true);
+#ifdef EMACS
+ if (Flag(FEMACS) || Flag(FGMACS))
+ i = x_emacs(buf, len);
+ else
+#endif
+#ifdef VI
+ if (Flag(FVI))
+ i = x_vi(buf, len);
+ else
+#endif
+ i = -1; /* internal error */
+ x_mode(false);
+ check_sigwinch();
+ return i;
+}
+
+/* tty I/O */
+
+int
+x_getc(void)
+{
+ char c;
+ int n;
+
+ while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR)
+ if (trap) {
+ x_mode(false);
+ runtraps(0);
+ x_mode(true);
+ }
+ if (n != 1)
+ return -1;
+ return (int) (unsigned char) c;
+}
+
+void
+x_flush(void)
+{
+ shf_flush(shl_out);
+}
+
+void
+x_putc(int c)
+{
+ shf_putc(c, shl_out);
+}
+
+void
+x_puts(const char *s)
+{
+ while (*s != 0)
+ shf_putc(*s++, shl_out);
+}
+
+bool
+x_mode(bool onoff)
+{
+ static bool x_cur_mode;
+ bool prev;
+
+ if (x_cur_mode == onoff)
+ return x_cur_mode;
+ prev = x_cur_mode;
+ x_cur_mode = onoff;
+
+ if (onoff) {
+ struct termios cb;
+ X_chars oldchars;
+
+ oldchars = edchars;
+ cb = tty_state;
+
+ edchars.erase = cb.c_cc[VERASE];
+ edchars.kill = cb.c_cc[VKILL];
+ edchars.intr = cb.c_cc[VINTR];
+ edchars.quit = cb.c_cc[VQUIT];
+ edchars.eof = cb.c_cc[VEOF];
+ edchars.werase = cb.c_cc[VWERASE];
+ cb.c_iflag &= ~(INLCR|ICRNL);
+ cb.c_lflag &= ~(ISIG|ICANON|ECHO);
+ /* osf/1 processes lnext when ~icanon */
+ cb.c_cc[VLNEXT] = _POSIX_VDISABLE;
+ /* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */
+ cb.c_cc[VDISCARD] = _POSIX_VDISABLE;
+ cb.c_cc[VTIME] = 0;
+ cb.c_cc[VMIN] = 1;
+
+ tcsetattr(tty_fd, TCSADRAIN, &cb);
+
+ /* Convert unset values to internal `unset' value */
+ if (edchars.erase == _POSIX_VDISABLE)
+ edchars.erase = -1;
+ if (edchars.kill == _POSIX_VDISABLE)
+ edchars.kill = -1;
+ if (edchars.intr == _POSIX_VDISABLE)
+ edchars.intr = -1;
+ if (edchars.quit == _POSIX_VDISABLE)
+ edchars.quit = -1;
+ if (edchars.eof == _POSIX_VDISABLE)
+ edchars.eof = -1;
+ if (edchars.werase == _POSIX_VDISABLE)
+ edchars.werase = -1;
+ if (memcmp(&edchars, &oldchars, sizeof(edchars)) != 0) {
+#ifdef EMACS
+ x_emacs_keys(&edchars);
+#endif
+ }
+ } else {
+ tcsetattr(tty_fd, TCSADRAIN, &tty_state);
+ }
+
+ return prev;
+}
+
+void
+set_editmode(const char *ed)
+{
+ static const enum sh_flag edit_flags[] = {
+#ifdef EMACS
+ FEMACS, FGMACS,
+#endif
+#ifdef VI
+ FVI,
+#endif
+ };
+ char *rcp;
+ int i;
+
+ if ((rcp = strrchr(ed, '/')))
+ ed = ++rcp;
+ for (i = 0; i < NELEM(edit_flags); i++)
+ if (strstr(ed, options[(int) edit_flags[i]].name)) {
+ change_flag(edit_flags[i], OF_SPECIAL, 1);
+ return;
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+/* Misc common code for vi/emacs */
+
+/* Handle the commenting/uncommenting of a line.
+ * Returns:
+ * 1 if a carriage return is indicated (comment added)
+ * 0 if no return (comment removed)
+ * -1 if there is an error (not enough room for comment chars)
+ * If successful, *lenp contains the new length. Note: cursor should be
+ * moved to the start of the line after (un)commenting.
+ */
+int
+x_do_comment(char *buf, int bsize, int *lenp)
+{
+ int i, j;
+ int len = *lenp;
+
+ if (len == 0)
+ return 1; /* somewhat arbitrary - it's what at&t ksh does */
+
+ /* Already commented? */
+ if (buf[0] == '#') {
+ int saw_nl = 0;
+
+ for (j = 0, i = 1; i < len; i++) {
+ if (!saw_nl || buf[i] != '#')
+ buf[j++] = buf[i];
+ saw_nl = buf[i] == '\n';
+ }
+ *lenp = j;
+ return 0;
+ } else {
+ int n = 1;
+
+ /* See if there's room for the #'s - 1 per \n */
+ for (i = 0; i < len; i++)
+ if (buf[i] == '\n')
+ n++;
+ if (len + n >= bsize)
+ return -1;
+ /* Now add them... */
+ for (i = len, j = len + n; --i >= 0; ) {
+ if (buf[i] == '\n')
+ buf[--j] = '#';
+ buf[--j] = buf[i];
+ }
+ buf[0] = '#';
+ *lenp += n;
+ return 1;
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+/* Common file/command completion code for vi/emacs */
+
+
+static char *add_glob(const char *str, int slen);
+static void glob_table(const char *pat, XPtrV *wp, struct table *tp);
+static void glob_path(int flags, const char *pat, XPtrV *wp,
+ const char *path);
+
+void
+x_print_expansions(int nwords, char *const *words, int is_command)
+{
+ int use_copy = 0;
+ int prefix_len;
+ XPtrV l;
+
+ /* Check if all matches are in the same directory (in this
+ * case, we want to omit the directory name)
+ */
+ if (!is_command &&
+ (prefix_len = x_longest_prefix(nwords, words)) > 0) {
+ int i;
+
+ /* Special case for 1 match (prefix is whole word) */
+ if (nwords == 1)
+ prefix_len = x_basename(words[0], (char *) 0);
+ /* Any (non-trailing) slashes in non-common word suffixes? */
+ for (i = 0; i < nwords; i++)
+ if (x_basename(words[i] + prefix_len, (char *) 0) >
+ prefix_len)
+ break;
+ /* All in same directory? */
+ if (i == nwords) {
+ while (prefix_len > 0 && words[0][prefix_len - 1] != '/')
+ prefix_len--;
+ use_copy = 1;
+ XPinit(l, nwords + 1);
+ for (i = 0; i < nwords; i++)
+ XPput(l, words[i] + prefix_len);
+ XPput(l, (char *) 0);
+ }
+ }
+
+ /*
+ * Enumerate expansions
+ */
+ x_putc('\r');
+ x_putc('\n');
+ pr_list(use_copy ? (char **) XPptrv(l) : words);
+
+ if (use_copy)
+ XPfree(l); /* not x_free_words() */
+}
+
+/*
+ * Do file globbing:
+ * - appends * to (copy of) str if no globbing chars found
+ * - does expansion, checks for no match, etc.
+ * - sets *wordsp to array of matching strings
+ * - returns number of matching strings
+ */
+static int
+x_file_glob(int flags, const char *str, int slen, char ***wordsp)
+{
+ char *toglob;
+ char **words;
+ int nwords;
+ XPtrV w;
+ struct source *s, *sold;
+
+ if (slen < 0)
+ return 0;
+
+ toglob = add_glob(str, slen);
+
+ /*
+ * Convert "foo*" (toglob) to an array of strings (words)
+ */
+ sold = source;
+ s = pushs(SWSTR, ATEMP);
+ s->start = s->str = toglob;
+ source = s;
+ if (yylex(ONEWORD|UNESCAPE) != LWORD) {
+ source = sold;
+ internal_errorf(0, "fileglob: substitute error");
+ return 0;
+ }
+ source = sold;
+ XPinit(w, 32);
+ expand(yylval.cp, &w, DOGLOB|DOTILDE|DOMARKDIRS);
+ XPput(w, NULL);
+ words = (char **) XPclose(w);
+
+ for (nwords = 0; words[nwords]; nwords++)
+ ;
+ if (nwords == 1) {
+ struct stat statb;
+
+ /* Check if file exists, also, check for empty
+ * result - happens if we tried to glob something
+ * which evaluated to an empty string (e.g.,
+ * "$FOO" when there is no FOO, etc).
+ */
+ if ((lstat(words[0], &statb) < 0) ||
+ words[0][0] == '\0') {
+ x_free_words(nwords, words);
+ words = NULL;
+ nwords = 0;
+ }
+ }
+ afree(toglob, ATEMP);
+
+ if (nwords) {
+ *wordsp = words;
+ } else if (words) {
+ x_free_words(nwords, words);
+ *wordsp = NULL;
+ }
+
+ return nwords;
+}
+
+/* Data structure used in x_command_glob() */
+struct path_order_info {
+ char *word;
+ int base;
+ int path_order;
+};
+
+static int path_order_cmp(const void *aa, const void *bb);
+
+/* Compare routine used in x_command_glob() */
+static int
+path_order_cmp(const void *aa, const void *bb)
+{
+ const struct path_order_info *a = (const struct path_order_info *) aa;
+ const struct path_order_info *b = (const struct path_order_info *) bb;
+ int t;
+
+ t = strcmp(a->word + a->base, b->word + b->base);
+ return t ? t : a->path_order - b->path_order;
+}
+
+static int
+x_command_glob(int flags, const char *str, int slen, char ***wordsp)
+{
+ char *toglob;
+ char *pat;
+ char *fpath;
+ int nwords;
+ XPtrV w;
+ struct block *l;
+
+ if (slen < 0)
+ return 0;
+
+ toglob = add_glob(str, slen);
+
+ /* Convert "foo*" (toglob) to a pattern for future use */
+ pat = evalstr(toglob, DOPAT|DOTILDE);
+ afree(toglob, ATEMP);
+
+ XPinit(w, 32);
+
+ glob_table(pat, &w, &keywords);
+ glob_table(pat, &w, &aliases);
+ glob_table(pat, &w, &builtins);
+ for (l = e->loc; l; l = l->next)
+ glob_table(pat, &w, &l->funs);
+
+ glob_path(flags, pat, &w, path);
+ if ((fpath = str_val(global("FPATH"))) != null)
+ glob_path(flags, pat, &w, fpath);
+
+ nwords = XPsize(w);
+
+ if (!nwords) {
+ *wordsp = (char **) 0;
+ XPfree(w);
+ return 0;
+ }
+
+ /* Sort entries */
+ if (flags & XCF_FULLPATH) {
+ /* Sort by basename, then path order */
+ struct path_order_info *info;
+ struct path_order_info *last_info = 0;
+ char **words = (char **) XPptrv(w);
+ int path_order = 0;
+ int i;
+
+ info = (struct path_order_info *)
+ alloc(sizeof(struct path_order_info) * nwords, ATEMP);
+ for (i = 0; i < nwords; i++) {
+ info[i].word = words[i];
+ info[i].base = x_basename(words[i], (char *) 0);
+ if (!last_info || info[i].base != last_info->base ||
+ strncmp(words[i], last_info->word, info[i].base) != 0) {
+ last_info = &info[i];
+ path_order++;
+ }
+ info[i].path_order = path_order;
+ }
+ qsort(info, nwords, sizeof(struct path_order_info),
+ path_order_cmp);
+ for (i = 0; i < nwords; i++)
+ words[i] = info[i].word;
+ afree((void *) info, ATEMP);
+ } else {
+ /* Sort and remove duplicate entries */
+ char **words = (char **) XPptrv(w);
+ int i, j;
+
+ qsortp(XPptrv(w), (size_t) nwords, xstrcmp);
+
+ for (i = j = 0; i < nwords - 1; i++) {
+ if (strcmp(words[i], words[i + 1]))
+ words[j++] = words[i];
+ else
+ afree(words[i], ATEMP);
+ }
+ words[j++] = words[i];
+ nwords = j;
+ w.cur = (void **) &words[j];
+ }
+
+ XPput(w, NULL);
+ *wordsp = (char **) XPclose(w);
+
+ return nwords;
+}
+
+#define IS_WORDC(c) !( ctype(c, C_LEX1) || (c) == '\'' || (c) == '"' || \
+ (c) == '`' || (c) == '=' || (c) == ':' )
+
+static int
+x_locate_word(const char *buf, int buflen, int pos, int *startp,
+ int *is_commandp)
+{
+ int p;
+ int start, end;
+
+ /* Bad call? Probably should report error */
+ if (pos < 0 || pos > buflen) {
+ *startp = pos;
+ *is_commandp = 0;
+ return 0;
+ }
+ /* The case where pos == buflen happens to take care of itself... */
+
+ start = pos;
+ /* Keep going backwards to start of word (has effect of allowing
+ * one blank after the end of a word)
+ */
+ for (; (start > 0 && IS_WORDC(buf[start - 1])) ||
+ (start > 1 && buf[start-2] == '\\'); start--)
+ ;
+ /* Go forwards to end of word */
+ for (end = start; end < buflen && IS_WORDC(buf[end]); end++) {
+ if (buf[end] == '\\' && (end+1) < buflen)
+ end++;
+ }
+
+ if (is_commandp) {
+ int iscmd;
+
+ /* Figure out if this is a command */
+ for (p = start - 1; p >= 0 && isspace((unsigned char)buf[p]);
+ p--)
+ ;
+ iscmd = p < 0 || strchr(";|&()`", buf[p]);
+ if (iscmd) {
+ /* If command has a /, path, etc. is not searched;
+ * only current directory is searched, which is just
+ * like file globbing.
+ */
+ for (p = start; p < end; p++)
+ if (buf[p] == '/')
+ break;
+ iscmd = p == end;
+ }
+ *is_commandp = iscmd;
+ }
+
+ *startp = start;
+
+ return end - start;
+}
+
+int
+x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp,
+ int *endp, char ***wordsp, int *is_commandp)
+{
+ int len;
+ int nwords;
+ char **words;
+ int is_command;
+
+ len = x_locate_word(buf, buflen, pos, startp, &is_command);
+ if (!(flags & XCF_COMMAND))
+ is_command = 0;
+ /* Don't do command globing on zero length strings - it takes too
+ * long and isn't very useful. File globs are more likely to be
+ * useful, so allow these.
+ */
+ if (len == 0 && is_command)
+ return 0;
+
+ nwords = (is_command ? x_command_glob : x_file_glob)(flags,
+ buf + *startp, len, &words);
+ if (nwords == 0) {
+ *wordsp = (char **) 0;
+ return 0;
+ }
+
+ if (is_commandp)
+ *is_commandp = is_command;
+ *wordsp = words;
+ *endp = *startp + len;
+
+ return nwords;
+}
+
+/* Given a string, copy it and possibly add a '*' to the end. The
+ * new string is returned.
+ */
+static char *
+add_glob(const char *str, int slen)
+{
+ char *toglob;
+ char *s;
+ bool saw_slash = false;
+
+ if (slen < 0)
+ return (char *) 0;
+
+ toglob = str_nsave(str, slen + 1, ATEMP); /* + 1 for "*" */
+ toglob[slen] = '\0';
+
+ /*
+ * If the pathname contains a wildcard (an unquoted '*',
+ * '?', or '[') or parameter expansion ('$'), or a ~username
+ * with no trailing slash, then it is globbed based on that
+ * value (i.e., without the appended '*').
+ */
+ for (s = toglob; *s; s++) {
+ if (*s == '\\' && s[1])
+ s++;
+ else if (*s == '*' || *s == '[' || *s == '?' || *s == '$' ||
+ (s[1] == '(' /*)*/ && strchr("+@!", *s)))
+ break;
+ else if (*s == '/')
+ saw_slash = true;
+ }
+ if (!*s && (*toglob != '~' || saw_slash)) {
+ toglob[slen] = '*';
+ toglob[slen + 1] = '\0';
+ }
+
+ return toglob;
+}
+
+/*
+ * Find longest common prefix
+ */
+int
+x_longest_prefix(int nwords, char *const *words)
+{
+ int i, j;
+ int prefix_len;
+ char *p;
+
+ if (nwords <= 0)
+ return 0;
+
+ prefix_len = strlen(words[0]);
+ for (i = 1; i < nwords; i++)
+ for (j = 0, p = words[i]; j < prefix_len; j++)
+ if (p[j] != words[0][j]) {
+ prefix_len = j;
+ break;
+ }
+ return prefix_len;
+}
+
+void
+x_free_words(int nwords, char **words)
+{
+ int i;
+
+ for (i = 0; i < nwords; i++)
+ if (words[i])
+ afree(words[i], ATEMP);
+ afree(words, ATEMP);
+}
+
+/* Return the offset of the basename of string s (which ends at se - need not
+ * be null terminated). Trailing slashes are ignored. If s is just a slash,
+ * then the offset is 0 (actually, length - 1).
+ * s Return
+ * /etc 1
+ * /etc/ 1
+ * /etc// 1
+ * /etc/fo 5
+ * foo 0
+ * /// 2
+ * 0
+ */
+int
+x_basename(const char *s, const char *se)
+{
+ const char *p;
+
+ if (se == (char *) 0)
+ se = s + strlen(s);
+ if (s == se)
+ return 0;
+
+ /* Skip trailing slashes */
+ for (p = se - 1; p > s && *p == '/'; p--)
+ ;
+ for (; p > s && *p != '/'; p--)
+ ;
+ if (*p == '/' && p + 1 < se)
+ p++;
+
+ return p - s;
+}
+
+/*
+ * Apply pattern matching to a table: all table entries that match a pattern
+ * are added to wp.
+ */
+static void
+glob_table(const char *pat, XPtrV *wp, struct table *tp)
+{
+ struct tstate ts;
+ struct tbl *te;
+
+ for (ktwalk(&ts, tp); (te = ktnext(&ts)); ) {
+ if (gmatch(te->name, pat, false))
+ XPput(*wp, str_save(te->name, ATEMP));
+ }
+}
+
+static void
+glob_path(int flags, const char *pat, XPtrV *wp, const char *path)
+{
+ const char *sp, *p;
+ char *xp;
+ int staterr;
+ int pathlen;
+ int patlen;
+ int oldsize, newsize, i, j;
+ char **words;
+ XString xs;
+
+ patlen = strlen(pat) + 1;
+ sp = path;
+ Xinit(xs, xp, patlen + 128, ATEMP);
+ while (sp) {
+ xp = Xstring(xs, xp);
+ if (!(p = strchr(sp, ':')))
+ p = sp + strlen(sp);
+ pathlen = p - sp;
+ if (pathlen) {
+ /* Copy sp into xp, stuffing any MAGIC characters
+ * on the way
+ */
+ const char *s = sp;
+
+ XcheckN(xs, xp, pathlen * 2);
+ while (s < p) {
+ if (ISMAGIC(*s))
+ *xp++ = MAGIC;
+ *xp++ = *s++;
+ }
+ *xp++ = '/';
+ pathlen++;
+ }
+ sp = p;
+ XcheckN(xs, xp, patlen);
+ memcpy(xp, pat, patlen);
+
+ oldsize = XPsize(*wp);
+ glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */
+ newsize = XPsize(*wp);
+
+ /* Check that each match is executable... */
+ words = (char **) XPptrv(*wp);
+ for (i = j = oldsize; i < newsize; i++) {
+ staterr = 0;
+ if ((search_access(words[i], X_OK, &staterr) >= 0) ||
+ (staterr == EISDIR)) {
+ words[j] = words[i];
+ if (!(flags & XCF_FULLPATH))
+ memmove(words[j], words[j] + pathlen,
+ strlen(words[j] + pathlen) + 1);
+ j++;
+ } else
+ afree(words[i], ATEMP);
+ }
+ wp->cur = (void **) &words[j];
+
+ if (!*sp++)
+ break;
+ }
+ Xfree(xs, xp);
+}
+
+/*
+ * if argument string contains any special characters, they will
+ * be escaped and the result will be put into edit buffer by
+ * keybinding-specific function
+ */
+int
+x_escape(const char *s, size_t len, int (*putbuf_func) (const char *, size_t))
+{
+ size_t add, wlen;
+ const char *ifs = str_val(local("IFS", 0));
+ int rval = 0;
+
+ for (add = 0, wlen = len; wlen - add > 0; add++) {
+ if (strchr("\"#$&'()*:;<=>?[\\]`{|}", s[add]) ||
+ strchr(ifs, s[add])) {
+ if (putbuf_func(s, add) != 0) {
+ rval = -1;
+ break;
+ }
+
+ putbuf_func("\\", 1);
+ putbuf_func(&s[add], 1);
+
+ add++;
+ wlen -= add;
+ s += add;
+ add = -1; /* after the increment it will go to 0 */
+ }
+ }
+ if (wlen > 0 && rval == 0)
+ rval = putbuf_func(s, wlen);
+
+ return (rval);
+}
+#endif /* EDIT */
diff --git a/edit.h b/edit.h
@@ -0,0 +1,86 @@
+/* $OpenBSD: edit.h,v 1.9 2011/05/30 17:14:35 martynas Exp $ */
+
+/* NAME:
+ * edit.h - globals for edit modes
+ *
+ * DESCRIPTION:
+ * This header defines various global edit objects.
+ *
+ * SEE ALSO:
+ *
+ *
+ * RCSid:
+ * $From: edit.h,v 1.2 1994/05/19 18:32:40 michael Exp michael $
+ *
+ */
+
+/* some useful #defines */
+#ifdef EXTERN
+# define I__(i) = i
+#else
+# define I__(i)
+# define EXTERN extern
+# define EXTERN_DEFINED
+#endif
+
+#define BEL 0x07
+
+/* tty driver characters we are interested in */
+typedef struct {
+ int erase;
+ int kill;
+ int werase;
+ int intr;
+ int quit;
+ int eof;
+} X_chars;
+
+EXTERN X_chars edchars;
+
+/* x_cf_glob() flags */
+#define XCF_COMMAND BIT(0) /* Do command completion */
+#define XCF_FILE BIT(1) /* Do file completion */
+#define XCF_FULLPATH BIT(2) /* command completion: store full path */
+#define XCF_COMMAND_FILE (XCF_COMMAND|XCF_FILE)
+
+/* edit.c */
+int x_getc(void);
+void x_flush(void);
+void x_putc(int);
+void x_puts(const char *);
+bool x_mode(bool);
+int promptlen(const char *, const char **);
+int x_do_comment(char *, int, int *);
+void x_print_expansions(int, char *const *, int);
+int x_cf_glob(int, const char *, int, int, int *, int *, char ***, int *);
+int x_longest_prefix(int , char *const *);
+int x_basename(const char *, const char *);
+void x_free_words(int, char **);
+int x_escape(const char *, size_t, int (*)(const char *, size_t));
+/* emacs.c */
+int x_emacs(char *, size_t);
+void x_init_emacs(void);
+void x_emacs_keys(X_chars *);
+/* vi.c */
+int x_vi(char *, size_t);
+
+
+#ifdef DEBUG
+# define D__(x) x
+#else
+# define D__(x)
+#endif
+
+/* This lot goes at the END */
+/* be sure not to interfere with anyone else's idea about EXTERN */
+#ifdef EXTERN_DEFINED
+# undef EXTERN_DEFINED
+# undef EXTERN
+#endif
+#undef I__
+/*
+ * Local Variables:
+ * version-control:t
+ * comment-column:40
+ * End:
+ */
diff --git a/emacs.c b/emacs.c
@@ -0,0 +1,2163 @@
+/* $OpenBSD: emacs.c,v 1.48 2013/12/17 16:37:05 deraadt Exp $ */
+
+/*
+ * Emacs-like command line editing and history
+ *
+ * created by Ron Natalie at BRL
+ * modified by Doug Kingston, Doug Gwyn, and Lou Salkind
+ * adapted to PD ksh by Eric Gisin
+ *
+ * partial rewrite by Marco Peereboom <marco@openbsd.org>
+ * under the same license
+ */
+
+#include "config.h"
+#ifdef EMACS
+
+#include "sh.h"
+#include <sys/stat.h>
+#include <sys/queue.h>
+#include <ctype.h>
+#include <locale.h>
+#include "edit.h"
+
+static Area aedit;
+#define AEDIT &aedit /* area for kill ring and macro defns */
+
+#define CTRL(x) ((x) == '?' ? 0x7F : (x) & 0x1F) /* ASCII */
+#define UNCTRL(x) ((x) == 0x7F ? '?' : (x) | 0x40) /* ASCII */
+#define META(x) ((x) & 0x7f)
+#define ISMETA(x) (Flag(FEMACSUSEMETA) && ((x) & 0x80))
+
+
+/* values returned by keyboard functions */
+#define KSTD 0
+#define KEOL 1 /* ^M, ^J */
+#define KINTR 2 /* ^G, ^C */
+
+struct x_ftab {
+ int (*xf_func)(int c);
+ const char *xf_name;
+ short xf_flags;
+};
+
+#define XF_ARG 1 /* command takes number prefix */
+#define XF_NOBIND 2 /* not allowed to bind to function */
+#define XF_PREFIX 4 /* function sets prefix */
+
+/* Separator for completion */
+#define is_cfs(c) (c == ' ' || c == '\t' || c == '"' || c == '\'')
+
+/* Separator for motion */
+#define is_mfs(c) (!(isalnum((unsigned char)c) || c == '_' || c == '$'))
+
+/* Arguments for do_complete()
+ * 0 = enumerate M-= complete as much as possible and then list
+ * 1 = complete M-Esc
+ * 2 = list M-?
+ */
+typedef enum {
+ CT_LIST, /* list the possible completions */
+ CT_COMPLETE, /* complete to longest prefix */
+ CT_COMPLIST /* complete and then list (if non-exact) */
+} Comp_type;
+
+/* keybindings */
+struct kb_entry {
+ TAILQ_ENTRY(kb_entry) entry;
+ unsigned char *seq;
+ int len;
+ struct x_ftab *ftab;
+ void *args;
+};
+TAILQ_HEAD(kb_list, kb_entry);
+struct kb_list kblist = TAILQ_HEAD_INITIALIZER(kblist);
+
+/* { from 4.9 edit.h */
+/*
+ * The following are used for my horizontal scrolling stuff
+ */
+static char *xbuf; /* beg input buffer */
+static char *xend; /* end input buffer */
+static char *xcp; /* current position */
+static char *xep; /* current end */
+static char *xbp; /* start of visible portion of input buffer */
+static char *xlp; /* last char visible on screen */
+static int x_adj_ok;
+/*
+ * we use x_adj_done so that functions can tell
+ * whether x_adjust() has been called while they are active.
+ */
+static int x_adj_done;
+
+static int xx_cols;
+static int x_col;
+static int x_displen;
+static int x_arg; /* general purpose arg */
+static int x_arg_defaulted;/* x_arg not explicitly set; defaulted to 1 */
+
+static int xlp_valid;
+/* end from 4.9 edit.h } */
+static int x_tty; /* are we on a tty? */
+static int x_bind_quiet; /* be quiet when binding keys */
+static int (*x_last_command)(int);
+
+static char **x_histp; /* history position */
+static int x_nextcmd; /* for newline-and-next */
+static char *xmp; /* mark pointer */
+#define KILLSIZE 20
+static char *killstack[KILLSIZE];
+static int killsp, killtp;
+static int x_literal_set;
+static int x_arg_set;
+static char *macro_args;
+static int prompt_skip;
+static int prompt_redraw;
+
+static int x_ins(char *);
+static void x_delete(int, int);
+static int x_bword(void);
+static int x_fword(void);
+static void x_goto(char *);
+static void x_bs(int);
+static int x_size_str(char *);
+static int x_size(int);
+static void x_zots(char *);
+static void x_zotc(int);
+static void x_load_hist(char **);
+static int x_search(char *, int, int);
+static int x_match(char *, char *);
+static void x_redraw(int);
+static void x_push(int);
+static void x_adjust(void);
+static void x_e_ungetc(int);
+static int x_e_getc(void);
+static void x_e_putc(int);
+static void x_e_puts(const char *);
+static int x_comment(int);
+static int x_fold_case(int);
+static char *x_lastcp(void);
+static void do_complete(int, Comp_type);
+static int x_emacs_putbuf(const char *, size_t);
+
+/* proto's for keybindings */
+static int x_abort(int);
+static int x_beg_hist(int);
+static int x_comp_comm(int);
+static int x_comp_file(int);
+static int x_complete(int);
+static int x_del_back(int);
+static int x_del_bword(int);
+static int x_del_char(int);
+static int x_del_fword(int);
+static int x_del_line(int);
+static int x_draw_line(int);
+static int x_end_hist(int);
+static int x_end_of_text(int);
+static int x_enumerate(int);
+static int x_eot_del(int);
+static int x_error(int);
+static int x_goto_hist(int);
+static int x_ins_string(int);
+static int x_insert(int);
+static int x_kill(int);
+static int x_kill_region(int);
+static int x_list_comm(int);
+static int x_list_file(int);
+static int x_literal(int);
+static int x_meta_yank(int);
+static int x_mv_back(int);
+static int x_mv_begin(int);
+static int x_mv_bword(int);
+static int x_mv_end(int);
+static int x_mv_forw(int);
+static int x_mv_fword(int);
+static int x_newline(int);
+static int x_next_com(int);
+static int x_nl_next_com(int);
+static int x_noop(int);
+static int x_prev_com(int);
+static int x_prev_histword(int);
+static int x_search_char_forw(int);
+static int x_search_char_back(int);
+static int x_search_hist(int);
+static int x_set_mark(int);
+static int x_stuff(int);
+static int x_stuffreset(int);
+static int x_transpose(int);
+static int x_version(int);
+static int x_xchg_point_mark(int);
+static int x_yank(int);
+static int x_comp_list(int);
+static int x_expand(int);
+static int x_fold_capitalize(int);
+static int x_fold_lower(int);
+static int x_fold_upper(int);
+static int x_set_arg(int);
+static int x_comment(int);
+#ifdef DEBUG
+static int x_debug_info(int);
+#endif
+
+static const struct x_ftab x_ftab[] = {
+ { x_abort, "abort", 0 },
+ { x_beg_hist, "beginning-of-history", 0 },
+ { x_comp_comm, "complete-command", 0 },
+ { x_comp_file, "complete-file", 0 },
+ { x_complete, "complete", 0 },
+ { x_del_back, "delete-char-backward", XF_ARG },
+ { x_del_bword, "delete-word-backward", XF_ARG },
+ { x_del_char, "delete-char-forward", XF_ARG },
+ { x_del_fword, "delete-word-forward", XF_ARG },
+ { x_del_line, "kill-line", 0 },
+ { x_draw_line, "redraw", 0 },
+ { x_end_hist, "end-of-history", 0 },
+ { x_end_of_text, "eot", 0 },
+ { x_enumerate, "list", 0 },
+ { x_eot_del, "eot-or-delete", XF_ARG },
+ { x_error, "error", 0 },
+ { x_goto_hist, "goto-history", XF_ARG },
+ { x_ins_string, "macro-string", XF_NOBIND },
+ { x_insert, "auto-insert", XF_ARG },
+ { x_kill, "kill-to-eol", XF_ARG },
+ { x_kill_region, "kill-region", 0 },
+ { x_list_comm, "list-command", 0 },
+ { x_list_file, "list-file", 0 },
+ { x_literal, "quote", 0 },
+ { x_meta_yank, "yank-pop", 0 },
+ { x_mv_back, "backward-char", XF_ARG },
+ { x_mv_begin, "beginning-of-line", 0 },
+ { x_mv_bword, "backward-word", XF_ARG },
+ { x_mv_end, "end-of-line", 0 },
+ { x_mv_forw, "forward-char", XF_ARG },
+ { x_mv_fword, "forward-word", XF_ARG },
+ { x_newline, "newline", 0 },
+ { x_next_com, "down-history", XF_ARG },
+ { x_nl_next_com, "newline-and-next", 0 },
+ { x_noop, "no-op", 0 },
+ { x_prev_com, "up-history", XF_ARG },
+ { x_prev_histword, "prev-hist-word", XF_ARG },
+ { x_search_char_forw, "search-character-forward", XF_ARG },
+ { x_search_char_back, "search-character-backward", XF_ARG },
+ { x_search_hist, "search-history", 0 },
+ { x_set_mark, "set-mark-command", 0 },
+ { x_stuff, "stuff", 0 },
+ { x_stuffreset, "stuff-reset", 0 },
+ { x_transpose, "transpose-chars", 0 },
+ { x_version, "version", 0 },
+ { x_xchg_point_mark, "exchange-point-and-mark", 0 },
+ { x_yank, "yank", 0 },
+ { x_comp_list, "complete-list", 0 },
+ { x_expand, "expand-file", 0 },
+ { x_fold_capitalize, "capitalize-word", XF_ARG },
+ { x_fold_lower, "downcase-word", XF_ARG },
+ { x_fold_upper, "upcase-word", XF_ARG },
+ { x_set_arg, "set-arg", XF_NOBIND },
+ { x_comment, "comment", 0 },
+ { 0, 0, 0 },
+#ifdef DEBUG
+ { x_debug_info, "debug-info", 0 },
+#else
+ { 0, 0, 0 },
+#endif
+ { 0, 0, 0 },
+};
+
+int
+x_emacs(char *buf, size_t len)
+{
+ struct kb_entry *k, *kmatch = NULL;
+ char line[LINE + 1];
+ int at = 0, submatch, ret, c;
+ const char *p;
+
+ xbp = xbuf = buf; xend = buf + len;
+ xlp = xcp = xep = buf;
+ *xcp = 0;
+ xlp_valid = true;
+ xmp = NULL;
+ x_histp = histptr + 1;
+
+ xx_cols = x_cols;
+ x_col = promptlen(prompt, &p);
+ prompt_skip = p - prompt;
+ x_adj_ok = 1;
+ prompt_redraw = 1;
+ if (x_col > xx_cols)
+ x_col = x_col - (x_col / xx_cols) * xx_cols;
+ x_displen = xx_cols - 2 - x_col;
+ x_adj_done = 0;
+
+ pprompt(prompt, 0);
+ if (x_displen < 1) {
+ x_col = 0;
+ x_displen = xx_cols - 2;
+ x_e_putc('\n');
+ prompt_redraw = 0;
+ }
+
+ if (x_nextcmd >= 0) {
+ int off = source->line - x_nextcmd;
+ if (histptr - history >= off)
+ x_load_hist(histptr - off);
+ x_nextcmd = -1;
+ }
+
+ line[0] = '\0';
+ x_literal_set = 0;
+ x_arg = -1;
+ x_last_command = NULL;
+ while (1) {
+ x_flush();
+ if ((c = x_e_getc()) < 0)
+ return 0;
+
+ line[at++] = c;
+ line[at] = '\0';
+
+ if (x_arg == -1) {
+ x_arg = 1;
+ x_arg_defaulted = 1;
+ }
+
+ if (x_literal_set) {
+ /* literal, so insert it */
+ x_literal_set = 0;
+ submatch = 0;
+ } else {
+ submatch = 0;
+ kmatch = NULL;
+ TAILQ_FOREACH(k, &kblist, entry) {
+ if (at > k->len)
+ continue;
+
+ if (!bcmp(k->seq, line, at)) {
+ /* sub match */
+ submatch++;
+ if (k->len == at)
+ kmatch = k;
+ }
+
+ /* see if we can abort search early */
+ if (submatch > 1)
+ break;
+ }
+ }
+
+ if (submatch == 1 && kmatch) {
+ if (kmatch->ftab->xf_func == x_ins_string &&
+ kmatch->args && !macro_args) {
+ /* treat macro string as input */
+ macro_args = kmatch->args;
+ ret = KSTD;
+ } else
+ ret = kmatch->ftab->xf_func(c);
+ } else {
+ if (submatch)
+ continue;
+ if (at == 1)
+ ret = x_insert(c);
+ else
+ ret = x_error(c); /* not matched meta sequence */
+ }
+
+ switch (ret) {
+ case KSTD:
+ if (kmatch)
+ x_last_command = kmatch->ftab->xf_func;
+ else
+ x_last_command = NULL;
+ break;
+ case KEOL:
+ ret = xep - xbuf;
+ return (ret);
+ break;
+ case KINTR:
+ trapsig(SIGINT);
+ x_mode(false);
+ unwind(LSHELL);
+ x_arg = -1;
+ break;
+ default:
+ bi_errorf("invalid return code"); /* can't happen */
+ }
+
+ /* reset meta sequence */
+ at = 0;
+ line[0] = '\0';
+ if (x_arg_set)
+ x_arg_set = 0; /* reset args next time around */
+ else
+ x_arg = -1;
+ }
+}
+
+static int
+x_insert(int c)
+{
+ char str[2];
+
+ /*
+ * Should allow tab and control chars.
+ */
+ if (c == 0) {
+ x_e_putc(BEL);
+ return KSTD;
+ }
+ str[0] = c;
+ str[1] = '\0';
+ while (x_arg--)
+ x_ins(str);
+ return KSTD;
+}
+
+static int
+x_ins_string(int c)
+{
+ return x_insert(c);
+}
+
+static int x_do_ins(const char *cp, int len);
+
+static int
+x_do_ins(const char *cp, int len)
+{
+ if (xep+len >= xend) {
+ x_e_putc(BEL);
+ return -1;
+ }
+
+ memmove(xcp+len, xcp, xep - xcp + 1);
+ memmove(xcp, cp, len);
+ xcp += len;
+ xep += len;
+ return 0;
+}
+
+static int
+x_ins(char *s)
+{
+ char *cp = xcp;
+ int adj = x_adj_done;
+
+ if (x_do_ins(s, strlen(s)) < 0)
+ return -1;
+ /*
+ * x_zots() may result in a call to x_adjust()
+ * we want xcp to reflect the new position.
+ */
+ xlp_valid = false;
+ x_lastcp();
+ x_adj_ok = (xcp >= xlp);
+ x_zots(cp);
+ if (adj == x_adj_done) { /* has x_adjust() been called? */
+ /* no */
+ for (cp = xlp; cp > xcp; )
+ x_bs(*--cp);
+ }
+
+ x_adj_ok = 1;
+ return 0;
+}
+
+/*
+ * this is used for x_escape() in do_complete()
+ */
+static int
+x_emacs_putbuf(const char *s, size_t len)
+{
+ int rval;
+
+ if ((rval = x_do_ins(s, len)) != 0)
+ return (rval);
+ return (rval);
+}
+
+static int
+x_del_back(int c)
+{
+ int col = xcp - xbuf;
+
+ if (col == 0) {
+ x_e_putc(BEL);
+ return KSTD;
+ }
+ if (x_arg > col)
+ x_arg = col;
+ x_goto(xcp - x_arg);
+ x_delete(x_arg, false);
+ return KSTD;
+}
+
+static int
+x_del_char(int c)
+{
+ int nleft = xep - xcp;
+
+ if (!nleft) {
+ x_e_putc(BEL);
+ return KSTD;
+ }
+ if (x_arg > nleft)
+ x_arg = nleft;
+ x_delete(x_arg, false);
+ return KSTD;
+}
+
+/* Delete nc chars to the right of the cursor (including cursor position) */
+static void
+x_delete(int nc, int push)
+{
+ int i,j;
+ char *cp;
+
+ if (nc == 0)
+ return;
+ if (xmp != NULL && xmp > xcp) {
+ if (xcp + nc > xmp)
+ xmp = xcp;
+ else
+ xmp -= nc;
+ }
+
+ /*
+ * This lets us yank a word we have deleted.
+ */
+ if (push)
+ x_push(nc);
+
+ xep -= nc;
+ cp = xcp;
+ j = 0;
+ i = nc;
+ while (i--) {
+ j += x_size((unsigned char)*cp++);
+ }
+ memmove(xcp, xcp+nc, xep - xcp + 1); /* Copies the null */
+ x_adj_ok = 0; /* don't redraw */
+ x_zots(xcp);
+ /*
+ * if we are already filling the line,
+ * there is no need to ' ','\b'.
+ * But if we must, make sure we do the minimum.
+ */
+ if ((i = xx_cols - 2 - x_col) > 0) {
+ j = (j < i) ? j : i;
+ i = j;
+ while (i--)
+ x_e_putc(' ');
+ i = j;
+ while (i--)
+ x_e_putc('\b');
+ }
+ /*x_goto(xcp);*/
+ x_adj_ok = 1;
+ xlp_valid = false;
+ for (cp = x_lastcp(); cp > xcp; )
+ x_bs(*--cp);
+
+ return;
+}
+
+static int
+x_del_bword(int c)
+{
+ x_delete(x_bword(), true);
+ return KSTD;
+}
+
+static int
+x_mv_bword(int c)
+{
+ (void)x_bword();
+ return KSTD;
+}
+
+static int
+x_mv_fword(int c)
+{
+ x_goto(xcp + x_fword());
+ return KSTD;
+}
+
+static int
+x_del_fword(int c)
+{
+ x_delete(x_fword(), true);
+ return KSTD;
+}
+
+static int
+x_bword(void)
+{
+ int nc = 0;
+ char *cp = xcp;
+
+ if (cp == xbuf) {
+ x_e_putc(BEL);
+ return 0;
+ }
+ while (x_arg--) {
+ while (cp != xbuf && is_mfs(cp[-1])) {
+ cp--;
+ nc++;
+ }
+ while (cp != xbuf && !is_mfs(cp[-1])) {
+ cp--;
+ nc++;
+ }
+ }
+ x_goto(cp);
+ return nc;
+}
+
+static int
+x_fword(void)
+{
+ int nc = 0;
+ char *cp = xcp;
+
+ if (cp == xep) {
+ x_e_putc(BEL);
+ return 0;
+ }
+ while (x_arg--) {
+ while (cp != xep && is_mfs(*cp)) {
+ cp++;
+ nc++;
+ }
+ while (cp != xep && !is_mfs(*cp)) {
+ cp++;
+ nc++;
+ }
+ }
+ return nc;
+}
+
+static void
+x_goto(char *cp)
+{
+ if (cp < xbp || cp >= (xbp + x_displen)) {
+ /* we are heading off screen */
+ xcp = cp;
+ x_adjust();
+ } else if (cp < xcp) { /* move back */
+ while (cp < xcp)
+ x_bs((unsigned char)*--xcp);
+ } else if (cp > xcp) { /* move forward */
+ while (cp > xcp)
+ x_zotc((unsigned char)*xcp++);
+ }
+}
+
+static void
+x_bs(int c)
+{
+ int i;
+
+ i = x_size(c);
+ while (i--)
+ x_e_putc('\b');
+}
+
+static int
+x_size_str(char *cp)
+{
+ int size = 0;
+ while (*cp)
+ size += x_size(*cp++);
+ return size;
+}
+
+static int
+x_size(int c)
+{
+ if (c=='\t')
+ return 4; /* Kludge, tabs are always four spaces. */
+ if (iscntrl(c)) /* control char */
+ return 2;
+ return 1;
+}
+
+static void
+x_zots(char *str)
+{
+ int adj = x_adj_done;
+
+ x_lastcp();
+ while (*str && str < xlp && adj == x_adj_done)
+ x_zotc(*str++);
+}
+
+static void
+x_zotc(int c)
+{
+ if (c == '\t') {
+ /* Kludge, tabs are always four spaces. */
+ x_e_puts(" ");
+ } else if (iscntrl(c)) {
+ x_e_putc('^');
+ x_e_putc(UNCTRL(c));
+ } else
+ x_e_putc(c);
+}
+
+static int
+x_mv_back(int c)
+{
+ int col = xcp - xbuf;
+
+ if (col == 0) {
+ x_e_putc(BEL);
+ return KSTD;
+ }
+ if (x_arg > col)
+ x_arg = col;
+ x_goto(xcp - x_arg);
+ return KSTD;
+}
+
+static int
+x_mv_forw(int c)
+{
+ int nleft = xep - xcp;
+
+ if (!nleft) {
+ x_e_putc(BEL);
+ return KSTD;
+ }
+ if (x_arg > nleft)
+ x_arg = nleft;
+ x_goto(xcp + x_arg);
+ return KSTD;
+}
+
+static int
+x_search_char_forw(int c)
+{
+ char *cp = xcp;
+
+ *xep = '\0';
+ c = x_e_getc();
+ while (x_arg--) {
+ if (c < 0 ||
+ ((cp = (cp == xep) ? NULL : strchr(cp + 1, c)) == NULL &&
+ (cp = strchr(xbuf, c)) == NULL)) {
+ x_e_putc(BEL);
+ return KSTD;
+ }
+ }
+ x_goto(cp);
+ return KSTD;
+}
+
+static int
+x_search_char_back(int c)
+{
+ char *cp = xcp, *p;
+
+ c = x_e_getc();
+ for (; x_arg--; cp = p)
+ for (p = cp; ; ) {
+ if (p-- == xbuf)
+ p = xep;
+ if (c < 0 || p == cp) {
+ x_e_putc(BEL);
+ return KSTD;
+ }
+ if (*p == c)
+ break;
+ }
+ x_goto(cp);
+ return KSTD;
+}
+
+static int
+x_newline(int c)
+{
+ x_e_putc('\r');
+ x_e_putc('\n');
+ x_flush();
+ *xep++ = '\n';
+ return KEOL;
+}
+
+static int
+x_end_of_text(int c)
+{
+ x_zotc(edchars.eof);
+ x_putc('\r');
+ x_putc('\n');
+ x_flush();
+ return KEOL;
+}
+
+static int x_beg_hist(int c) { x_load_hist(history); return KSTD;}
+
+static int x_end_hist(int c) { x_load_hist(histptr); return KSTD;}
+
+static int x_prev_com(int c) { x_load_hist(x_histp - x_arg); return KSTD;}
+
+static int x_next_com(int c) { x_load_hist(x_histp + x_arg); return KSTD;}
+
+/* Goto a particular history number obtained from argument.
+ * If no argument is given history 1 is probably not what you
+ * want so we'll simply go to the oldest one.
+ */
+static int
+x_goto_hist(int c)
+{
+ if (x_arg_defaulted)
+ x_load_hist(history);
+ else
+ x_load_hist(histptr + x_arg - source->line);
+ return KSTD;
+}
+
+static void
+x_load_hist(char **hp)
+{
+ int oldsize;
+
+ if (hp < history || hp > histptr) {
+ x_e_putc(BEL);
+ return;
+ }
+ x_histp = hp;
+ oldsize = x_size_str(xbuf);
+ strlcpy(xbuf, *hp, xend - xbuf);
+ xbp = xbuf;
+ xep = xcp = xbuf + strlen(xbuf);
+ xlp_valid = false;
+ if (xep <= x_lastcp())
+ x_redraw(oldsize);
+ x_goto(xep);
+}
+
+static int
+x_nl_next_com(int c)
+{
+ x_nextcmd = source->line - (histptr - x_histp) + 1;
+ return (x_newline(c));
+}
+
+static int
+x_eot_del(int c)
+{
+ if (xep == xbuf && x_arg_defaulted)
+ return (x_end_of_text(c));
+ else
+ return (x_del_char(c));
+}
+
+static void *
+kb_find_hist_func(char c)
+{
+ struct kb_entry *k;
+ char line[LINE + 1];
+
+ line[0] = c;
+ line[1] = '\0';
+ TAILQ_FOREACH(k, &kblist, entry)
+ if (!strcmp(k->seq, line))
+ return (k->ftab->xf_func);
+
+ return (x_insert);
+}
+
+/* reverse incremental history search */
+static int
+x_search_hist(int c)
+{
+ int offset = -1; /* offset of match in xbuf, else -1 */
+ char pat [256+1]; /* pattern buffer */
+ char *p = pat;
+ int (*f)(int);
+
+ *p = '\0';
+ while (1) {
+ if (offset < 0) {
+ x_e_puts("\nI-search: ");
+ x_e_puts(pat);
+ }
+ x_flush();
+ if ((c = x_e_getc()) < 0)
+ return KSTD;
+ f = kb_find_hist_func(c);
+ if (c == CTRL('['))
+ break;
+ else if (f == x_search_hist)
+ offset = x_search(pat, 0, offset);
+ else if (f == x_del_back) {
+ if (p == pat) {
+ offset = -1;
+ break;
+ }
+ if (p > pat)
+ *--p = '\0';
+ if (p == pat)
+ offset = -1;
+ else
+ offset = x_search(pat, 1, offset);
+ continue;
+ } else if (f == x_insert) {
+ /* add char to pattern */
+ /* overflow check... */
+ if (p >= &pat[sizeof(pat) - 1]) {
+ x_e_putc(BEL);
+ continue;
+ }
+ *p++ = c, *p = '\0';
+ if (offset >= 0) {
+ /* already have partial match */
+ offset = x_match(xbuf, pat);
+ if (offset >= 0) {
+ x_goto(xbuf + offset + (p - pat) -
+ (*pat == '^'));
+ continue;
+ }
+ }
+ offset = x_search(pat, 0, offset);
+ } else { /* other command */
+ x_e_ungetc(c);
+ break;
+ }
+ }
+ if (offset < 0)
+ x_redraw(-1);
+ return KSTD;
+}
+
+/* search backward from current line */
+static int
+x_search(char *pat, int sameline, int offset)
+{
+ char **hp;
+ int i;
+
+ for (hp = x_histp - (sameline ? 0 : 1) ; hp >= history; --hp) {
+ i = x_match(*hp, pat);
+ if (i >= 0) {
+ if (offset < 0)
+ x_e_putc('\n');
+ x_load_hist(hp);
+ x_goto(xbuf + i + strlen(pat) - (*pat == '^'));
+ return i;
+ }
+ }
+ x_e_putc(BEL);
+ x_histp = histptr;
+ return -1;
+}
+
+/* return position of first match of pattern in string, else -1 */
+static int
+x_match(char *str, char *pat)
+{
+ if (*pat == '^') {
+ return (strncmp(str, pat+1, strlen(pat+1)) == 0) ? 0 : -1;
+ } else {
+ char *q = strstr(str, pat);
+ return (q == NULL) ? -1 : q - str;
+ }
+}
+
+static int
+x_del_line(int c)
+{
+ int i, j;
+
+ *xep = 0;
+ i = xep - xbuf;
+ j = x_size_str(xbuf);
+ xcp = xbuf;
+ x_push(i);
+ xlp = xbp = xep = xbuf;
+ xlp_valid = true;
+ *xcp = 0;
+ xmp = NULL;
+ x_redraw(j);
+ return KSTD;
+}
+
+static int
+x_mv_end(int c)
+{
+ x_goto(xep);
+ return KSTD;
+}
+
+static int
+x_mv_begin(int c)
+{
+ x_goto(xbuf);
+ return KSTD;
+}
+
+static int
+x_draw_line(int c)
+{
+ x_redraw(-1);
+ return KSTD;
+
+}
+
+/* Redraw (part of) the line. If limit is < 0, the everything is redrawn
+ * on a NEW line, otherwise limit is the screen column up to which needs
+ * redrawing.
+ */
+static void
+x_redraw(int limit)
+{
+ int i, j, truncate = 0;
+ char *cp;
+
+ x_adj_ok = 0;
+ if (limit == -1)
+ x_e_putc('\n');
+ else
+ x_e_putc('\r');
+ x_flush();
+ if (xbp == xbuf) {
+ x_col = promptlen(prompt, (const char **) 0);
+ if (x_col > xx_cols)
+ truncate = (x_col / xx_cols) * xx_cols;
+ if (prompt_redraw)
+ pprompt(prompt + prompt_skip, truncate);
+ }
+ if (x_col > xx_cols)
+ x_col = x_col - (x_col / xx_cols) * xx_cols;
+ x_displen = xx_cols - 2 - x_col;
+ if (x_displen < 1) {
+ x_col = 0;
+ x_displen = xx_cols - 2;
+ }
+ xlp_valid = false;
+ cp = x_lastcp();
+ x_zots(xbp);
+ if (xbp != xbuf || xep > xlp)
+ limit = xx_cols;
+ if (limit >= 0) {
+ if (xep > xlp)
+ i = 0; /* we fill the line */
+ else
+ i = limit - (xlp - xbp);
+
+ for (j = 0; j < i && x_col < (xx_cols - 2); j++)
+ x_e_putc(' ');
+ i = ' ';
+ if (xep > xlp) { /* more off screen */
+ if (xbp > xbuf)
+ i = '*';
+ else
+ i = '>';
+ } else if (xbp > xbuf)
+ i = '<';
+ x_e_putc(i);
+ j++;
+ while (j--)
+ x_e_putc('\b');
+ }
+ for (cp = xlp; cp > xcp; )
+ x_bs(*--cp);
+ x_adj_ok = 1;
+ D__(x_flush();)
+ return;
+}
+
+static int
+x_transpose(int c)
+{
+ char tmp;
+
+ /* What transpose is meant to do seems to be up for debate. This
+ * is a general summary of the options; the text is abcd with the
+ * upper case character or underscore indicating the cursor position:
+ * Who Before After Before After
+ * at&t ksh in emacs mode: abCd abdC abcd_ (bell)
+ * at&t ksh in gmacs mode: abCd baCd abcd_ abdc_
+ * gnu emacs: abCd acbD abcd_ abdc_
+ * Pdksh currently goes with GNU behavior since I believe this is the
+ * most common version of emacs, unless in gmacs mode, in which case
+ * it does the at&t ksh gmacs mode.
+ * This should really be broken up into 3 functions so users can bind
+ * to the one they want.
+ */
+ if (xcp == xbuf) {
+ x_e_putc(BEL);
+ return KSTD;
+ } else if (xcp == xep || Flag(FGMACS)) {
+ if (xcp - xbuf == 1) {
+ x_e_putc(BEL);
+ return KSTD;
+ }
+ /* Gosling/Unipress emacs style: Swap two characters before the
+ * cursor, do not change cursor position
+ */
+ x_bs(xcp[-1]);
+ x_bs(xcp[-2]);
+ x_zotc(xcp[-1]);
+ x_zotc(xcp[-2]);
+ tmp = xcp[-1];
+ xcp[-1] = xcp[-2];
+ xcp[-2] = tmp;
+ } else {
+ /* GNU emacs style: Swap the characters before and under the
+ * cursor, move cursor position along one.
+ */
+ x_bs(xcp[-1]);
+ x_zotc(xcp[0]);
+ x_zotc(xcp[-1]);
+ tmp = xcp[-1];
+ xcp[-1] = xcp[0];
+ xcp[0] = tmp;
+ x_bs(xcp[0]);
+ x_goto(xcp + 1);
+ }
+ return KSTD;
+}
+
+static int
+x_literal(int c)
+{
+ x_literal_set = 1;
+ return KSTD;
+}
+
+static int
+x_kill(int c)
+{
+ int col = xcp - xbuf;
+ int lastcol = xep - xbuf;
+ int ndel;
+
+ if (x_arg_defaulted)
+ x_arg = lastcol;
+ else if (x_arg > lastcol)
+ x_arg = lastcol;
+ ndel = x_arg - col;
+ if (ndel < 0) {
+ x_goto(xbuf + x_arg);
+ ndel = -ndel;
+ }
+ x_delete(ndel, true);
+ return KSTD;
+}
+
+static void
+x_push(int nchars)
+{
+ char *cp = str_nsave(xcp, nchars, AEDIT);
+ if (killstack[killsp])
+ afree((void *)killstack[killsp], AEDIT);
+ killstack[killsp] = cp;
+ killsp = (killsp + 1) % KILLSIZE;
+}
+
+static int
+x_yank(int c)
+{
+ if (killsp == 0)
+ killtp = KILLSIZE;
+ else
+ killtp = killsp;
+ killtp --;
+ if (killstack[killtp] == 0) {
+ x_e_puts("\nnothing to yank");
+ x_redraw(-1);
+ return KSTD;
+ }
+ xmp = xcp;
+ x_ins(killstack[killtp]);
+ return KSTD;
+}
+
+static int
+x_meta_yank(int c)
+{
+ int len;
+ if ((x_last_command != x_yank && x_last_command != x_meta_yank) ||
+ killstack[killtp] == 0) {
+ killtp = killsp;
+ x_e_puts("\nyank something first");
+ x_redraw(-1);
+ return KSTD;
+ }
+ len = strlen(killstack[killtp]);
+ x_goto(xcp - len);
+ x_delete(len, false);
+ do {
+ if (killtp == 0)
+ killtp = KILLSIZE - 1;
+ else
+ killtp--;
+ } while (killstack[killtp] == 0);
+ x_ins(killstack[killtp]);
+ return KSTD;
+}
+
+static int
+x_abort(int c)
+{
+ /* x_zotc(c); */
+ xlp = xep = xcp = xbp = xbuf;
+ xlp_valid = true;
+ *xcp = 0;
+ return KINTR;
+}
+
+static int
+x_error(int c)
+{
+ x_e_putc(BEL);
+ return KSTD;
+}
+
+static int
+x_stuffreset(int c)
+{
+#ifdef TIOCSTI
+ (void)x_stuff(c);
+ return KINTR;
+#else
+ x_zotc(c);
+ xlp = xcp = xep = xbp = xbuf;
+ xlp_valid = true;
+ *xcp = 0;
+ x_redraw(-1);
+ return KSTD;
+#endif
+}
+
+static int
+x_stuff(int c)
+{
+#ifdef TIOCSTI
+ char ch = c;
+ bool savmode = x_mode(false);
+
+ (void)ioctl(TTY, TIOCSTI, &ch);
+ (void)x_mode(savmode);
+ x_redraw(-1);
+#endif
+ return KSTD;
+}
+
+static char *
+kb_encode(const char *s)
+{
+ static char l[LINE + 1];
+ int at = 0;
+
+ l[at] = '\0';
+ while (*s) {
+ if (*s == '^') {
+ s++;
+ if (*s >= '?')
+ l[at++] = CTRL(*s);
+ else {
+ l[at++] = '^';
+ s--;
+ }
+ } else
+ l[at++] = *s;
+ l[at] = '\0';
+ s++;
+ }
+ return (l);
+}
+
+static char *
+kb_decode(const char *s)
+{
+ static char l[LINE + 1];
+ int i, at = 0;
+
+ l[0] = '\0';
+ for (i = 0; i < strlen(s); i++) {
+ if (iscntrl(s[i])) {
+ l[at++] = '^';
+ l[at++] = UNCTRL(s[i]);
+ } else
+ l[at++] = s[i];
+ l[at] = '\0';
+ }
+
+ return (l);
+}
+
+static int
+kb_match(char *s)
+{
+ int len = strlen(s);
+ struct kb_entry *k;
+
+ TAILQ_FOREACH(k, &kblist, entry) {
+ if (len > k->len)
+ continue;
+
+ if (!bcmp(k->seq, s, len))
+ return (1);
+ }
+
+ return (0);
+}
+
+static void
+kb_del(struct kb_entry *k)
+{
+ TAILQ_REMOVE(&kblist, k, entry);
+ if (k->args)
+ free(k->args);
+ afree(k, AEDIT);
+}
+
+static struct kb_entry *
+kb_add_string(void *func, void *args, char *str)
+{
+ int i, count;
+ struct kb_entry *k;
+ struct x_ftab *xf = NULL;
+
+ for (i = 0; i < NELEM(x_ftab); i++)
+ if (x_ftab[i].xf_func == func) {
+ xf = (struct x_ftab *)&x_ftab[i];
+ break;
+ }
+ if (xf == NULL)
+ return (NULL);
+
+ if (kb_match(str)) {
+ if (x_bind_quiet == 0)
+ bi_errorf("duplicate binding for %s", kb_decode(str));
+ return (NULL);
+ }
+ count = strlen(str);
+
+ k = alloc(sizeof *k + count + 1, AEDIT);
+ k->seq = (unsigned char *)(k + 1);
+ k->len = count;
+ k->ftab = xf;
+ k->args = args ? strdup(args) : NULL;
+
+ strlcpy(k->seq, str, count + 1);
+
+ TAILQ_INSERT_TAIL(&kblist, k, entry);
+
+ return (k);
+}
+
+static struct kb_entry *
+kb_add(void *func, void *args, ...)
+{
+ va_list ap;
+ int i, count;
+ char l[LINE + 1];
+
+ va_start(ap, args);
+ count = 0;
+ while (va_arg(ap, unsigned int) != 0)
+ count++;
+ va_end(ap);
+
+ va_start(ap, args);
+ for (i = 0; i <= count /* <= is correct */; i++)
+ l[i] = (unsigned char)va_arg(ap, unsigned int);
+ va_end(ap);
+
+ return (kb_add_string(func, args, l));
+}
+
+static void
+kb_print(struct kb_entry *k)
+{
+ if (!(k->ftab->xf_flags & XF_NOBIND))
+ shprintf("%s = %s\n",
+ kb_decode(k->seq), k->ftab->xf_name);
+ else if (k->args) {
+ shprintf("%s = ", kb_decode(k->seq));
+ shprintf("'%s'\n", kb_decode(k->args));
+ }
+}
+
+int
+x_bind(const char *a1, const char *a2,
+ int macro, /* bind -m */
+ int list) /* bind -l */
+{
+ int i;
+ struct kb_entry *k, *kb;
+ char in[LINE + 1];
+
+ if (x_tty == 0) {
+ bi_errorf("cannot bind, not a tty");
+ return (1);
+ }
+
+ if (list) {
+ /* show all function names */
+ for (i = 0; i < NELEM(x_ftab); i++) {
+ if (x_ftab[i].xf_name == NULL)
+ continue;
+ if (x_ftab[i].xf_name &&
+ !(x_ftab[i].xf_flags & XF_NOBIND))
+ shprintf("%s\n", x_ftab[i].xf_name);
+ }
+ return (0);
+ }
+
+ if (a1 == NULL) {
+ /* show all bindings */
+ TAILQ_FOREACH(k, &kblist, entry)
+ kb_print(k);
+ return (0);
+ }
+
+ snprintf(in, sizeof in, "%s", kb_encode(a1));
+ if (a2 == NULL) {
+ /* print binding */
+ TAILQ_FOREACH(k, &kblist, entry)
+ if (!strcmp(k->seq, in)) {
+ kb_print(k);
+ return (0);
+ }
+ shprintf("%s = %s\n", kb_decode(a1), "auto-insert");
+ return (0);
+ }
+
+ if (strlen(a2) == 0) {
+ /* clear binding */
+ TAILQ_FOREACH_SAFE(k, &kblist, entry, kb)
+ if (!strcmp(k->seq, in)) {
+ kb_del(k);
+ break;
+ }
+ return (0);
+ }
+
+ /* set binding */
+ if (macro) {
+ /* delete old mapping */
+ TAILQ_FOREACH_SAFE(k, &kblist, entry, kb)
+ if (!strcmp(k->seq, in)) {
+ kb_del(k);
+ break;
+ }
+ kb_add_string(x_ins_string, kb_encode(a2), in);
+ return (0);
+ }
+
+ /* set non macro binding */
+ for (i = 0; i < NELEM(x_ftab); i++) {
+ if (x_ftab[i].xf_name == NULL)
+ continue;
+ if (!strcmp(x_ftab[i].xf_name, a2)) {
+ /* delete old mapping */
+ TAILQ_FOREACH_SAFE(k, &kblist, entry, kb)
+ if (!strcmp(k->seq, in)) {
+ kb_del(k);
+ break;
+ }
+ kb_add_string(x_ftab[i].xf_func, NULL, in);
+ return (0);
+ }
+ }
+ bi_errorf("%s: no such function", a2);
+ return (1);
+}
+
+void
+x_init_emacs(void)
+{
+ char *locale;
+
+ x_tty = 1;
+ ainit(AEDIT);
+ x_nextcmd = -1;
+
+ /* Determine if we can translate meta key or use 8-bit AscII
+ * XXX - It would be nice if there was a locale attribute to
+ * determine if the locale is 7-bit or not.
+ */
+ locale = setlocale(LC_CTYPE, NULL);
+ if (locale == NULL || !strcmp(locale, "C") || !strcmp(locale, "POSIX"))
+ Flag(FEMACSUSEMETA) = 1;
+
+ /* new keybinding stuff */
+ TAILQ_INIT(&kblist);
+
+ /* man page order */
+ kb_add(x_abort, NULL, CTRL('G'), 0);
+ kb_add(x_mv_back, NULL, CTRL('B'), 0);
+ kb_add(x_mv_back, NULL, CTRL('X'), CTRL('D'), 0);
+ kb_add(x_mv_bword, NULL, CTRL('['), 'b', 0);
+ kb_add(x_beg_hist, NULL, CTRL('['), '<', 0);
+ kb_add(x_mv_begin, NULL, CTRL('A'), 0);
+ kb_add(x_fold_capitalize, NULL, CTRL('['), 'C', 0);
+ kb_add(x_fold_capitalize, NULL, CTRL('['), 'c', 0);
+ kb_add(x_comment, NULL, CTRL('['), '#', 0);
+ kb_add(x_complete, NULL, CTRL('['), CTRL('['), 0);
+ kb_add(x_comp_comm, NULL, CTRL('X'), CTRL('['), 0);
+ kb_add(x_comp_file, NULL, CTRL('['), CTRL('X'), 0);
+ kb_add(x_comp_list, NULL, CTRL('I'), 0);
+ kb_add(x_comp_list, NULL, CTRL('['), '=', 0);
+ kb_add(x_del_back, NULL, CTRL('?'), 0);
+ kb_add(x_del_back, NULL, CTRL('H'), 0);
+ /* x_del_char not assigned by default */
+ kb_add(x_del_bword, NULL, CTRL('['), CTRL('?'), 0);
+ kb_add(x_del_bword, NULL, CTRL('['), CTRL('H'), 0);
+ kb_add(x_del_bword, NULL, CTRL('['), 'h', 0);
+ kb_add(x_del_fword, NULL, CTRL('['), 'd', 0);
+ kb_add(x_next_com, NULL, CTRL('N'), 0);
+ kb_add(x_next_com, NULL, CTRL('X'), 'B', 0);
+ kb_add(x_fold_lower, NULL, CTRL('['), 'L', 0);
+ kb_add(x_fold_lower, NULL, CTRL('['), 'l', 0);
+ kb_add(x_end_hist, NULL, CTRL('['), '>', 0);
+ kb_add(x_mv_end, NULL, CTRL('E'), 0);
+ /* how to handle: eot: ^_, underneath copied from original keybindings */
+ kb_add(x_end_of_text, NULL, CTRL('_'), 0);
+ kb_add(x_eot_del, NULL, CTRL('D'), 0);
+ /* error */
+ kb_add(x_xchg_point_mark, NULL, CTRL('X'), CTRL('X'), 0);
+ kb_add(x_expand, NULL, CTRL('['), '*', 0);
+ kb_add(x_mv_forw, NULL, CTRL('F'), 0);
+ kb_add(x_mv_forw, NULL, CTRL('X'), 'C', 0);
+ kb_add(x_mv_fword, NULL, CTRL('['), 'f', 0);
+ kb_add(x_goto_hist, NULL, CTRL('['), 'g', 0);
+ /* kill-line */
+ kb_add(x_del_bword, NULL, CTRL('W'), 0); /* not what man says */
+ kb_add(x_kill, NULL, CTRL('K'), 0);
+ kb_add(x_enumerate, NULL, CTRL('['), '?', 0);
+ kb_add(x_list_comm, NULL, CTRL('X'), '?', 0);
+ kb_add(x_list_file, NULL, CTRL('X'), CTRL('Y'), 0);
+ kb_add(x_newline, NULL, CTRL('J'), 0);
+ kb_add(x_newline, NULL, CTRL('M'), 0);
+ kb_add(x_nl_next_com, NULL, CTRL('O'), 0);
+ /* no-op */
+ kb_add(x_prev_histword, NULL, CTRL('['), '.', 0);
+ kb_add(x_prev_histword, NULL, CTRL('['), '_', 0);
+ /* how to handle: quote: ^^ */
+ kb_add(x_draw_line, NULL, CTRL('L'), 0);
+ kb_add(x_search_char_back, NULL, CTRL('['), CTRL(']'), 0);
+ kb_add(x_search_char_forw, NULL, CTRL(']'), 0);
+ kb_add(x_search_hist, NULL, CTRL('R'), 0);
+ kb_add(x_set_mark, NULL, CTRL('['), ' ', 0);
+#if defined(TIOCSTI)
+ kb_add(x_stuff, NULL, CTRL('T'), 0);
+ /* stuff-reset */
+#else
+ kb_add(x_transpose, NULL, CTRL('T'), 0);
+#endif
+ kb_add(x_prev_com, NULL, CTRL('P'), 0);
+ kb_add(x_prev_com, NULL, CTRL('X'), 'A', 0);
+ kb_add(x_fold_upper, NULL, CTRL('['), 'U', 0);
+ kb_add(x_fold_upper, NULL, CTRL('['), 'u', 0);
+ kb_add(x_literal, NULL, CTRL('V'), 0);
+ kb_add(x_literal, NULL, CTRL('^'), 0);
+ kb_add(x_yank, NULL, CTRL('Y'), 0);
+ kb_add(x_meta_yank, NULL, CTRL('['), 'y', 0);
+ /* man page ends here */
+
+ /* arrow keys */
+ kb_add(x_prev_com, NULL, CTRL('['), '[', 'A', 0); /* up */
+ kb_add(x_next_com, NULL, CTRL('['), '[', 'B', 0); /* down */
+ kb_add(x_mv_forw, NULL, CTRL('['), '[', 'C', 0); /* right */
+ kb_add(x_mv_back, NULL, CTRL('['), '[', 'D', 0); /* left */
+ kb_add(x_prev_com, NULL, CTRL('['), 'O', 'A', 0); /* up */
+ kb_add(x_next_com, NULL, CTRL('['), 'O', 'B', 0); /* down */
+ kb_add(x_mv_forw, NULL, CTRL('['), 'O', 'C', 0); /* right */
+ kb_add(x_mv_back, NULL, CTRL('['), 'O', 'D', 0); /* left */
+
+ /* more navigation keys */
+ kb_add(x_mv_begin, NULL, CTRL('['), '[', 'H', 0); /* home */
+ kb_add(x_mv_end, NULL, CTRL('['), '[', 'F', 0); /* end */
+ kb_add(x_mv_begin, NULL, CTRL('['), 'O', 'H', 0); /* home */
+ kb_add(x_mv_end, NULL, CTRL('['), 'O', 'F', 0); /* end */
+
+ /* can't be bound */
+ kb_add(x_set_arg, NULL, CTRL('['), '0', 0);
+ kb_add(x_set_arg, NULL, CTRL('['), '1', 0);
+ kb_add(x_set_arg, NULL, CTRL('['), '2', 0);
+ kb_add(x_set_arg, NULL, CTRL('['), '3', 0);
+ kb_add(x_set_arg, NULL, CTRL('['), '4', 0);
+ kb_add(x_set_arg, NULL, CTRL('['), '5', 0);
+ kb_add(x_set_arg, NULL, CTRL('['), '6', 0);
+ kb_add(x_set_arg, NULL, CTRL('['), '7', 0);
+ kb_add(x_set_arg, NULL, CTRL('['), '8', 0);
+ kb_add(x_set_arg, NULL, CTRL('['), '9', 0);
+
+ /* ctrl arrow keys */
+ kb_add(x_mv_end, NULL, CTRL('['), '[', '1', ';', '5', 'A', 0); /* ctrl up */
+ kb_add(x_mv_begin, NULL, CTRL('['), '[', '1', ';', '5', 'B', 0); /* ctrl down */
+ kb_add(x_mv_fword, NULL, CTRL('['), '[', '1', ';', '5', 'C', 0); /* ctrl right */
+ kb_add(x_mv_bword, NULL, CTRL('['), '[', '1', ';', '5', 'D', 0); /* ctrl left */
+}
+
+void
+x_emacs_keys(X_chars *ec)
+{
+ x_bind_quiet = 1;
+ if (ec->erase >= 0) {
+ kb_add(x_del_back, NULL, ec->erase, 0);
+ kb_add(x_del_bword, NULL, CTRL('['), ec->erase, 0);
+ }
+ if (ec->kill >= 0)
+ kb_add(x_del_line, NULL, ec->kill, 0);
+ if (ec->werase >= 0)
+ kb_add(x_del_bword, NULL, ec->werase, 0);
+ if (ec->intr >= 0)
+ kb_add(x_abort, NULL, ec->intr, 0);
+ if (ec->quit >= 0)
+ kb_add(x_noop, NULL, ec->quit, 0);
+ x_bind_quiet = 0;
+}
+
+static int
+x_set_mark(int c)
+{
+ xmp = xcp;
+ return KSTD;
+}
+
+static int
+x_kill_region(int c)
+{
+ int rsize;
+ char *xr;
+
+ if (xmp == NULL) {
+ x_e_putc(BEL);
+ return KSTD;
+ }
+ if (xmp > xcp) {
+ rsize = xmp - xcp;
+ xr = xcp;
+ } else {
+ rsize = xcp - xmp;
+ xr = xmp;
+ }
+ x_goto(xr);
+ x_delete(rsize, true);
+ xmp = xr;
+ return KSTD;
+}
+
+static int
+x_xchg_point_mark(int c)
+{
+ char *tmp;
+
+ if (xmp == NULL) {
+ x_e_putc(BEL);
+ return KSTD;
+ }
+ tmp = xmp;
+ xmp = xcp;
+ x_goto( tmp );
+ return KSTD;
+}
+
+static int
+x_version(int c)
+{
+ char *o_xbuf = xbuf, *o_xend = xend;
+ char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp;
+ int lim = x_lastcp() - xbp;
+
+ xbuf = xbp = xcp = (char *) ksh_version + 4;
+ xend = xep = (char *) ksh_version + 4 + strlen(ksh_version + 4);
+ x_redraw(lim);
+ x_flush();
+
+ c = x_e_getc();
+ xbuf = o_xbuf;
+ xend = o_xend;
+ xbp = o_xbp;
+ xep = o_xep;
+ xcp = o_xcp;
+ x_redraw(strlen(ksh_version));
+
+ if (c < 0)
+ return KSTD;
+ /* This is what at&t ksh seems to do... Very bizarre */
+ if (c != ' ')
+ x_e_ungetc(c);
+
+ return KSTD;
+}
+
+static int
+x_noop(int c)
+{
+ return KSTD;
+}
+
+/*
+ * File/command name completion routines
+ */
+
+static int
+x_comp_comm(int c)
+{
+ do_complete(XCF_COMMAND, CT_COMPLETE);
+ return KSTD;
+}
+static int
+x_list_comm(int c)
+{
+ do_complete(XCF_COMMAND, CT_LIST);
+ return KSTD;
+}
+static int
+x_complete(int c)
+{
+ do_complete(XCF_COMMAND_FILE, CT_COMPLETE);
+ return KSTD;
+}
+static int
+x_enumerate(int c)
+{
+ do_complete(XCF_COMMAND_FILE, CT_LIST);
+ return KSTD;
+}
+static int
+x_comp_file(int c)
+{
+ do_complete(XCF_FILE, CT_COMPLETE);
+ return KSTD;
+}
+static int
+x_list_file(int c)
+{
+ do_complete(XCF_FILE, CT_LIST);
+ return KSTD;
+}
+static int
+x_comp_list(int c)
+{
+ do_complete(XCF_COMMAND_FILE, CT_COMPLIST);
+ return KSTD;
+}
+static int
+x_expand(int c)
+{
+ char **words;
+ int nwords = 0;
+ int start, end;
+ int is_command;
+ int i;
+
+ nwords = x_cf_glob(XCF_FILE, xbuf, xep - xbuf, xcp - xbuf,
+ &start, &end, &words, &is_command);
+
+ if (nwords == 0) {
+ x_e_putc(BEL);
+ return KSTD;
+ }
+
+ x_goto(xbuf + start);
+ x_delete(end - start, false);
+ for (i = 0; i < nwords;) {
+ if (x_escape(words[i], strlen(words[i]), x_emacs_putbuf) < 0 ||
+ (++i < nwords && x_ins(space) < 0)) {
+ x_e_putc(BEL);
+ return KSTD;
+ }
+ }
+ x_adjust();
+
+ return KSTD;
+}
+
+/* type == 0 for list, 1 for complete and 2 for complete-list */
+static void
+do_complete(int flags, /* XCF_{COMMAND,FILE,COMMAND_FILE} */
+ Comp_type type)
+{
+ char **words;
+ int nwords;
+ int start, end, nlen, olen;
+ int is_command;
+ int completed = 0;
+
+ nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf,
+ &start, &end, &words, &is_command);
+ /* no match */
+ if (nwords == 0) {
+ x_e_putc(BEL);
+ return;
+ }
+
+ if (type == CT_LIST) {
+ x_print_expansions(nwords, words, is_command);
+ x_redraw(0);
+ x_free_words(nwords, words);
+ return;
+ }
+
+ olen = end - start;
+ nlen = x_longest_prefix(nwords, words);
+ /* complete */
+ if (nwords == 1 || nlen > olen) {
+ x_goto(xbuf + start);
+ x_delete(olen, false);
+ x_escape(words[0], nlen, x_emacs_putbuf);
+ x_adjust();
+ completed = 1;
+ }
+ /* add space if single non-dir match */
+ if (nwords == 1 && words[0][nlen - 1] != '/') {
+ x_ins(space);
+ completed = 1;
+ }
+
+ if (type == CT_COMPLIST && !completed) {
+ x_print_expansions(nwords, words, is_command);
+ completed = 1;
+ }
+
+ if (completed)
+ x_redraw(0);
+
+ x_free_words(nwords, words);
+}
+
+/* NAME:
+ * x_adjust - redraw the line adjusting starting point etc.
+ *
+ * DESCRIPTION:
+ * This function is called when we have exceeded the bounds
+ * of the edit window. It increments x_adj_done so that
+ * functions like x_ins and x_delete know that we have been
+ * called and can skip the x_bs() stuff which has already
+ * been done by x_redraw.
+ *
+ * RETURN VALUE:
+ * None
+ */
+
+static void
+x_adjust(void)
+{
+ x_adj_done++; /* flag the fact that we were called. */
+ /*
+ * we had a problem if the prompt length > xx_cols / 2
+ */
+ if ((xbp = xcp - (x_displen / 2)) < xbuf)
+ xbp = xbuf;
+ xlp_valid = false;
+ x_redraw(xx_cols);
+ x_flush();
+}
+
+static int unget_char = -1;
+
+static void
+x_e_ungetc(int c)
+{
+ unget_char = c;
+}
+
+static int
+x_e_getc(void)
+{
+ int c;
+
+ if (unget_char >= 0) {
+ c = unget_char;
+ unget_char = -1;
+ } else if (macro_args) {
+ c = *macro_args++;
+ if (!c) {
+ macro_args = NULL;
+ c = x_getc();
+ }
+ } else
+ c = x_getc();
+
+ return c;
+}
+
+static void
+x_e_putc(int c)
+{
+ if (c == '\r' || c == '\n')
+ x_col = 0;
+ if (x_col < xx_cols) {
+ x_putc(c);
+ switch (c) {
+ case BEL:
+ break;
+ case '\r':
+ case '\n':
+ break;
+ case '\b':
+ x_col--;
+ break;
+ default:
+ x_col++;
+ break;
+ }
+ }
+ if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2)))
+ x_adjust();
+}
+
+#ifdef DEBUG
+static int
+x_debug_info(int c)
+{
+ x_flush();
+ shellf("\nksh debug:\n");
+ shellf("\tx_col == %d,\t\tx_cols == %d,\tx_displen == %d\n",
+ x_col, xx_cols, x_displen);
+ shellf("\txcp == 0x%lx,\txep == 0x%lx\n", (long) xcp, (long) xep);
+ shellf("\txbp == 0x%lx,\txbuf == 0x%lx\n", (long) xbp, (long) xbuf);
+ shellf("\txlp == 0x%lx\n", (long) xlp);
+ shellf("\txlp == 0x%lx\n", (long) x_lastcp());
+ shellf(newline);
+ x_redraw(-1);
+ return 0;
+}
+#endif
+
+static void
+x_e_puts(const char *s)
+{
+ int adj = x_adj_done;
+
+ while (*s && adj == x_adj_done)
+ x_e_putc(*s++);
+}
+
+/* NAME:
+ * x_set_arg - set an arg value for next function
+ *
+ * DESCRIPTION:
+ * This is a simple implementation of M-[0-9].
+ *
+ * RETURN VALUE:
+ * KSTD
+ */
+
+static int
+x_set_arg(int c)
+{
+ int n = 0;
+ int first = 1;
+
+ for (; c >= 0 && isdigit(c); c = x_e_getc(), first = 0)
+ n = n * 10 + (c - '0');
+ if (c < 0 || first) {
+ x_e_putc(BEL);
+ x_arg = 1;
+ x_arg_defaulted = 1;
+ } else {
+ x_e_ungetc(c);
+ x_arg = n;
+ x_arg_defaulted = 0;
+ x_arg_set = 1;
+ }
+ return KSTD;
+}
+
+
+/* Comment or uncomment the current line. */
+static int
+x_comment(int c)
+{
+ int oldsize = x_size_str(xbuf);
+ int len = xep - xbuf;
+ int ret = x_do_comment(xbuf, xend - xbuf, &len);
+
+ if (ret < 0)
+ x_e_putc(BEL);
+ else {
+ xep = xbuf + len;
+ *xep = '\0';
+ xcp = xbp = xbuf;
+ x_redraw(oldsize);
+ if (ret > 0)
+ return x_newline('\n');
+ }
+ return KSTD;
+}
+
+
+/* NAME:
+ * x_prev_histword - recover word from prev command
+ *
+ * DESCRIPTION:
+ * This function recovers the last word from the previous
+ * command and inserts it into the current edit line. If a
+ * numeric arg is supplied then the n'th word from the
+ * start of the previous command is used.
+ *
+ * Bound to M-.
+ *
+ * RETURN VALUE:
+ * KSTD
+ */
+
+static int
+x_prev_histword(int c)
+{
+ char *rcp;
+ char *cp;
+
+ cp = *histptr;
+ if (!cp)
+ x_e_putc(BEL);
+ else if (x_arg_defaulted) {
+ rcp = &cp[strlen(cp) - 1];
+ /*
+ * ignore white-space after the last word
+ */
+ while (rcp > cp && is_cfs(*rcp))
+ rcp--;
+ while (rcp > cp && !is_cfs(*rcp))
+ rcp--;
+ if (is_cfs(*rcp))
+ rcp++;
+ x_ins(rcp);
+ } else {
+ int c;
+
+ rcp = cp;
+ /*
+ * ignore white-space at start of line
+ */
+ while (*rcp && is_cfs(*rcp))
+ rcp++;
+ while (x_arg-- > 1) {
+ while (*rcp && !is_cfs(*rcp))
+ rcp++;
+ while (*rcp && is_cfs(*rcp))
+ rcp++;
+ }
+ cp = rcp;
+ while (*rcp && !is_cfs(*rcp))
+ rcp++;
+ c = *rcp;
+ *rcp = '\0';
+ x_ins(cp);
+ *rcp = c;
+ }
+ return KSTD;
+}
+
+/* Uppercase N(1) words */
+static int
+x_fold_upper(int c)
+{
+ return x_fold_case('U');
+}
+
+/* Lowercase N(1) words */
+static int
+x_fold_lower(int c)
+{
+ return x_fold_case('L');
+}
+
+/* Lowercase N(1) words */
+static int
+x_fold_capitalize(int c)
+{
+ return x_fold_case('C');
+}
+
+/* NAME:
+ * x_fold_case - convert word to UPPER/lower/Capital case
+ *
+ * DESCRIPTION:
+ * This function is used to implement M-U,M-u,M-L,M-l,M-C and M-c
+ * to UPPER case, lower case or Capitalize words.
+ *
+ * RETURN VALUE:
+ * None
+ */
+
+static int
+x_fold_case(int c)
+{
+ char *cp = xcp;
+
+ if (cp == xep) {
+ x_e_putc(BEL);
+ return KSTD;
+ }
+ while (x_arg--) {
+ /*
+ * first skip over any white-space
+ */
+ while (cp != xep && is_mfs(*cp))
+ cp++;
+ /*
+ * do the first char on its own since it may be
+ * a different action than for the rest.
+ */
+ if (cp != xep) {
+ if (c == 'L') { /* lowercase */
+ if (isupper((unsigned char)*cp))
+ *cp = tolower((unsigned char)*cp);
+ } else { /* uppercase, capitalize */
+ if (islower((unsigned char)*cp))
+ *cp = toupper((unsigned char)*cp);
+ }
+ cp++;
+ }
+ /*
+ * now for the rest of the word
+ */
+ while (cp != xep && !is_mfs(*cp)) {
+ if (c == 'U') { /* uppercase */
+ if (islower((unsigned char)*cp))
+ *cp = toupper((unsigned char)*cp);
+ } else { /* lowercase, capitalize */
+ if (isupper((unsigned char)*cp))
+ *cp = tolower((unsigned char)*cp);
+ }
+ cp++;
+ }
+ }
+ x_goto(cp);
+ return KSTD;
+}
+
+/* NAME:
+ * x_lastcp - last visible char
+ *
+ * SYNOPSIS:
+ * x_lastcp()
+ *
+ * DESCRIPTION:
+ * This function returns a pointer to that char in the
+ * edit buffer that will be the last displayed on the
+ * screen. The sequence:
+ *
+ * for (cp = x_lastcp(); cp > xcp; cp)
+ * x_bs(*--cp);
+ *
+ * Will position the cursor correctly on the screen.
+ *
+ * RETURN VALUE:
+ * cp or NULL
+ */
+
+static char *
+x_lastcp(void)
+{
+ char *rcp;
+ int i;
+
+ if (!xlp_valid) {
+ for (i = 0, rcp = xbp; rcp < xep && i < x_displen; rcp++)
+ i += x_size((unsigned char)*rcp);
+ xlp = rcp;
+ }
+ xlp_valid = true;
+ return (xlp);
+}
+
+#endif /* EDIT */
diff --git a/eval.c b/eval.c
@@ -0,0 +1,1333 @@
+/* $OpenBSD: eval.c,v 1.40 2013/09/14 20:09:30 millert Exp $ */
+
+/*
+ * Expansion - quoting, separation, substitution, globbing
+ */
+
+#include "sh.h"
+#include <pwd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+/*
+ * string expansion
+ *
+ * first pass: quoting, IFS separation, ~, ${}, $() and $(()) substitution.
+ * second pass: alternation ({,}), filename expansion (*?[]).
+ */
+
+/* expansion generator state */
+typedef struct Expand {
+ /* int type; */ /* see expand() */
+ const char *str; /* string */
+ union {
+ const char **strv;/* string[] */
+ struct shf *shf;/* file */
+ } u; /* source */
+ struct tbl *var; /* variable in ${var..} */
+ short split; /* split "$@" / call waitlast $() */
+} Expand;
+
+#define XBASE 0 /* scanning original */
+#define XSUB 1 /* expanding ${} string */
+#define XARGSEP 2 /* ifs0 between "$*" */
+#define XARG 3 /* expanding $*, $@ */
+#define XCOM 4 /* expanding $() */
+#define XNULLSUB 5 /* "$@" when $# is 0 (don't generate word) */
+#define XSUBMID 6 /* middle of expanding ${} */
+
+/* States used for field splitting */
+#define IFS_WORD 0 /* word has chars (or quotes) */
+#define IFS_WS 1 /* have seen IFS white-space */
+#define IFS_NWS 2 /* have seen IFS non-white-space */
+
+static int varsub(Expand *, char *, char *, int *, int *);
+static int comsub(Expand *, char *);
+static char *trimsub(char *, char *, int);
+static void glob(char *, XPtrV *, int);
+static void globit(XString *, char **, char *, XPtrV *, int);
+static char *maybe_expand_tilde(char *, XString *, char **, int);
+static char *tilde(char *);
+static char *homedir(char *);
+#ifdef BRACE_EXPAND
+static void alt_expand(XPtrV *, char *, char *, char *, int);
+#endif
+
+/* compile and expand word */
+char *
+substitute(const char *cp, int f)
+{
+ struct source *s, *sold;
+
+ sold = source;
+ s = pushs(SWSTR, ATEMP);
+ s->start = s->str = cp;
+ source = s;
+ if (yylex(ONEWORD) != LWORD)
+ internal_errorf(1, "substitute");
+ source = sold;
+ afree(s, ATEMP);
+ return evalstr(yylval.cp, f);
+}
+
+/*
+ * expand arg-list
+ */
+char **
+eval(char **ap, int f)
+{
+ XPtrV w;
+
+ if (*ap == NULL)
+ return ap;
+ XPinit(w, 32);
+ XPput(w, NULL); /* space for shell name */
+ while (*ap != NULL)
+ expand(*ap++, &w, f);
+ XPput(w, NULL);
+ return (char **) XPclose(w) + 1;
+}
+
+/*
+ * expand string
+ */
+char *
+evalstr(char *cp, int f)
+{
+ XPtrV w;
+
+ XPinit(w, 1);
+ expand(cp, &w, f);
+ cp = (XPsize(w) == 0) ? null : (char*) *XPptrv(w);
+ XPfree(w);
+ return cp;
+}
+
+/*
+ * expand string - return only one component
+ * used from iosetup to expand redirection files
+ */
+char *
+evalonestr(char *cp, int f)
+{
+ XPtrV w;
+
+ XPinit(w, 1);
+ expand(cp, &w, f);
+ switch (XPsize(w)) {
+ case 0:
+ cp = null;
+ break;
+ case 1:
+ cp = (char*) *XPptrv(w);
+ break;
+ default:
+ cp = evalstr(cp, f&~DOGLOB);
+ break;
+ }
+ XPfree(w);
+ return cp;
+}
+
+/* for nested substitution: ${var:=$var2} */
+typedef struct SubType {
+ short stype; /* [=+-?%#] action after expanded word */
+ short base; /* begin position of expanded word */
+ short f; /* saved value of f (DOPAT, etc) */
+ struct tbl *var; /* variable for ${var..} */
+ short quote; /* saved value of quote (for ${..[%#]..}) */
+ struct SubType *prev; /* old type */
+ struct SubType *next; /* poped type (to avoid re-allocating) */
+} SubType;
+
+void
+expand(char *cp, /* input word */
+ XPtrV *wp, /* output words */
+ int f) /* DO* flags */
+{
+ int c = 0;
+ int type; /* expansion type */
+ int quote = 0; /* quoted */
+ XString ds; /* destination string */
+ char *dp, *sp; /* dest., source */
+ int fdo, word; /* second pass flags; have word */
+ int doblank; /* field splitting of parameter/command subst */
+ Expand x = {
+ /* expansion variables */
+ NULL, { NULL }, NULL, 0
+ };
+ SubType st_head, *st;
+ int newlines = 0; /* For trailing newlines in COMSUB */
+ int saw_eq, tilde_ok;
+ int make_magic;
+ size_t len;
+
+ if (cp == NULL)
+ internal_errorf(1, "expand(NULL)");
+ /* for alias, readonly, set, typeset commands */
+ if ((f & DOVACHECK) && is_wdvarassign(cp)) {
+ f &= ~(DOVACHECK|DOBLANK|DOGLOB|DOTILDE);
+ f |= DOASNTILDE;
+ }
+ if (Flag(FNOGLOB))
+ f &= ~DOGLOB;
+ if (Flag(FMARKDIRS))
+ f |= DOMARKDIRS;
+#ifdef BRACE_EXPAND
+ if (Flag(FBRACEEXPAND) && (f & DOGLOB))
+ f |= DOBRACE_;
+#endif /* BRACE_EXPAND */
+
+ Xinit(ds, dp, 128, ATEMP); /* init dest. string */
+ type = XBASE;
+ sp = cp;
+ fdo = 0;
+ saw_eq = 0;
+ tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0; /* must be 1/0 */
+ doblank = 0;
+ make_magic = 0;
+ word = (f&DOBLANK) ? IFS_WS : IFS_WORD;
+ st_head.next = (SubType *) 0;
+ st = &st_head;
+
+ while (1) {
+ Xcheck(ds, dp);
+
+ switch (type) {
+ case XBASE: /* original prefixed string */
+ c = *sp++;
+ switch (c) {
+ case EOS:
+ c = 0;
+ break;
+ case CHAR:
+ c = *sp++;
+ break;
+ case QCHAR:
+ quote |= 2; /* temporary quote */
+ c = *sp++;
+ break;
+ case OQUOTE:
+ word = IFS_WORD;
+ tilde_ok = 0;
+ quote = 1;
+ continue;
+ case CQUOTE:
+ quote = 0;
+ continue;
+ case COMSUB:
+ tilde_ok = 0;
+ if (f & DONTRUNCOMMAND) {
+ word = IFS_WORD;
+ *dp++ = '$'; *dp++ = '(';
+ while (*sp != '\0') {
+ Xcheck(ds, dp);
+ *dp++ = *sp++;
+ }
+ *dp++ = ')';
+ } else {
+ type = comsub(&x, sp);
+ if (type == XCOM && (f&DOBLANK))
+ doblank++;
+ sp = strchr(sp, 0) + 1;
+ newlines = 0;
+ }
+ continue;
+ case EXPRSUB:
+ word = IFS_WORD;
+ tilde_ok = 0;
+ if (f & DONTRUNCOMMAND) {
+ *dp++ = '$'; *dp++ = '('; *dp++ = '(';
+ while (*sp != '\0') {
+ Xcheck(ds, dp);
+ *dp++ = *sp++;
+ }
+ *dp++ = ')'; *dp++ = ')';
+ } else {
+ struct tbl v;
+ char *p;
+
+ v.flag = DEFINED|ISSET|INTEGER;
+ v.type = 10; /* not default */
+ v.name[0] = '\0';
+ v_evaluate(&v, substitute(sp, 0),
+ KSH_UNWIND_ERROR, true);
+ sp = strchr(sp, 0) + 1;
+ for (p = str_val(&v); *p; ) {
+ Xcheck(ds, dp);
+ *dp++ = *p++;
+ }
+ }
+ continue;
+ case OSUBST: /* ${{#}var{:}[=+-?#%]word} */
+ /* format is:
+ * OSUBST [{x] plain-variable-part \0
+ * compiled-word-part CSUBST [}x]
+ * This is where all syntax checking gets done...
+ */
+ {
+ char *varname = ++sp; /* skip the { or x (}) */
+ int stype;
+ int slen = 0;
+
+ sp = strchr(sp, '\0') + 1; /* skip variable */
+ type = varsub(&x, varname, sp, &stype, &slen);
+ if (type < 0) {
+ char endc;
+ char *str, *end;
+
+ sp = varname - 2; /* restore sp */
+ end = (char *) wdscan(sp, CSUBST);
+ /* ({) the } or x is already skipped */
+ endc = *end;
+ *end = EOS;
+ str = snptreef(NULL, 64, "%S", sp);
+ *end = endc;
+ errorf("%s: bad substitution", str);
+ }
+ if (f&DOBLANK)
+ doblank++;
+ tilde_ok = 0;
+ if (type == XBASE) { /* expand? */
+ if (!st->next) {
+ SubType *newst;
+
+ newst = (SubType *) alloc(
+ sizeof(SubType), ATEMP);
+ newst->next = (SubType *) 0;
+ newst->prev = st;
+ st->next = newst;
+ }
+ st = st->next;
+ st->stype = stype;
+ st->base = Xsavepos(ds, dp);
+ st->f = f;
+ st->var = x.var;
+ st->quote = quote;
+ /* skip qualifier(s) */
+ if (stype)
+ sp += slen;
+ switch (stype & 0x7f) {
+ case '#':
+ case '%':
+ /* ! DOBLANK,DOBRACE_,DOTILDE */
+ f = DOPAT | (f&DONTRUNCOMMAND) |
+ DOTEMP_;
+ quote = 0;
+ /* Prepend open pattern (so |
+ * in a trim will work as
+ * expected)
+ */
+ *dp++ = MAGIC;
+ *dp++ = '@' + 0x80;
+ break;
+ case '=':
+ /* Enabling tilde expansion
+ * after :'s here is
+ * non-standard ksh, but is
+ * consistent with rules for
+ * other assignments. Not
+ * sure what POSIX thinks of
+ * this.
+ * Not doing tilde expansion
+ * for integer variables is a
+ * non-POSIX thing - makes
+ * sense though, since ~ is
+ * a arithmetic operator.
+ */
+ if (!(x.var->flag & INTEGER))
+ f |= DOASNTILDE|DOTILDE;
+ f |= DOTEMP_;
+ /* These will be done after the
+ * value has been assigned.
+ */
+ f &= ~(DOBLANK|DOGLOB|DOBRACE_);
+ tilde_ok = 1;
+ break;
+ case '?':
+ f &= ~DOBLANK;
+ f |= DOTEMP_;
+ /* FALLTHROUGH */
+ default:
+ /* Enable tilde expansion */
+ tilde_ok = 1;
+ f |= DOTILDE;
+ }
+ } else
+ /* skip word */
+ sp = (char *) wdscan(sp, CSUBST);
+ continue;
+ }
+ case CSUBST: /* only get here if expanding word */
+ sp++; /* ({) skip the } or x */
+ tilde_ok = 0; /* in case of ${unset:-} */
+ *dp = '\0';
+ quote = st->quote;
+ f = st->f;
+ if (f&DOBLANK)
+ doblank--;
+ switch (st->stype&0x7f) {
+ case '#':
+ case '%':
+ /* Append end-pattern */
+ *dp++ = MAGIC; *dp++ = ')'; *dp = '\0';
+ dp = Xrestpos(ds, dp, st->base);
+ /* Must use st->var since calling
+ * global would break things
+ * like x[i+=1].
+ */
+ x.str = trimsub(str_val(st->var),
+ dp, st->stype);
+ if (x.str[0] != '\0' || st->quote)
+ type = XSUB;
+ else
+ type = XNULLSUB;
+ if (f&DOBLANK)
+ doblank++;
+ st = st->prev;
+ continue;
+ case '=':
+ /* Restore our position and substitute
+ * the value of st->var (may not be
+ * the assigned value in the presence
+ * of integer/right-adj/etc attributes).
+ */
+ dp = Xrestpos(ds, dp, st->base);
+ /* Must use st->var since calling
+ * global would cause with things
+ * like x[i+=1] to be evaluated twice.
+ */
+ /* Note: not exported by FEXPORT
+ * in at&t ksh.
+ */
+ /* XXX POSIX says readonly is only
+ * fatal for special builtins (setstr
+ * does readonly check).
+ */
+ len = strlen(dp) + 1;
+ setstr(st->var,
+ debunk((char *) alloc(len, ATEMP),
+ dp, len), KSH_UNWIND_ERROR);
+ x.str = str_val(st->var);
+ type = XSUB;
+ if (f&DOBLANK)
+ doblank++;
+ st = st->prev;
+ continue;
+ case '?':
+ {
+ char *s = Xrestpos(ds, dp, st->base);
+
+ errorf("%s: %s", st->var->name,
+ dp == s ?
+ "parameter null or not set" :
+ (debunk(s, s, strlen(s) + 1), s));
+ }
+ }
+ st = st->prev;
+ type = XBASE;
+ continue;
+
+ case OPAT: /* open pattern: *(foo|bar) */
+ /* Next char is the type of pattern */
+ make_magic = 1;
+ c = *sp++ + 0x80;
+ break;
+
+ case SPAT: /* pattern separator (|) */
+ make_magic = 1;
+ c = '|';
+ break;
+
+ case CPAT: /* close pattern */
+ make_magic = 1;
+ c = /*(*/ ')';
+ break;
+ }
+ break;
+
+ case XNULLSUB:
+ /* Special case for "$@" (and "${foo[@]}") - no
+ * word is generated if $# is 0 (unless there is
+ * other stuff inside the quotes).
+ */
+ type = XBASE;
+ if (f&DOBLANK) {
+ doblank--;
+ /* not really correct: x=; "$x$@" should
+ * generate a null argument and
+ * set A; "${@:+}" shouldn't.
+ */
+ if (dp == Xstring(ds, dp))
+ word = IFS_WS;
+ }
+ continue;
+
+ case XSUB:
+ case XSUBMID:
+ if ((c = *x.str++) == 0) {
+ type = XBASE;
+ if (f&DOBLANK)
+ doblank--;
+ continue;
+ }
+ break;
+
+ case XARGSEP:
+ type = XARG;
+ quote = 1;
+ case XARG:
+ if ((c = *x.str++) == '\0') {
+ /* force null words to be created so
+ * set -- '' 2 ''; foo "$@" will do
+ * the right thing
+ */
+ if (quote && x.split)
+ word = IFS_WORD;
+ if ((x.str = *x.u.strv++) == NULL) {
+ type = XBASE;
+ if (f&DOBLANK)
+ doblank--;
+ continue;
+ }
+ c = ifs0;
+ if (c == 0) {
+ if (quote && !x.split)
+ continue;
+ c = ' ';
+ }
+ if (quote && x.split) {
+ /* terminate word for "$@" */
+ type = XARGSEP;
+ quote = 0;
+ }
+ }
+ break;
+
+ case XCOM:
+ if (x.u.shf == NULL) /* $(< ...) failed, fake EOF */
+ c = EOF;
+ else if (newlines) { /* Spit out saved nl's */
+ c = '\n';
+ --newlines;
+ } else {
+ while ((c = shf_getc(x.u.shf)) == 0 || c == '\n')
+ if (c == '\n')
+ newlines++; /* Save newlines */
+ if (newlines && c != EOF) {
+ shf_ungetc(c, x.u.shf);
+ c = '\n';
+ --newlines;
+ }
+ }
+ if (c == EOF) {
+ newlines = 0;
+ if (x.u.shf != NULL)
+ shf_close(x.u.shf);
+ if (x.split)
+ subst_exstat = waitlast();
+ else
+ subst_exstat = (x.u.shf == NULL);
+ type = XBASE;
+ if (f&DOBLANK)
+ doblank--;
+ continue;
+ }
+ break;
+ }
+
+ /* check for end of word or IFS separation */
+ if (c == 0 || (!quote && (f & DOBLANK) && doblank &&
+ !make_magic && ctype(c, C_IFS))) {
+ /* How words are broken up:
+ * | value of c
+ * word | ws nws 0
+ * -----------------------------------
+ * IFS_WORD w/WS w/NWS w
+ * IFS_WS -/WS w/NWS -
+ * IFS_NWS -/NWS w/NWS w
+ * (w means generate a word)
+ * Note that IFS_NWS/0 generates a word (at&t ksh
+ * doesn't do this, but POSIX does).
+ */
+ if (word == IFS_WORD ||
+ (!ctype(c, C_IFSWS) && c && word == IFS_NWS)) {
+ char *p;
+
+ *dp++ = '\0';
+ p = Xclose(ds, dp);
+#ifdef BRACE_EXPAND
+ if (fdo & DOBRACE_)
+ /* also does globbing */
+ alt_expand(wp, p, p,
+ p + Xlength(ds, (dp - 1)),
+ fdo | (f & DOMARKDIRS));
+ else
+#endif /* BRACE_EXPAND */
+ if (fdo & DOGLOB)
+ glob(p, wp, f & DOMARKDIRS);
+ else if ((f & DOPAT) || !(fdo & DOMAGIC_))
+ XPput(*wp, p);
+ else
+ XPput(*wp, debunk(p, p, strlen(p) + 1));
+ fdo = 0;
+ saw_eq = 0;
+ tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0;
+ if (c != 0)
+ Xinit(ds, dp, 128, ATEMP);
+ }
+ if (c == 0)
+ return;
+ if (word != IFS_NWS)
+ word = ctype(c, C_IFSWS) ? IFS_WS : IFS_NWS;
+ } else {
+ if (type == XSUB) {
+ if (word == IFS_NWS &&
+ Xlength(ds, dp) == 0) {
+ char *p;
+
+ if ((p = strdup("")) == NULL)
+ internal_errorf(1, "unable "
+ "to allocate memory");
+ XPput(*wp, p);
+ }
+ type = XSUBMID;
+ }
+
+ /* age tilde_ok info - ~ code tests second bit */
+ tilde_ok <<= 1;
+ /* mark any special second pass chars */
+ if (!quote)
+ switch (c) {
+ case '[':
+ case NOT:
+ case '-':
+ case ']':
+ /* For character classes - doesn't hurt
+ * to have magic !,-,]'s outside of
+ * [...] expressions.
+ */
+ if (f & (DOPAT | DOGLOB)) {
+ fdo |= DOMAGIC_;
+ if (c == '[')
+ fdo |= f & DOGLOB;
+ *dp++ = MAGIC;
+ }
+ break;
+ case '*':
+ case '?':
+ if (f & (DOPAT | DOGLOB)) {
+ fdo |= DOMAGIC_ | (f & DOGLOB);
+ *dp++ = MAGIC;
+ }
+ break;
+#ifdef BRACE_EXPAND
+ case OBRACE:
+ case ',':
+ case CBRACE:
+ if ((f & DOBRACE_) && (c == OBRACE ||
+ (fdo & DOBRACE_))) {
+ fdo |= DOBRACE_|DOMAGIC_;
+ *dp++ = MAGIC;
+ }
+ break;
+#endif /* BRACE_EXPAND */
+ case '=':
+ /* Note first unquoted = for ~ */
+ if (!(f & DOTEMP_) && !saw_eq) {
+ saw_eq = 1;
+ tilde_ok = 1;
+ }
+ break;
+ case ':': /* : */
+ /* Note unquoted : for ~ */
+ if (!(f & DOTEMP_) && (f & DOASNTILDE))
+ tilde_ok = 1;
+ break;
+ case '~':
+ /* tilde_ok is reset whenever
+ * any of ' " $( $(( ${ } are seen.
+ * Note that tilde_ok must be preserved
+ * through the sequence ${A=a=}~
+ */
+ if (type == XBASE &&
+ (f & (DOTILDE|DOASNTILDE)) &&
+ (tilde_ok & 2)) {
+ char *p, *dp_x;
+
+ dp_x = dp;
+ p = maybe_expand_tilde(sp,
+ &ds, &dp_x,
+ f & DOASNTILDE);
+ if (p) {
+ if (dp != dp_x)
+ word = IFS_WORD;
+ dp = dp_x;
+ sp = p;
+ continue;
+ }
+ }
+ break;
+ }
+ else
+ quote &= ~2; /* undo temporary */
+
+ if (make_magic) {
+ make_magic = 0;
+ fdo |= DOMAGIC_ | (f & DOGLOB);
+ *dp++ = MAGIC;
+ } else if (ISMAGIC(c)) {
+ fdo |= DOMAGIC_;
+ *dp++ = MAGIC;
+ }
+ *dp++ = c; /* save output char */
+ word = IFS_WORD;
+ }
+ }
+}
+
+/*
+ * Prepare to generate the string returned by ${} substitution.
+ */
+static int
+varsub(Expand *xp, char *sp, char *word,
+ int *stypep, /* becomes qualifier type */
+ int *slenp) /* " " len (=, :=, etc.) valid iff *stypep != 0 */
+{
+ int c;
+ int state; /* next state: XBASE, XARG, XSUB, XNULLSUB */
+ int stype; /* substitution type */
+ int slen;
+ char *p;
+ struct tbl *vp;
+
+ if (sp[0] == '\0') /* Bad variable name */
+ return -1;
+
+ xp->var = (struct tbl *) 0;
+
+ /* ${#var}, string length or array size */
+ if (sp[0] == '#' && (c = sp[1]) != '\0') {
+ int zero_ok = 0;
+
+ /* Can't have any modifiers for ${#...} */
+ if (*word != CSUBST)
+ return -1;
+ sp++;
+ /* Check for size of array */
+ if ((p=strchr(sp,'[')) && (p[1]=='*'||p[1]=='@') && p[2]==']') {
+ int n = 0;
+
+ vp = global(arrayname(sp));
+ if (vp->flag & (ISSET|ARRAY))
+ zero_ok = 1;
+ for (; vp; vp = vp->u.array)
+ if (vp->flag & ISSET)
+ n++;
+ c = n; /* ksh88/ksh93 go for number, not max index */
+ } else if (c == '*' || c == '@')
+ c = e->loc->argc;
+ else {
+ p = str_val(global(sp));
+ zero_ok = p != null;
+ c = strlen(p);
+ }
+ if (Flag(FNOUNSET) && c == 0 && !zero_ok)
+ errorf("%s: parameter not set", sp);
+ *stypep = 0; /* unqualified variable/string substitution */
+ xp->str = str_save(ulton((unsigned long)c, 10), ATEMP);
+ return XSUB;
+ }
+
+ /* Check for qualifiers in word part */
+ stype = 0;
+ c = word[slen = 0] == CHAR ? word[1] : 0;
+ if (c == ':') {
+ slen += 2;
+ stype = 0x80;
+ c = word[slen + 0] == CHAR ? word[slen + 1] : 0;
+ }
+ if (ctype(c, C_SUBOP1)) {
+ slen += 2;
+ stype |= c;
+ } else if (ctype(c, C_SUBOP2)) { /* Note: ksh88 allows :%, :%%, etc */
+ slen += 2;
+ stype = c;
+ if (word[slen + 0] == CHAR && c == word[slen + 1]) {
+ stype |= 0x80;
+ slen += 2;
+ }
+ } else if (stype) /* : is not ok */
+ return -1;
+ if (!stype && *word != CSUBST)
+ return -1;
+ *stypep = stype;
+ *slenp = slen;
+
+ c = sp[0];
+ if (c == '*' || c == '@') {
+ switch (stype & 0x7f) {
+ case '=': /* can't assign to a vector */
+ case '%': /* can't trim a vector (yet) */
+ case '#':
+ return -1;
+ }
+ if (e->loc->argc == 0) {
+ xp->str = null;
+ xp->var = global(sp);
+ state = c == '@' ? XNULLSUB : XSUB;
+ } else {
+ xp->u.strv = (const char **) e->loc->argv + 1;
+ xp->str = *xp->u.strv++;
+ xp->split = c == '@'; /* $@ */
+ state = XARG;
+ }
+ } else {
+ if ((p=strchr(sp,'[')) && (p[1]=='*'||p[1]=='@') && p[2]==']') {
+ XPtrV wv;
+
+ switch (stype & 0x7f) {
+ case '=': /* can't assign to a vector */
+ case '%': /* can't trim a vector (yet) */
+ case '#':
+ case '?':
+ return -1;
+ }
+ XPinit(wv, 32);
+ vp = global(arrayname(sp));
+ for (; vp; vp = vp->u.array) {
+ if (!(vp->flag&ISSET))
+ continue;
+ XPput(wv, str_val(vp));
+ }
+ if (XPsize(wv) == 0) {
+ xp->str = null;
+ state = p[1] == '@' ? XNULLSUB : XSUB;
+ XPfree(wv);
+ } else {
+ XPput(wv, 0);
+ xp->u.strv = (const char **) XPptrv(wv);
+ xp->str = *xp->u.strv++;
+ xp->split = p[1] == '@'; /* ${foo[@]} */
+ state = XARG;
+ }
+ } else {
+ /* Can't assign things like $! or $1 */
+ if ((stype & 0x7f) == '=' &&
+ (ctype(*sp, C_VAR1) || digit(*sp)))
+ return -1;
+ xp->var = global(sp);
+ xp->str = str_val(xp->var);
+ state = XSUB;
+ }
+ }
+
+ c = stype&0x7f;
+ /* test the compiler's code generator */
+ if (ctype(c, C_SUBOP2) ||
+ (((stype&0x80) ? *xp->str=='\0' : xp->str==null) ? /* undef? */
+ c == '=' || c == '-' || c == '?' : c == '+'))
+ state = XBASE; /* expand word instead of variable value */
+ if (Flag(FNOUNSET) && xp->str == null &&
+ (ctype(c, C_SUBOP2) || (state != XBASE && c != '+')))
+ errorf("%s: parameter not set", sp);
+ return state;
+}
+
+/*
+ * Run the command in $(...) and read its output.
+ */
+static int
+comsub(Expand *xp, char *cp)
+{
+ Source *s, *sold;
+ struct op *t;
+ struct shf *shf;
+
+ s = pushs(SSTRING, ATEMP);
+ s->start = s->str = cp;
+ sold = source;
+ t = compile(s);
+ afree(s, ATEMP);
+ source = sold;
+
+ if (t == NULL)
+ return XBASE;
+
+ if (t != NULL && t->type == TCOM && /* $(<file) */
+ *t->args == NULL && *t->vars == NULL && t->ioact != NULL) {
+ struct ioword *io = *t->ioact;
+ char *name;
+
+ if ((io->flag&IOTYPE) != IOREAD)
+ errorf("funny $() command: %s",
+ snptreef((char *) 0, 32, "%R", io));
+ shf = shf_open(name = evalstr(io->name, DOTILDE), O_RDONLY, 0,
+ SHF_MAPHI|SHF_CLEXEC);
+ if (shf == NULL)
+ warningf(!Flag(FTALKING),
+ "%s: cannot open $(<) input", name);
+ xp->split = 0; /* no waitlast() */
+ } else {
+ int ofd1, pv[2];
+ openpipe(pv);
+ shf = shf_fdopen(pv[0], SHF_RD, (struct shf *) 0);
+ ofd1 = savefd(1);
+ if (pv[1] != 1) {
+ ksh_dup2(pv[1], 1, false);
+ close(pv[1]);
+ }
+ execute(t, XFORK|XXCOM|XPIPEO, NULL);
+ restfd(1, ofd1);
+ startlast();
+ xp->split = 1; /* waitlast() */
+ }
+
+ xp->u.shf = shf;
+ return XCOM;
+}
+
+/*
+ * perform #pattern and %pattern substitution in ${}
+ */
+
+static char *
+trimsub(char *str, char *pat, int how)
+{
+ char *end = strchr(str, 0);
+ char *p, c;
+
+ switch (how&0xff) { /* UCHAR_MAX maybe? */
+ case '#': /* shortest at beginning */
+ for (p = str; p <= end; p++) {
+ c = *p; *p = '\0';
+ if (gmatch(str, pat, false)) {
+ *p = c;
+ return p;
+ }
+ *p = c;
+ }
+ break;
+ case '#'|0x80: /* longest match at beginning */
+ for (p = end; p >= str; p--) {
+ c = *p; *p = '\0';
+ if (gmatch(str, pat, false)) {
+ *p = c;
+ return p;
+ }
+ *p = c;
+ }
+ break;
+ case '%': /* shortest match at end */
+ for (p = end; p >= str; p--) {
+ if (gmatch(p, pat, false))
+ return str_nsave(str, p - str, ATEMP);
+ }
+ break;
+ case '%'|0x80: /* longest match at end */
+ for (p = str; p <= end; p++) {
+ if (gmatch(p, pat, false))
+ return str_nsave(str, p - str, ATEMP);
+ }
+ break;
+ }
+
+ return str; /* no match, return string */
+}
+
+/*
+ * glob
+ * Name derived from V6's /etc/glob, the program that expanded filenames.
+ */
+
+/* XXX cp not const 'cause slashes are temporarily replaced with nulls... */
+static void
+glob(char *cp, XPtrV *wp, int markdirs)
+{
+ int oldsize = XPsize(*wp);
+
+ if (glob_str(cp, wp, markdirs) == 0)
+ XPput(*wp, debunk(cp, cp, strlen(cp) + 1));
+ else
+ qsortp(XPptrv(*wp) + oldsize, (size_t)(XPsize(*wp) - oldsize),
+ xstrcmp);
+}
+
+#define GF_NONE 0
+#define GF_EXCHECK BIT(0) /* do existence check on file */
+#define GF_GLOBBED BIT(1) /* some globbing has been done */
+#define GF_MARKDIR BIT(2) /* add trailing / to directories */
+
+/* Apply file globbing to cp and store the matching files in wp. Returns
+ * the number of matches found.
+ */
+int
+glob_str(char *cp, XPtrV *wp, int markdirs)
+{
+ int oldsize = XPsize(*wp);
+ XString xs;
+ char *xp;
+
+ Xinit(xs, xp, 256, ATEMP);
+ globit(&xs, &xp, cp, wp, markdirs ? GF_MARKDIR : GF_NONE);
+ Xfree(xs, xp);
+
+ return XPsize(*wp) - oldsize;
+}
+
+static void
+globit(XString *xs, /* dest string */
+ char **xpp, /* ptr to dest end */
+ char *sp, /* source path */
+ XPtrV *wp, /* output list */
+ int check) /* GF_* flags */
+{
+ char *np; /* next source component */
+ char *xp = *xpp;
+ char *se;
+ char odirsep;
+
+ /* This to allow long expansions to be interrupted */
+ intrcheck();
+
+ if (sp == NULL) { /* end of source path */
+ /* We only need to check if the file exists if a pattern
+ * is followed by a non-pattern (eg, foo*x/bar; no check
+ * is needed for foo* since the match must exist) or if
+ * any patterns were expanded and the markdirs option is set.
+ * Symlinks make things a bit tricky...
+ */
+ if ((check & GF_EXCHECK) ||
+ ((check & GF_MARKDIR) && (check & GF_GLOBBED))) {
+#define stat_check() (stat_done ? stat_done : \
+ (stat_done = stat(Xstring(*xs, xp), &statb) < 0 \
+ ? -1 : 1))
+ struct stat lstatb, statb;
+ int stat_done = 0; /* -1: failed, 1 ok */
+
+ if (lstat(Xstring(*xs, xp), &lstatb) < 0)
+ return;
+ /* special case for systems which strip trailing
+ * slashes from regular files (eg, /etc/passwd/).
+ * SunOS 4.1.3 does this...
+ */
+ if ((check & GF_EXCHECK) && xp > Xstring(*xs, xp) &&
+ xp[-1] == '/' && !S_ISDIR(lstatb.st_mode) &&
+ (!S_ISLNK(lstatb.st_mode) ||
+ stat_check() < 0 || !S_ISDIR(statb.st_mode)))
+ return;
+ /* Possibly tack on a trailing / if there isn't already
+ * one and if the file is a directory or a symlink to a
+ * directory
+ */
+ if (((check & GF_MARKDIR) && (check & GF_GLOBBED)) &&
+ xp > Xstring(*xs, xp) && xp[-1] != '/' &&
+ (S_ISDIR(lstatb.st_mode) ||
+ (S_ISLNK(lstatb.st_mode) && stat_check() > 0 &&
+ S_ISDIR(statb.st_mode)))) {
+ *xp++ = '/';
+ *xp = '\0';
+ }
+ }
+ XPput(*wp, str_nsave(Xstring(*xs, xp), Xlength(*xs, xp), ATEMP));
+ return;
+ }
+
+ if (xp > Xstring(*xs, xp))
+ *xp++ = '/';
+ while (*sp == '/') {
+ Xcheck(*xs, xp);
+ *xp++ = *sp++;
+ }
+ np = strchr(sp, '/');
+ if (np != NULL) {
+ se = np;
+ odirsep = *np; /* don't assume '/', can be multiple kinds */
+ *np++ = '\0';
+ } else {
+ odirsep = '\0'; /* keep gcc quiet */
+ se = sp + strlen(sp);
+ }
+
+
+ /* Check if sp needs globbing - done to avoid pattern checks for strings
+ * containing MAGIC characters, open ['s without the matching close ],
+ * etc. (otherwise opendir() will be called which may fail because the
+ * directory isn't readable - if no globbing is needed, only execute
+ * permission should be required (as per POSIX)).
+ */
+ if (!has_globbing(sp, se)) {
+ XcheckN(*xs, xp, se - sp + 1);
+ debunk(xp, sp, Xnleft(*xs, xp));
+ xp += strlen(xp);
+ *xpp = xp;
+ globit(xs, xpp, np, wp, check);
+ } else {
+ DIR *dirp;
+ struct dirent *d;
+ char *name;
+ int len;
+ int prefix_len;
+
+ /* xp = *xpp; copy_non_glob() may have re-alloc'd xs */
+ *xp = '\0';
+ prefix_len = Xlength(*xs, xp);
+ dirp = opendir(prefix_len ? Xstring(*xs, xp) : ".");
+ if (dirp == NULL)
+ goto Nodir;
+ while ((d = readdir(dirp)) != NULL) {
+ name = d->d_name;
+ if (name[0] == '.' &&
+ (name[1] == 0 || (name[1] == '.' && name[2] == 0)))
+ continue; /* always ignore . and .. */
+ if ((*name == '.' && *sp != '.') ||
+ !gmatch(name, sp, true))
+ continue;
+
+ len = strlen(d->d_name) + 1;
+ XcheckN(*xs, xp, len);
+ memcpy(xp, name, len);
+ *xpp = xp + len - 1;
+ globit(xs, xpp, np, wp,
+ (check & GF_MARKDIR) | GF_GLOBBED
+ | (np ? GF_EXCHECK : GF_NONE));
+ xp = Xstring(*xs, xp) + prefix_len;
+ }
+ closedir(dirp);
+ Nodir:;
+ }
+
+ if (np != NULL)
+ *--np = odirsep;
+}
+
+#if 0
+/* Check if p contains something that needs globbing; if it does, 0 is
+ * returned; if not, p is copied into xs/xp after stripping any MAGICs
+ */
+static int copy_non_glob(XString *xs, char **xpp, char *p);
+static int
+copy_non_glob(XString *xs, char **xpp, char *p)
+{
+ char *xp;
+ int len = strlen(p);
+
+ XcheckN(*xs, *xpp, len);
+ xp = *xpp;
+ for (; *p; p++) {
+ if (ISMAGIC(*p)) {
+ int c = *++p;
+
+ if (c == '*' || c == '?')
+ return 0;
+ if (*p == '[') {
+ char *q = p + 1;
+
+ if (ISMAGIC(*q) && q[1] == NOT)
+ q += 2;
+ if (ISMAGIC(*q) && q[1] == ']')
+ q += 2;
+ for (; *q; q++)
+ if (ISMAGIC(*q) && *++q == ']')
+ return 0;
+ /* pass a literal [ through */
+ }
+ /* must be a MAGIC-MAGIC, or MAGIC-!, MAGIC--, etc. */
+ }
+ *xp++ = *p;
+ }
+ *xp = '\0';
+ *xpp = xp;
+ return 1;
+}
+#endif /* 0 */
+
+/* remove MAGIC from string */
+char *
+debunk(char *dp, const char *sp, size_t dlen)
+{
+ char *d, *s;
+
+ if ((s = strchr(sp, MAGIC))) {
+ if (s - sp >= dlen)
+ return dp;
+ memcpy(dp, sp, s - sp);
+ for (d = dp + (s - sp); *s && (d - dp < dlen); s++)
+ if (!ISMAGIC(*s) || !(*++s & 0x80) ||
+ !strchr("*+?@! ", *s & 0x7f))
+ *d++ = *s;
+ else {
+ /* extended pattern operators: *+?@! */
+ if ((*s & 0x7f) != ' ')
+ *d++ = *s & 0x7f;
+ if (d - dp < dlen)
+ *d++ = '(';
+ }
+ *d = '\0';
+ } else if (dp != sp)
+ strlcpy(dp, sp, dlen);
+ return dp;
+}
+
+/* Check if p is an unquoted name, possibly followed by a / or :. If so
+ * puts the expanded version in *dcp,dp and returns a pointer in p just
+ * past the name, otherwise returns 0.
+ */
+static char *
+maybe_expand_tilde(char *p, XString *dsp, char **dpp, int isassign)
+{
+ XString ts;
+ char *dp = *dpp;
+ char *tp, *r;
+
+ Xinit(ts, tp, 16, ATEMP);
+ /* : only for DOASNTILDE form */
+ while (p[0] == CHAR && p[1] != '/' && (!isassign || p[1] != ':'))
+ {
+ Xcheck(ts, tp);
+ *tp++ = p[1];
+ p += 2;
+ }
+ *tp = '\0';
+ r = (p[0] == EOS || p[0] == CHAR || p[0] == CSUBST) ?
+ tilde(Xstring(ts, tp)) : (char *) 0;
+ Xfree(ts, tp);
+ if (r) {
+ while (*r) {
+ Xcheck(*dsp, dp);
+ if (ISMAGIC(*r))
+ *dp++ = MAGIC;
+ *dp++ = *r++;
+ }
+ *dpp = dp;
+ r = p;
+ }
+ return r;
+}
+
+/*
+ * tilde expansion
+ *
+ * based on a version by Arnold Robbins
+ */
+
+static char *
+tilde(char *cp)
+{
+ char *dp;
+
+ if (cp[0] == '\0')
+ dp = str_val(global("HOME"));
+ else if (cp[0] == '+' && cp[1] == '\0')
+ dp = str_val(global("PWD"));
+ else if (cp[0] == '-' && cp[1] == '\0')
+ dp = str_val(global("OLDPWD"));
+ else
+ dp = homedir(cp);
+ /* If HOME, PWD or OLDPWD are not set, don't expand ~ */
+ if (dp == null)
+ dp = (char *) 0;
+ return dp;
+}
+
+/*
+ * map userid to user's home directory.
+ * note that 4.3's getpw adds more than 6K to the shell,
+ * and the YP version probably adds much more.
+ * we might consider our own version of getpwnam() to keep the size down.
+ */
+
+static char *
+homedir(char *name)
+{
+ struct tbl *ap;
+
+ ap = ktenter(&homedirs, name, hash(name));
+ if (!(ap->flag & ISSET)) {
+ struct passwd *pw;
+
+ pw = getpwnam(name);
+ if (pw == NULL)
+ return NULL;
+ ap->val.s = str_save(pw->pw_dir, APERM);
+ ap->flag |= DEFINED|ISSET|ALLOC;
+ }
+ return ap->val.s;
+}
+
+#ifdef BRACE_EXPAND
+static void
+alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo)
+{
+ int count = 0;
+ char *brace_start, *brace_end, *comma = NULL;
+ char *field_start;
+ char *p;
+
+ /* search for open brace */
+ for (p = exp_start; (p = strchr(p, MAGIC)) && p[1] != OBRACE; p += 2)
+ ;
+ brace_start = p;
+
+ /* find matching close brace, if any */
+ if (p) {
+ comma = (char *) 0;
+ count = 1;
+ for (p += 2; *p && count; p++) {
+ if (ISMAGIC(*p)) {
+ if (*++p == OBRACE)
+ count++;
+ else if (*p == CBRACE)
+ --count;
+ else if (*p == ',' && count == 1)
+ comma = p;
+ }
+ }
+ }
+ /* no valid expansions... */
+ if (!p || count != 0) {
+ /* Note that given a{{b,c} we do not expand anything (this is
+ * what at&t ksh does. This may be changed to do the {b,c}
+ * expansion. }
+ */
+ if (fdo & DOGLOB)
+ glob(start, wp, fdo & DOMARKDIRS);
+ else
+ XPput(*wp, debunk(start, start, end - start));
+ return;
+ }
+ brace_end = p;
+ if (!comma) {
+ alt_expand(wp, start, brace_end, end, fdo);
+ return;
+ }
+
+ /* expand expression */
+ field_start = brace_start + 2;
+ count = 1;
+ for (p = brace_start + 2; p != brace_end; p++) {
+ if (ISMAGIC(*p)) {
+ if (*++p == OBRACE)
+ count++;
+ else if ((*p == CBRACE && --count == 0) ||
+ (*p == ',' && count == 1)) {
+ char *new;
+ int l1, l2, l3;
+
+ l1 = brace_start - start;
+ l2 = (p - 1) - field_start;
+ l3 = end - brace_end;
+ new = (char *) alloc(l1 + l2 + l3 + 1, ATEMP);
+ memcpy(new, start, l1);
+ memcpy(new + l1, field_start, l2);
+ memcpy(new + l1 + l2, brace_end, l3);
+ new[l1 + l2 + l3] = '\0';
+ alt_expand(wp, new, new + l1,
+ new + l1 + l2 + l3, fdo);
+ field_start = p + 1;
+ }
+ }
+ }
+ return;
+}
+#endif /* BRACE_EXPAND */
diff --git a/exec.c b/exec.c
@@ -0,0 +1,1433 @@
+/* $OpenBSD: exec.c,v 1.50 2013/06/10 21:09:27 millert Exp $ */
+
+/*
+ * execute command tree
+ */
+
+#include "sh.h"
+#include "c_test.h"
+#include <ctype.h>
+#include <sys/stat.h>
+
+/* Does ps4 get parameter substitutions done? */
+# define PS4_SUBSTITUTE(s) substitute((s), 0)
+
+static int comexec(struct op *, struct tbl *volatile, char **,
+ int volatile, volatile int *);
+static void scriptexec(struct op *, char **);
+static int call_builtin(struct tbl *, char **);
+static int iosetup(struct ioword *, struct tbl *);
+static int herein(const char *, int);
+static char *do_selectargs(char **, bool);
+static int dbteste_isa(Test_env *, Test_meta);
+static const char *dbteste_getopnd(Test_env *, Test_op, int);
+static int dbteste_eval(Test_env *, Test_op, const char *, const char *,
+ int);
+static void dbteste_error(Test_env *, int, const char *);
+
+
+/*
+ * execute command tree
+ */
+int
+execute(struct op *volatile t,
+ volatile int flags, volatile int *xerrok) /* if XEXEC don't fork */
+{
+ int i, dummy = 0;
+ volatile int rv = 0;
+ int pv[2];
+ char ** volatile ap;
+ char *s, *cp;
+ struct ioword **iowp;
+ struct tbl *tp = NULL;
+
+ if (t == NULL)
+ return 0;
+
+ /* Caller doesn't care if XERROK should propagate. */
+ if (xerrok == NULL)
+ xerrok = &dummy;
+
+ /* Is this the end of a pipeline? If so, we want to evaluate the
+ * command arguments
+ bool eval_done = false;
+ if ((flags&XFORK) && !(flags&XEXEC) && (flags&XPCLOSE)) {
+ eval_done = true;
+ tp = eval_execute_args(t, &ap);
+ }
+ */
+ if ((flags&XFORK) && !(flags&XEXEC) && t->type != TPIPE)
+ return exchild(t, flags & ~XTIME, xerrok, -1); /* run in sub-process */
+
+ newenv(E_EXEC);
+ if (trap)
+ runtraps(0);
+
+ if (t->type == TCOM) {
+ /* Clear subst_exstat before argument expansion. Used by
+ * null commands (see comexec() and c_eval()) and by c_set().
+ */
+ subst_exstat = 0;
+
+ current_lineno = t->lineno; /* for $LINENO */
+
+ /* POSIX says expand command words first, then redirections,
+ * and assignments last..
+ */
+ ap = eval(t->args, t->u.evalflags | DOBLANK | DOGLOB | DOTILDE);
+ if (flags & XTIME)
+ /* Allow option parsing (bizarre, but POSIX) */
+ timex_hook(t, &ap);
+ if (Flag(FXTRACE) && ap[0]) {
+ shf_fprintf(shl_out, "%s",
+ PS4_SUBSTITUTE(str_val(global("PS4"))));
+ for (i = 0; ap[i]; i++)
+ shf_fprintf(shl_out, "%s%s", ap[i],
+ ap[i + 1] ? space : newline);
+ shf_flush(shl_out);
+ }
+ if (ap[0])
+ tp = findcom(ap[0], FC_BI|FC_FUNC);
+ }
+ flags &= ~XTIME;
+
+ if (t->ioact != NULL || t->type == TPIPE || t->type == TCOPROC) {
+ e->savefd = (short *) alloc(sizeofN(short, NUFILE), ATEMP);
+ /* initialize to not redirected */
+ memset(e->savefd, 0, sizeofN(short, NUFILE));
+ }
+
+ /* do redirection, to be restored in quitenv() */
+ if (t->ioact != NULL)
+ for (iowp = t->ioact; *iowp != NULL; iowp++) {
+ if (iosetup(*iowp, tp) < 0) {
+ exstat = rv = 1;
+ /* Redirection failures for special commands
+ * cause (non-interactive) shell to exit.
+ */
+ if (tp && tp->type == CSHELL &&
+ (tp->flag & SPEC_BI))
+ errorf(null);
+ /* Deal with FERREXIT, quitenv(), etc. */
+ goto Break;
+ }
+ }
+
+ switch (t->type) {
+ case TCOM:
+ rv = comexec(t, tp, ap, flags, xerrok);
+ break;
+
+ case TPAREN:
+ rv = execute(t->left, flags|XFORK, xerrok);
+ break;
+
+ case TPIPE:
+ flags |= XFORK;
+ flags &= ~XEXEC;
+ e->savefd[0] = savefd(0);
+ e->savefd[1] = savefd(1);
+ while (t->type == TPIPE) {
+ openpipe(pv);
+ (void) ksh_dup2(pv[1], 1, false); /* stdout of curr */
+ /* Let exchild() close pv[0] in child
+ * (if this isn't done, commands like
+ * (: ; cat /etc/termcap) | sleep 1
+ * will hang forever).
+ */
+ exchild(t->left, flags|XPIPEO|XCCLOSE, NULL, pv[0]);
+ (void) ksh_dup2(pv[0], 0, false); /* stdin of next */
+ closepipe(pv);
+ flags |= XPIPEI;
+ t = t->right;
+ }
+ restfd(1, e->savefd[1]); /* stdout of last */
+ e->savefd[1] = 0; /* no need to re-restore this */
+ /* Let exchild() close 0 in parent, after fork, before wait */
+ i = exchild(t, flags|XPCLOSE, xerrok, 0);
+ if (!(flags&XBGND) && !(flags&XXCOM))
+ rv = i;
+ break;
+
+ case TLIST:
+ while (t->type == TLIST) {
+ execute(t->left, flags & XERROK, NULL);
+ t = t->right;
+ }
+ rv = execute(t, flags & XERROK, xerrok);
+ break;
+
+ case TCOPROC:
+ {
+ sigset_t omask;
+
+ /* Block sigchild as we are using things changed in the
+ * signal handler
+ */
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+ e->type = E_ERRH;
+ i = sigsetjmp(e->jbuf, 0);
+ if (i) {
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ quitenv(NULL);
+ unwind(i);
+ /* NOTREACHED */
+ }
+ /* Already have a (live) co-process? */
+ if (coproc.job && coproc.write >= 0)
+ errorf("coprocess already exists");
+
+ /* Can we re-use the existing co-process pipe? */
+ coproc_cleanup(true);
+
+ /* do this before opening pipes, in case these fail */
+ e->savefd[0] = savefd(0);
+ e->savefd[1] = savefd(1);
+
+ openpipe(pv);
+ if (pv[0] != 0) {
+ ksh_dup2(pv[0], 0, false);
+ close(pv[0]);
+ }
+ coproc.write = pv[1];
+ coproc.job = (void *) 0;
+
+ if (coproc.readw >= 0)
+ ksh_dup2(coproc.readw, 1, false);
+ else {
+ openpipe(pv);
+ coproc.read = pv[0];
+ ksh_dup2(pv[1], 1, false);
+ coproc.readw = pv[1]; /* closed before first read */
+ coproc.njobs = 0;
+ /* create new coprocess id */
+ ++coproc.id;
+ }
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ e->type = E_EXEC; /* no more need for error handler */
+
+ /* exchild() closes coproc.* in child after fork,
+ * will also increment coproc.njobs when the
+ * job is actually created.
+ */
+ flags &= ~XEXEC;
+ exchild(t->left, flags|XBGND|XFORK|XCOPROC|XCCLOSE,
+ NULL, coproc.readw);
+ break;
+ }
+
+ case TASYNC:
+ /* XXX non-optimal, I think - "(foo &)", forks for (),
+ * forks again for async... parent should optimize
+ * this to "foo &"...
+ */
+ rv = execute(t->left, (flags&~XEXEC)|XBGND|XFORK, xerrok);
+ break;
+
+ case TOR:
+ case TAND:
+ rv = execute(t->left, XERROK, xerrok);
+ if ((rv == 0) == (t->type == TAND))
+ rv = execute(t->right, flags & XERROK, xerrok);
+ else {
+ flags |= XERROK;
+ if (xerrok)
+ *xerrok = 1;
+ }
+ break;
+
+ case TBANG:
+ rv = !execute(t->right, XERROK, xerrok);
+ flags |= XERROK;
+ if (xerrok)
+ *xerrok = 1;
+ break;
+
+ case TDBRACKET:
+ {
+ Test_env te;
+
+ te.flags = TEF_DBRACKET;
+ te.pos.wp = t->args;
+ te.isa = dbteste_isa;
+ te.getopnd = dbteste_getopnd;
+ te.eval = dbteste_eval;
+ te.error = dbteste_error;
+
+ rv = test_parse(&te);
+ break;
+ }
+
+ case TFOR:
+ case TSELECT:
+ {
+ volatile bool is_first = true;
+ ap = (t->vars != NULL) ? eval(t->vars, DOBLANK|DOGLOB|DOTILDE) :
+ e->loc->argv + 1;
+ e->type = E_LOOP;
+ while (1) {
+ i = sigsetjmp(e->jbuf, 0);
+ if (!i)
+ break;
+ if ((e->flags&EF_BRKCONT_PASS) ||
+ (i != LBREAK && i != LCONTIN)) {
+ quitenv(NULL);
+ unwind(i);
+ } else if (i == LBREAK) {
+ rv = 0;
+ goto Break;
+ }
+ }
+ rv = 0; /* in case of a continue */
+ if (t->type == TFOR) {
+ while (*ap != NULL) {
+ setstr(global(t->str), *ap++, KSH_UNWIND_ERROR);
+ rv = execute(t->left, flags & XERROK, xerrok);
+ }
+ } else { /* TSELECT */
+ for (;;) {
+ if (!(cp = do_selectargs(ap, is_first))) {
+ rv = 1;
+ break;
+ }
+ is_first = false;
+ setstr(global(t->str), cp, KSH_UNWIND_ERROR);
+ rv = execute(t->left, flags & XERROK, xerrok);
+ }
+ }
+ }
+ break;
+
+ case TWHILE:
+ case TUNTIL:
+ e->type = E_LOOP;
+ while (1) {
+ i = sigsetjmp(e->jbuf, 0);
+ if (!i)
+ break;
+ if ((e->flags&EF_BRKCONT_PASS) ||
+ (i != LBREAK && i != LCONTIN)) {
+ quitenv(NULL);
+ unwind(i);
+ } else if (i == LBREAK) {
+ rv = 0;
+ goto Break;
+ }
+ }
+ rv = 0; /* in case of a continue */
+ while ((execute(t->left, XERROK, NULL) == 0) == (t->type == TWHILE))
+ rv = execute(t->right, flags & XERROK, xerrok);
+ break;
+
+ case TIF:
+ case TELIF:
+ if (t->right == NULL)
+ break; /* should be error */
+ rv = execute(t->left, XERROK, NULL) == 0 ?
+ execute(t->right->left, flags & XERROK, xerrok) :
+ execute(t->right->right, flags & XERROK, xerrok);
+ break;
+
+ case TCASE:
+ cp = evalstr(t->str, DOTILDE);
+ for (t = t->left; t != NULL && t->type == TPAT; t = t->right)
+ for (ap = t->vars; *ap; ap++)
+ if ((s = evalstr(*ap, DOTILDE|DOPAT)) &&
+ gmatch(cp, s, false))
+ goto Found;
+ break;
+ Found:
+ rv = execute(t->left, flags & XERROK, xerrok);
+ break;
+
+ case TBRACE:
+ rv = execute(t->left, flags & XERROK, xerrok);
+ break;
+
+ case TFUNCT:
+ rv = define(t->str, t);
+ break;
+
+ case TTIME:
+ /* Clear XEXEC so nested execute() call doesn't exit
+ * (allows "ls -l | time grep foo").
+ */
+ rv = timex(t, flags & ~XEXEC, xerrok);
+ break;
+
+ case TEXEC: /* an eval'd TCOM */
+ s = t->args[0];
+ ap = makenv();
+ restoresigs();
+ cleanup_proc_env();
+ execve(t->str, t->args, ap);
+ if (errno == ENOEXEC)
+ scriptexec(t, ap);
+ else
+ errorf("%s: %s", s, strerror(errno));
+ }
+ Break:
+ exstat = rv;
+
+ quitenv(NULL); /* restores IO */
+ if ((flags&XEXEC))
+ unwind(LEXIT); /* exit child */
+ if (rv != 0 && !(flags & XERROK) &&
+ (xerrok == NULL || !*xerrok)) {
+ trapsig(SIGERR_);
+ if (Flag(FERREXIT))
+ unwind(LERROR);
+ }
+ return rv;
+}
+
+/*
+ * execute simple command
+ */
+
+static int
+comexec(struct op *t, struct tbl *volatile tp, char **ap, volatile int flags,
+ volatile int *xerrok)
+{
+ int i;
+ volatile int rv = 0;
+ char *cp;
+ char **lastp;
+ static struct op texec; /* Must be static (XXX but why?) */
+ int type_flags;
+ int keepasn_ok;
+ int fcflags = FC_BI|FC_FUNC|FC_PATH;
+ int bourne_function_call = 0;
+
+ /* snag the last argument for $_ XXX not the same as at&t ksh,
+ * which only seems to set $_ after a newline (but not in
+ * functions/dot scripts, but in interactive and script) -
+ * perhaps save last arg here and set it in shell()?.
+ */
+ if (!Flag(FSH) && Flag(FTALKING) && *(lastp = ap)) {
+ while (*++lastp)
+ ;
+ /* setstr() can't fail here */
+ setstr(typeset("_", LOCAL, 0, INTEGER, 0), *--lastp,
+ KSH_RETURN_ERROR);
+ }
+
+ /* Deal with the shell builtins builtin, exec and command since
+ * they can be followed by other commands. This must be done before
+ * we know if we should create a local block, which must be done
+ * before we can do a path search (in case the assignments change
+ * PATH).
+ * Odd cases:
+ * FOO=bar exec > /dev/null FOO is kept but not exported
+ * FOO=bar exec foobar FOO is exported
+ * FOO=bar command exec > /dev/null FOO is neither kept nor exported
+ * FOO=bar command FOO is neither kept nor exported
+ * PATH=... foobar use new PATH in foobar search
+ */
+ keepasn_ok = 1;
+ while (tp && tp->type == CSHELL) {
+ fcflags = FC_BI|FC_FUNC|FC_PATH;/* undo effects of command */
+ if (tp->val.f == c_builtin) {
+ if ((cp = *++ap) == NULL) {
+ tp = NULL;
+ break;
+ }
+ tp = findcom(cp, FC_BI);
+ if (tp == NULL)
+ errorf("builtin: %s: not a builtin", cp);
+ continue;
+ } else if (tp->val.f == c_exec) {
+ if (ap[1] == NULL)
+ break;
+ ap++;
+ flags |= XEXEC;
+ } else if (tp->val.f == c_command) {
+ int optc, saw_p = 0;
+
+ /* Ugly dealing with options in two places (here and
+ * in c_command(), but such is life)
+ */
+ ksh_getopt_reset(&builtin_opt, 0);
+ while ((optc = ksh_getopt(ap, &builtin_opt, ":p")) == 'p')
+ saw_p = 1;
+ if (optc != EOF)
+ break; /* command -vV or something */
+ /* don't look for functions */
+ fcflags = FC_BI|FC_PATH;
+ if (saw_p) {
+ if (Flag(FRESTRICTED)) {
+ warningf(true,
+ "command -p: restricted");
+ rv = 1;
+ goto Leave;
+ }
+ fcflags |= FC_DEFPATH;
+ }
+ ap += builtin_opt.optind;
+ /* POSIX says special builtins lose their status
+ * if accessed using command.
+ */
+ keepasn_ok = 0;
+ if (!ap[0]) {
+ /* ensure command with no args exits with 0 */
+ subst_exstat = 0;
+ break;
+ }
+ } else
+ break;
+ tp = findcom(ap[0], fcflags & (FC_BI|FC_FUNC));
+ }
+ if (keepasn_ok && (!ap[0] || (tp && (tp->flag & KEEPASN))))
+ type_flags = 0;
+ else {
+ /* create new variable/function block */
+ newblock();
+ /* ksh functions don't keep assignments, POSIX functions do. */
+ if (keepasn_ok && tp && tp->type == CFUNC &&
+ !(tp->flag & FKSH)) {
+ bourne_function_call = 1;
+ type_flags = 0;
+ } else
+ type_flags = LOCAL|LOCAL_COPY|EXPORT;
+ }
+ if (Flag(FEXPORT))
+ type_flags |= EXPORT;
+ for (i = 0; t->vars[i]; i++) {
+ cp = evalstr(t->vars[i], DOASNTILDE);
+ if (Flag(FXTRACE)) {
+ if (i == 0)
+ shf_fprintf(shl_out, "%s",
+ PS4_SUBSTITUTE(str_val(global("PS4"))));
+ shf_fprintf(shl_out, "%s%s", cp,
+ t->vars[i + 1] ? space : newline);
+ if (!t->vars[i + 1])
+ shf_flush(shl_out);
+ }
+ typeset(cp, type_flags, 0, 0, 0);
+ if (bourne_function_call && !(type_flags & EXPORT))
+ typeset(cp, LOCAL|LOCAL_COPY|EXPORT, 0, 0, 0);
+ }
+
+ if ((cp = *ap) == NULL) {
+ rv = subst_exstat;
+ goto Leave;
+ } else if (!tp) {
+ if (Flag(FRESTRICTED) && strchr(cp, '/')) {
+ warningf(true, "%s: restricted", cp);
+ rv = 1;
+ goto Leave;
+ }
+ tp = findcom(cp, fcflags);
+ }
+
+ switch (tp->type) {
+ case CSHELL: /* shell built-in */
+ rv = call_builtin(tp, ap);
+ break;
+
+ case CFUNC: /* function call */
+ {
+ volatile int old_xflag;
+ volatile Tflag old_inuse;
+ const char *volatile old_kshname;
+
+ if (!(tp->flag & ISSET)) {
+ struct tbl *ftp;
+
+ if (!tp->u.fpath) {
+ if (tp->u2.errno_) {
+ warningf(true,
+ "%s: can't find function "
+ "definition file - %s",
+ cp, strerror(tp->u2.errno_));
+ rv = 126;
+ } else {
+ warningf(true,
+ "%s: can't find function "
+ "definition file", cp);
+ rv = 127;
+ }
+ break;
+ }
+ if (include(tp->u.fpath, 0, (char **) 0, 0) < 0) {
+ warningf(true,
+ "%s: can't open function definition file %s - %s",
+ cp, tp->u.fpath, strerror(errno));
+ rv = 127;
+ break;
+ }
+ if (!(ftp = findfunc(cp, hash(cp), false)) ||
+ !(ftp->flag & ISSET)) {
+ warningf(true,
+ "%s: function not defined by %s",
+ cp, tp->u.fpath);
+ rv = 127;
+ break;
+ }
+ tp = ftp;
+ }
+
+ /* ksh functions set $0 to function name, POSIX functions leave
+ * $0 unchanged.
+ */
+ old_kshname = kshname;
+ if (tp->flag & FKSH)
+ kshname = ap[0];
+ else
+ ap[0] = (char *) kshname;
+ e->loc->argv = ap;
+ for (i = 0; *ap++ != NULL; i++)
+ ;
+ e->loc->argc = i - 1;
+ /* ksh-style functions handle getopts sanely,
+ * bourne/posix functions are insane...
+ */
+ if (tp->flag & FKSH) {
+ e->loc->flags |= BF_DOGETOPTS;
+ e->loc->getopts_state = user_opt;
+ getopts_reset(1);
+ }
+
+ old_xflag = Flag(FXTRACE);
+ Flag(FXTRACE) = tp->flag & TRACE ? true : false;
+
+ old_inuse = tp->flag & FINUSE;
+ tp->flag |= FINUSE;
+
+ e->type = E_FUNC;
+ i = sigsetjmp(e->jbuf, 0);
+ if (i == 0) {
+ /* seems odd to pass XERROK here, but at&t ksh does */
+ exstat = execute(tp->val.t, flags & XERROK, xerrok);
+ i = LRETURN;
+ }
+ kshname = old_kshname;
+ Flag(FXTRACE) = old_xflag;
+ tp->flag = (tp->flag & ~FINUSE) | old_inuse;
+ /* Were we deleted while executing? If so, free the execution
+ * tree. todo: Unfortunately, the table entry is never re-used
+ * until the lookup table is expanded.
+ */
+ if ((tp->flag & (FDELETE|FINUSE)) == FDELETE) {
+ if (tp->flag & ALLOC) {
+ tp->flag &= ~ALLOC;
+ tfree(tp->val.t, tp->areap);
+ }
+ tp->flag = 0;
+ }
+ switch (i) {
+ case LRETURN:
+ case LERROR:
+ rv = exstat;
+ break;
+ case LINTR:
+ case LEXIT:
+ case LLEAVE:
+ case LSHELL:
+ quitenv(NULL);
+ unwind(i);
+ /* NOTREACHED */
+ default:
+ quitenv(NULL);
+ internal_errorf(1, "CFUNC %d", i);
+ }
+ break;
+ }
+
+ case CEXEC: /* executable command */
+ case CTALIAS: /* tracked alias */
+ if (!(tp->flag&ISSET)) {
+ /* errno_ will be set if the named command was found
+ * but could not be executed (permissions, no execute
+ * bit, directory, etc). Print out a (hopefully)
+ * useful error message and set the exit status to 126.
+ */
+ if (tp->u2.errno_) {
+ warningf(true, "%s: cannot execute - %s", cp,
+ strerror(tp->u2.errno_));
+ rv = 126; /* POSIX */
+ } else {
+ warningf(true, "%s: not found", cp);
+ rv = 127;
+ }
+ break;
+ }
+
+ if (!Flag(FSH)) {
+ /* set $_ to program's full path */
+ /* setstr() can't fail here */
+ setstr(typeset("_", LOCAL|EXPORT, 0, INTEGER, 0),
+ tp->val.s, KSH_RETURN_ERROR);
+ }
+
+ if (flags&XEXEC) {
+ j_exit();
+ if (!(flags&XBGND) || Flag(FMONITOR)) {
+ setexecsig(&sigtraps[SIGINT], SS_RESTORE_ORIG);
+ setexecsig(&sigtraps[SIGQUIT], SS_RESTORE_ORIG);
+ }
+ }
+
+ /* to fork we set up a TEXEC node and call execute */
+ texec.type = TEXEC;
+ texec.left = t; /* for tprint */
+ texec.str = tp->val.s;
+ texec.args = ap;
+ rv = exchild(&texec, flags, xerrok, -1);
+ break;
+ }
+ Leave:
+ if (flags & XEXEC) {
+ exstat = rv;
+ unwind(LLEAVE);
+ }
+ return rv;
+}
+
+static void
+scriptexec(struct op *tp, char **ap)
+{
+ char *shell;
+
+ shell = str_val(global(EXECSHELL_STR));
+ if (shell && *shell)
+ shell = search(shell, path, X_OK, (int *) 0);
+ if (!shell || !*shell)
+ shell = EXECSHELL;
+
+ *tp->args-- = tp->str;
+ *tp->args = shell;
+
+ execve(tp->args[0], tp->args, ap);
+
+ /* report both the program that was run and the bogus shell */
+ errorf("%s: %s: %s", tp->str, shell, strerror(errno));
+}
+
+int
+shcomexec(char **wp)
+{
+ struct tbl *tp;
+
+ tp = ktsearch(&builtins, *wp, hash(*wp));
+ if (tp == NULL)
+ internal_errorf(1, "shcomexec: %s", *wp);
+ return call_builtin(tp, wp);
+}
+
+/*
+ * Search function tables for a function. If create set, a table entry
+ * is created if none is found.
+ */
+struct tbl *
+findfunc(const char *name, unsigned int h, int create)
+{
+ struct block *l;
+ struct tbl *tp = (struct tbl *) 0;
+
+ for (l = e->loc; l; l = l->next) {
+ tp = ktsearch(&l->funs, name, h);
+ if (tp)
+ break;
+ if (!l->next && create) {
+ tp = ktenter(&l->funs, name, h);
+ tp->flag = DEFINED;
+ tp->type = CFUNC;
+ tp->val.t = (struct op *) 0;
+ break;
+ }
+ }
+ return tp;
+}
+
+/*
+ * define function. Returns 1 if function is being undefined (t == 0) and
+ * function did not exist, returns 0 otherwise.
+ */
+int
+define(const char *name, struct op *t)
+{
+ struct tbl *tp;
+ int was_set = 0;
+
+ while (1) {
+ tp = findfunc(name, hash(name), true);
+
+ if (tp->flag & ISSET)
+ was_set = 1;
+ /* If this function is currently being executed, we zap this
+ * table entry so findfunc() won't see it
+ */
+ if (tp->flag & FINUSE) {
+ tp->name[0] = '\0';
+ tp->flag &= ~DEFINED; /* ensure it won't be found */
+ tp->flag |= FDELETE;
+ } else
+ break;
+ }
+
+ if (tp->flag & ALLOC) {
+ tp->flag &= ~(ISSET|ALLOC);
+ tfree(tp->val.t, tp->areap);
+ }
+
+ if (t == NULL) { /* undefine */
+ ktdelete(tp);
+ return was_set ? 0 : 1;
+ }
+
+ tp->val.t = tcopy(t->left, tp->areap);
+ tp->flag |= (ISSET|ALLOC);
+ if (t->u.ksh_func)
+ tp->flag |= FKSH;
+
+ return 0;
+}
+
+/*
+ * add builtin
+ */
+void
+builtin(const char *name, int (*func) (char **))
+{
+ struct tbl *tp;
+ Tflag flag;
+
+ /* see if any flags should be set for this builtin */
+ for (flag = 0; ; name++) {
+ if (*name == '=') /* command does variable assignment */
+ flag |= KEEPASN;
+ else if (*name == '*') /* POSIX special builtin */
+ flag |= SPEC_BI;
+ else if (*name == '+') /* POSIX regular builtin */
+ flag |= REG_BI;
+ else
+ break;
+ }
+
+ tp = ktenter(&builtins, name, hash(name));
+ tp->flag = DEFINED | flag;
+ tp->type = CSHELL;
+ tp->val.f = func;
+}
+
+/*
+ * find command
+ * either function, hashed command, or built-in (in that order)
+ */
+struct tbl *
+findcom(const char *name, int flags)
+{
+ static struct tbl temp;
+ unsigned int h = hash(name);
+ struct tbl *tp = NULL, *tbi;
+ int insert = Flag(FTRACKALL); /* insert if not found */
+ char *fpath; /* for function autoloading */
+ char *npath;
+
+ if (strchr(name, '/') != NULL) {
+ insert = 0;
+ /* prevent FPATH search below */
+ flags &= ~FC_FUNC;
+ goto Search;
+ }
+ tbi = (flags & FC_BI) ? ktsearch(&builtins, name, h) : NULL;
+ /* POSIX says special builtins first, then functions, then
+ * POSIX regular builtins, then search path...
+ */
+ if ((flags & FC_SPECBI) && tbi && (tbi->flag & SPEC_BI))
+ tp = tbi;
+ if (!tp && (flags & FC_FUNC)) {
+ tp = findfunc(name, h, false);
+ if (tp && !(tp->flag & ISSET)) {
+ if ((fpath = str_val(global("FPATH"))) == null) {
+ tp->u.fpath = (char *) 0;
+ tp->u2.errno_ = 0;
+ } else
+ tp->u.fpath = search(name, fpath, R_OK,
+ &tp->u2.errno_);
+ }
+ }
+ if (!tp && (flags & FC_REGBI) && tbi && (tbi->flag & REG_BI))
+ tp = tbi;
+ /* todo: posix says non-special/non-regular builtins must
+ * be triggered by some user-controllable means like a
+ * special directory in PATH. Requires modifications to
+ * the search() function. Tracked aliases should be
+ * modified to allow tracking of builtin commands.
+ * This should be under control of the FPOSIX flag.
+ * If this is changed, also change c_whence...
+ */
+ if (!tp && (flags & FC_UNREGBI) && tbi)
+ tp = tbi;
+ if (!tp && (flags & FC_PATH) && !(flags & FC_DEFPATH)) {
+ tp = ktsearch(&taliases, name, h);
+ if (tp && (tp->flag & ISSET) && access(tp->val.s, X_OK) != 0) {
+ if (tp->flag & ALLOC) {
+ tp->flag &= ~ALLOC;
+ afree(tp->val.s, APERM);
+ }
+ tp->flag &= ~ISSET;
+ }
+ }
+
+ Search:
+ if ((!tp || (tp->type == CTALIAS && !(tp->flag&ISSET))) &&
+ (flags & FC_PATH)) {
+ if (!tp) {
+ if (insert && !(flags & FC_DEFPATH)) {
+ tp = ktenter(&taliases, name, h);
+ tp->type = CTALIAS;
+ } else {
+ tp = &temp;
+ tp->type = CEXEC;
+ }
+ tp->flag = DEFINED; /* make ~ISSET */
+ }
+ npath = search(name, flags & FC_DEFPATH ? def_path : path,
+ X_OK, &tp->u2.errno_);
+ if (npath) {
+ if (tp == &temp) {
+ tp->val.s = npath;
+ } else {
+ tp->val.s = str_save(npath, APERM);
+ if (npath != name)
+ afree(npath, ATEMP);
+ }
+ tp->flag |= ISSET|ALLOC;
+ } else if ((flags & FC_FUNC) &&
+ (fpath = str_val(global("FPATH"))) != null &&
+ (npath = search(name, fpath, R_OK,
+ &tp->u2.errno_)) != (char *) 0) {
+ /* An undocumented feature of at&t ksh is that it
+ * searches FPATH if a command is not found, even
+ * if the command hasn't been set up as an autoloaded
+ * function (ie, no typeset -uf).
+ */
+ tp = &temp;
+ tp->type = CFUNC;
+ tp->flag = DEFINED; /* make ~ISSET */
+ tp->u.fpath = npath;
+ }
+ }
+ return tp;
+}
+
+/*
+ * flush executable commands with relative paths
+ */
+void
+flushcom(int all) /* just relative or all */
+{
+ struct tbl *tp;
+ struct tstate ts;
+
+ for (ktwalk(&ts, &taliases); (tp = ktnext(&ts)) != NULL; )
+ if ((tp->flag&ISSET) && (all || tp->val.s[0] != '/')) {
+ if (tp->flag&ALLOC) {
+ tp->flag &= ~(ALLOC|ISSET);
+ afree(tp->val.s, APERM);
+ }
+ tp->flag &= ~ISSET;
+ }
+}
+
+/* Check if path is something we want to find. Returns -1 for failure. */
+int
+search_access(const char *path, int mode,
+ int *errnop) /* set if candidate found, but not suitable */
+{
+ int ret, err = 0;
+ struct stat statb;
+
+ if (stat(path, &statb) < 0)
+ return -1;
+ ret = access(path, mode);
+ if (ret < 0)
+ err = errno; /* File exists, but we can't access it */
+ else if (mode == X_OK && (!S_ISREG(statb.st_mode) ||
+ !(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) {
+ /* This 'cause access() says root can execute everything */
+ ret = -1;
+ err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES;
+ }
+ if (err && errnop && !*errnop)
+ *errnop = err;
+ return ret;
+}
+
+/*
+ * search for command with PATH
+ */
+char *
+search(const char *name, const char *path,
+ int mode, /* R_OK or X_OK */
+ int *errnop) /* set if candidate found, but not suitable */
+{
+ const char *sp, *p;
+ char *xp;
+ XString xs;
+ int namelen;
+
+ if (errnop)
+ *errnop = 0;
+ if (strchr(name, '/')) {
+ if (search_access(name, mode, errnop) == 0)
+ return (char *) name;
+ return NULL;
+ }
+
+ namelen = strlen(name) + 1;
+ Xinit(xs, xp, 128, ATEMP);
+
+ sp = path;
+ while (sp != NULL) {
+ xp = Xstring(xs, xp);
+ if (!(p = strchr(sp, ':')))
+ p = sp + strlen(sp);
+ if (p != sp) {
+ XcheckN(xs, xp, p - sp);
+ memcpy(xp, sp, p - sp);
+ xp += p - sp;
+ *xp++ = '/';
+ }
+ sp = p;
+ XcheckN(xs, xp, namelen);
+ memcpy(xp, name, namelen);
+ if (search_access(Xstring(xs, xp), mode, errnop) == 0)
+ return Xclose(xs, xp + namelen);
+ if (*sp++ == '\0')
+ sp = NULL;
+ }
+ Xfree(xs, xp);
+ return NULL;
+}
+
+static int
+call_builtin(struct tbl *tp, char **wp)
+{
+ int rv;
+
+ builtin_argv0 = wp[0];
+ builtin_flag = tp->flag;
+ shf_reopen(1, SHF_WR, shl_stdout);
+ shl_stdout_ok = 1;
+ ksh_getopt_reset(&builtin_opt, GF_ERROR);
+ rv = (*tp->val.f)(wp);
+ shf_flush(shl_stdout);
+ shl_stdout_ok = 0;
+ builtin_flag = 0;
+ builtin_argv0 = (char *) 0;
+ return rv;
+}
+
+/*
+ * set up redirection, saving old fd's in e->savefd
+ */
+static int
+iosetup(struct ioword *iop, struct tbl *tp)
+{
+ int u = -1;
+ char *cp = iop->name;
+ int iotype = iop->flag & IOTYPE;
+ int do_open = 1, do_close = 0, flags = 0;
+ struct ioword iotmp;
+ struct stat statb;
+
+ if (iotype != IOHERE)
+ cp = evalonestr(cp, DOTILDE|(Flag(FTALKING_I) ? DOGLOB : 0));
+
+ /* Used for tracing and error messages to print expanded cp */
+ iotmp = *iop;
+ iotmp.name = (iotype == IOHERE) ? (char *) 0 : cp;
+ iotmp.flag |= IONAMEXP;
+
+ if (Flag(FXTRACE))
+ shellf("%s%s\n",
+ PS4_SUBSTITUTE(str_val(global("PS4"))),
+ snptreef((char *) 0, 32, "%R", &iotmp));
+
+ switch (iotype) {
+ case IOREAD:
+ flags = O_RDONLY;
+ break;
+
+ case IOCAT:
+ flags = O_WRONLY | O_APPEND | O_CREAT;
+ break;
+
+ case IOWRITE:
+ flags = O_WRONLY | O_CREAT | O_TRUNC;
+ /* The stat() is here to allow redirections to
+ * things like /dev/null without error.
+ */
+ if (Flag(FNOCLOBBER) && !(iop->flag & IOCLOB) &&
+ (stat(cp, &statb) < 0 || S_ISREG(statb.st_mode)))
+ flags |= O_EXCL;
+ break;
+
+ case IORDWR:
+ flags = O_RDWR | O_CREAT;
+ break;
+
+ case IOHERE:
+ do_open = 0;
+ /* herein() returns -2 if error has been printed */
+ u = herein(iop->heredoc, iop->flag & IOEVAL);
+ /* cp may have wrong name */
+ break;
+
+ case IODUP:
+ {
+ const char *emsg;
+
+ do_open = 0;
+ if (*cp == '-' && !cp[1]) {
+ u = 1009; /* prevent error return below */
+ do_close = 1;
+ } else if ((u = check_fd(cp,
+ X_OK | ((iop->flag & IORDUP) ? R_OK : W_OK),
+ &emsg)) < 0) {
+ warningf(true, "%s: %s",
+ snptreef((char *) 0, 32, "%R", &iotmp), emsg);
+ return -1;
+ }
+ if (u == iop->unit)
+ return 0; /* "dup from" == "dup to" */
+ break;
+ }
+ }
+
+ if (do_open) {
+ if (Flag(FRESTRICTED) && (flags & O_CREAT)) {
+ warningf(true, "%s: restricted", cp);
+ return -1;
+ }
+ u = open(cp, flags, 0666);
+ }
+ if (u < 0) {
+ /* herein() may already have printed message */
+ if (u == -1)
+ warningf(true, "cannot %s %s: %s",
+ iotype == IODUP ? "dup" :
+ (iotype == IOREAD || iotype == IOHERE) ?
+ "open" : "create", cp, strerror(errno));
+ return -1;
+ }
+ /* Do not save if it has already been redirected (i.e. "cat >x >y"). */
+ if (e->savefd[iop->unit] == 0) {
+ /* If these are the same, it means unit was previously closed */
+ if (u == iop->unit)
+ e->savefd[iop->unit] = -1;
+ else
+ /* c_exec() assumes e->savefd[fd] set for any
+ * redirections. Ask savefd() not to close iop->unit;
+ * this allows error messages to be seen if iop->unit
+ * is 2; also means we can't lose the fd (eg, both
+ * dup2 below and dup2 in restfd() failing).
+ */
+ e->savefd[iop->unit] = savefd(iop->unit);
+ }
+
+ if (do_close)
+ close(iop->unit);
+ else if (u != iop->unit) {
+ if (ksh_dup2(u, iop->unit, true) < 0) {
+ warningf(true,
+ "could not finish (dup) redirection %s: %s",
+ snptreef((char *) 0, 32, "%R", &iotmp),
+ strerror(errno));
+ if (iotype != IODUP)
+ close(u);
+ return -1;
+ }
+ if (iotype != IODUP)
+ close(u);
+ /* Touching any co-process fd in an empty exec
+ * causes the shell to close its copies
+ */
+ else if (tp && tp->type == CSHELL && tp->val.f == c_exec) {
+ if (iop->flag & IORDUP) /* possible exec <&p */
+ coproc_read_close(u);
+ else /* possible exec >&p */
+ coproc_write_close(u);
+ }
+ }
+ if (u == 2) /* Clear any write errors */
+ shf_reopen(2, SHF_WR, shl_out);
+ return 0;
+}
+
+/*
+ * open here document temp file.
+ * if unquoted here, expand here temp file into second temp file.
+ */
+static int
+herein(const char *content, int sub)
+{
+ volatile int fd = -1;
+ struct source *s, *volatile osource;
+ struct shf *volatile shf;
+ struct temp *h;
+ int i;
+
+ /* ksh -c 'cat << EOF' can cause this... */
+ if (content == (char *) 0) {
+ warningf(true, "here document missing");
+ return -2; /* special to iosetup(): don't print error */
+ }
+
+ /* Create temp file to hold content (done before newenv so temp
+ * doesn't get removed too soon).
+ */
+ h = maketemp(ATEMP, TT_HEREDOC_EXP, &e->temps);
+ if (!(shf = h->shf) || (fd = open(h->name, O_RDONLY, 0)) < 0) {
+ warningf(true, "can't %s temporary file %s: %s",
+ !shf ? "create" : "open",
+ h->name, strerror(errno));
+ if (shf)
+ shf_close(shf);
+ return -2 /* special to iosetup(): don't print error */;
+ }
+
+ osource = source;
+ newenv(E_ERRH);
+ i = sigsetjmp(e->jbuf, 0);
+ if (i) {
+ source = osource;
+ quitenv(shf);
+ close(fd);
+ return -2; /* special to iosetup(): don't print error */
+ }
+ if (sub) {
+ /* Do substitutions on the content of heredoc */
+ s = pushs(SSTRING, ATEMP);
+ s->start = s->str = content;
+ source = s;
+ if (yylex(ONEWORD|HEREDOC) != LWORD)
+ internal_errorf(1, "herein: yylex");
+ source = osource;
+ shf_puts(evalstr(yylval.cp, 0), shf);
+ } else
+ shf_puts(content, shf);
+
+ quitenv(NULL);
+
+ if (shf_close(shf) == EOF) {
+ close(fd);
+ warningf(true, "error writing %s: %s", h->name,
+ strerror(errno));
+ return -2; /* special to iosetup(): don't print error */
+ }
+
+ return fd;
+}
+
+#ifdef EDIT
+/*
+ * ksh special - the select command processing section
+ * print the args in column form - assuming that we can
+ */
+static char *
+do_selectargs(char **ap, bool print_menu)
+{
+ static const char *const read_args[] = {
+ "read", "-r", "REPLY", (char *) 0
+ };
+ char *s;
+ int i, argct;
+
+ for (argct = 0; ap[argct]; argct++)
+ ;
+ while (1) {
+ /* Menu is printed if
+ * - this is the first time around the select loop
+ * - the user enters a blank line
+ * - the REPLY parameter is empty
+ */
+ if (print_menu || !*str_val(global("REPLY")))
+ pr_menu(ap);
+ shellf("%s", str_val(global("PS3")));
+ if (call_builtin(findcom("read", FC_BI), (char **) read_args))
+ return (char *) 0;
+ s = str_val(global("REPLY"));
+ if (*s) {
+ i = atoi(s);
+ return (i >= 1 && i <= argct) ? ap[i - 1] : null;
+ }
+ print_menu = 1;
+ }
+}
+
+struct select_menu_info {
+ char *const *args;
+ int arg_width;
+ int num_width;
+};
+
+static char *select_fmt_entry(void *arg, int i, char *buf, int buflen);
+
+/* format a single select menu item */
+static char *
+select_fmt_entry(void *arg, int i, char *buf, int buflen)
+{
+ struct select_menu_info *smi = (struct select_menu_info *) arg;
+
+ shf_snprintf(buf, buflen, "%*d) %s",
+ smi->num_width, i + 1, smi->args[i]);
+ return buf;
+}
+
+/*
+ * print a select style menu
+ */
+int
+pr_menu(char *const *ap)
+{
+ struct select_menu_info smi;
+ char *const *pp;
+ int nwidth, dwidth;
+ int i, n;
+
+ /* Width/column calculations were done once and saved, but this
+ * means select can't be used recursively so we re-calculate each
+ * time (could save in a structure that is returned, but its probably
+ * not worth the bother).
+ */
+
+ /*
+ * get dimensions of the list
+ */
+ for (n = 0, nwidth = 0, pp = ap; *pp; n++, pp++) {
+ i = strlen(*pp);
+ nwidth = (i > nwidth) ? i : nwidth;
+ }
+ /*
+ * we will print an index of the form
+ * %d)
+ * in front of each entry
+ * get the max width of this
+ */
+ for (i = n, dwidth = 1; i >= 10; i /= 10)
+ dwidth++;
+
+ smi.args = ap;
+ smi.arg_width = nwidth;
+ smi.num_width = dwidth;
+ print_columns(shl_out, n, select_fmt_entry, (void *) &smi,
+ dwidth + nwidth + 2, 1);
+
+ return n;
+}
+
+/* XXX: horrible kludge to fit within the framework */
+
+static char *plain_fmt_entry(void *arg, int i, char *buf, int buflen);
+
+static char *
+plain_fmt_entry(void *arg, int i, char *buf, int buflen)
+{
+ shf_snprintf(buf, buflen, "%s", ((char *const *)arg)[i]);
+ return buf;
+}
+
+int
+pr_list(char *const *ap)
+{
+ char *const *pp;
+ int nwidth;
+ int i, n;
+
+ for (n = 0, nwidth = 0, pp = ap; *pp; n++, pp++) {
+ i = strlen(*pp);
+ nwidth = (i > nwidth) ? i : nwidth;
+ }
+ print_columns(shl_out, n, plain_fmt_entry, (void *) ap, nwidth + 1, 0);
+
+ return n;
+}
+#endif /* EDIT */
+
+/*
+ * [[ ... ]] evaluation routines
+ */
+
+extern const char *const dbtest_tokens[];
+extern const char db_close[];
+
+/* Test if the current token is a whatever. Accepts the current token if
+ * it is. Returns 0 if it is not, non-zero if it is (in the case of
+ * TM_UNOP and TM_BINOP, the returned value is a Test_op).
+ */
+static int
+dbteste_isa(Test_env *te, Test_meta meta)
+{
+ int ret = 0;
+ int uqword;
+ char *p;
+
+ if (!*te->pos.wp)
+ return meta == TM_END;
+
+ /* unquoted word? */
+ for (p = *te->pos.wp; *p == CHAR; p += 2)
+ ;
+ uqword = *p == EOS;
+
+ if (meta == TM_UNOP || meta == TM_BINOP) {
+ if (uqword) {
+ char buf[8]; /* longer than the longest operator */
+ char *q = buf;
+ for (p = *te->pos.wp;
+ *p == CHAR && q < &buf[sizeof(buf) - 1]; p += 2)
+ *q++ = p[1];
+ *q = '\0';
+ ret = (int) test_isop(te, meta, buf);
+ }
+ } else if (meta == TM_END)
+ ret = 0;
+ else
+ ret = uqword &&
+ strcmp(*te->pos.wp, dbtest_tokens[(int) meta]) == 0;
+
+ /* Accept the token? */
+ if (ret)
+ te->pos.wp++;
+
+ return ret;
+}
+
+static const char *
+dbteste_getopnd(Test_env *te, Test_op op, int do_eval)
+{
+ char *s = *te->pos.wp;
+
+ if (!s)
+ return (char *) 0;
+
+ te->pos.wp++;
+
+ if (!do_eval)
+ return null;
+
+ if (op == TO_STEQL || op == TO_STNEQ)
+ s = evalstr(s, DOTILDE | DOPAT);
+ else
+ s = evalstr(s, DOTILDE);
+
+ return s;
+}
+
+static int
+dbteste_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
+ int do_eval)
+{
+ return test_eval(te, op, opnd1, opnd2, do_eval);
+}
+
+static void
+dbteste_error(Test_env *te, int offset, const char *msg)
+{
+ te->flags |= TEF_ERROR;
+ internal_errorf(0, "dbteste_error: %s (offset %d)", msg, offset);
+}
diff --git a/expand.h b/expand.h
@@ -0,0 +1,107 @@
+/* $OpenBSD: expand.h,v 1.6 2005/03/30 17:16:37 deraadt Exp $ */
+
+/*
+ * Expanding strings
+ */
+
+#define X_EXTRA 8 /* this many extra bytes in X string */
+
+#if 0 /* Usage */
+ XString xs;
+ char *xp;
+
+ Xinit(xs, xp, 128, ATEMP); /* allocate initial string */
+ while ((c = generate()) {
+ Xcheck(xs, xp); /* expand string if necessary */
+ Xput(xs, xp, c); /* add character */
+ }
+ return Xclose(xs, xp); /* resize string */
+/*
+ * NOTE:
+ * The Xcheck and Xinit macros have a magic + X_EXTRA in the lengths.
+ * This is so that you can put up to X_EXTRA characters in a XString
+ * before calling Xcheck. (See yylex in lex.c)
+ */
+#endif /* 0 */
+
+typedef struct XString {
+ char *end, *beg; /* end, begin of string */
+ size_t len; /* length */
+ Area *areap; /* area to allocate/free from */
+} XString;
+
+typedef char * XStringP;
+
+/* initialize expandable string */
+#define Xinit(xs, xp, length, area) do { \
+ (xs).len = length; \
+ (xs).areap = (area); \
+ (xs).beg = alloc((xs).len + X_EXTRA, (xs).areap); \
+ (xs).end = (xs).beg + (xs).len; \
+ xp = (xs).beg; \
+ } while (0)
+
+/* stuff char into string */
+#define Xput(xs, xp, c) (*xp++ = (c))
+
+/* check if there are at least n bytes left */
+#define XcheckN(xs, xp, n) do { \
+ int more = ((xp) + (n)) - (xs).end; \
+ if (more > 0) \
+ xp = Xcheck_grow_(&xs, xp, more); \
+ } while (0)
+
+/* check for overflow, expand string */
+#define Xcheck(xs, xp) XcheckN(xs, xp, 1)
+
+/* free string */
+#define Xfree(xs, xp) afree((void*) (xs).beg, (xs).areap)
+
+/* close, return string */
+#define Xclose(xs, xp) (char*) aresize((void*)(xs).beg, \
+ (size_t)((xp) - (xs).beg), (xs).areap)
+/* begin of string */
+#define Xstring(xs, xp) ((xs).beg)
+
+#define Xnleft(xs, xp) ((xs).end - (xp)) /* may be less than 0 */
+#define Xlength(xs, xp) ((xp) - (xs).beg)
+#define Xsize(xs, xp) ((xs).end - (xs).beg)
+#define Xsavepos(xs, xp) ((xp) - (xs).beg)
+#define Xrestpos(xs, xp, n) ((xs).beg + (n))
+
+char * Xcheck_grow_(XString *xsp, char *xp, int more);
+
+/*
+ * expandable vector of generic pointers
+ */
+
+typedef struct XPtrV {
+ void **cur; /* next avail pointer */
+ void **beg, **end; /* begin, end of vector */
+} XPtrV;
+
+#define XPinit(x, n) do { \
+ void **vp__; \
+ vp__ = (void**) alloc(sizeofN(void*, n), ATEMP); \
+ (x).cur = (x).beg = vp__; \
+ (x).end = vp__ + n; \
+ } while (0)
+
+#define XPput(x, p) do { \
+ if ((x).cur >= (x).end) { \
+ int n = XPsize(x); \
+ (x).beg = (void**) aresize((void*) (x).beg, \
+ sizeofN(void*, n*2), ATEMP); \
+ (x).cur = (x).beg + n; \
+ (x).end = (x).cur + n; \
+ } \
+ *(x).cur++ = (p); \
+ } while (0)
+
+#define XPptrv(x) ((x).beg)
+#define XPsize(x) ((x).cur - (x).beg)
+
+#define XPclose(x) (void**) aresize((void*)(x).beg, \
+ sizeofN(void*, XPsize(x)), ATEMP)
+
+#define XPfree(x) afree((void*) (x).beg, ATEMP)
diff --git a/expr.c b/expr.c
@@ -0,0 +1,594 @@
+/* $OpenBSD: expr.c,v 1.24 2014/12/08 14:26:31 otto Exp $ */
+
+/*
+ * Korn expression evaluation
+ */
+/*
+ * todo: better error handling: if in builtin, should be builtin error, etc.
+ */
+
+#include "sh.h"
+#include <ctype.h>
+
+
+/* The order of these enums is constrained by the order of opinfo[] */
+enum token {
+ /* some (long) unary operators */
+ O_PLUSPLUS = 0, O_MINUSMINUS,
+ /* binary operators */
+ O_EQ, O_NE,
+ /* assignments are assumed to be in range O_ASN .. O_BORASN */
+ O_ASN, O_TIMESASN, O_DIVASN, O_MODASN, O_PLUSASN, O_MINUSASN,
+ O_LSHIFTASN, O_RSHIFTASN, O_BANDASN, O_BXORASN, O_BORASN,
+ O_LSHIFT, O_RSHIFT,
+ O_LE, O_GE, O_LT, O_GT,
+ O_LAND,
+ O_LOR,
+ O_TIMES, O_DIV, O_MOD,
+ O_PLUS, O_MINUS,
+ O_BAND,
+ O_BXOR,
+ O_BOR,
+ O_TERN,
+ O_COMMA,
+ /* things after this aren't used as binary operators */
+ /* unary that are not also binaries */
+ O_BNOT, O_LNOT,
+ /* misc */
+ OPEN_PAREN, CLOSE_PAREN, CTERN,
+ /* things that don't appear in the opinfo[] table */
+ VAR, LIT, END, BAD
+};
+#define IS_BINOP(op) (((int)op) >= (int)O_EQ && ((int)op) <= (int)O_COMMA)
+#define IS_ASSIGNOP(op) ((int)(op) >= (int)O_ASN && (int)(op) <= (int)O_BORASN)
+
+enum prec {
+ P_PRIMARY = 0, /* VAR, LIT, (), ~ ! - + */
+ P_MULT, /* * / % */
+ P_ADD, /* + - */
+ P_SHIFT, /* << >> */
+ P_RELATION, /* < <= > >= */
+ P_EQUALITY, /* == != */
+ P_BAND, /* & */
+ P_BXOR, /* ^ */
+ P_BOR, /* | */
+ P_LAND, /* && */
+ P_LOR, /* || */
+ P_TERN, /* ?: */
+ P_ASSIGN, /* = *= /= %= += -= <<= >>= &= ^= |= */
+ P_COMMA /* , */
+};
+#define MAX_PREC P_COMMA
+
+struct opinfo {
+ char name[4];
+ int len; /* name length */
+ enum prec prec; /* precedence: lower is higher */
+};
+
+/* Tokens in this table must be ordered so the longest are first
+ * (eg, += before +). If you change something, change the order
+ * of enum token too.
+ */
+static const struct opinfo opinfo[] = {
+ { "++", 2, P_PRIMARY }, /* before + */
+ { "--", 2, P_PRIMARY }, /* before - */
+ { "==", 2, P_EQUALITY }, /* before = */
+ { "!=", 2, P_EQUALITY }, /* before ! */
+ { "=", 1, P_ASSIGN }, /* keep assigns in a block */
+ { "*=", 2, P_ASSIGN },
+ { "/=", 2, P_ASSIGN },
+ { "%=", 2, P_ASSIGN },
+ { "+=", 2, P_ASSIGN },
+ { "-=", 2, P_ASSIGN },
+ { "<<=", 3, P_ASSIGN },
+ { ">>=", 3, P_ASSIGN },
+ { "&=", 2, P_ASSIGN },
+ { "^=", 2, P_ASSIGN },
+ { "|=", 2, P_ASSIGN },
+ { "<<", 2, P_SHIFT },
+ { ">>", 2, P_SHIFT },
+ { "<=", 2, P_RELATION },
+ { ">=", 2, P_RELATION },
+ { "<", 1, P_RELATION },
+ { ">", 1, P_RELATION },
+ { "&&", 2, P_LAND },
+ { "||", 2, P_LOR },
+ { "*", 1, P_MULT },
+ { "/", 1, P_MULT },
+ { "%", 1, P_MULT },
+ { "+", 1, P_ADD },
+ { "-", 1, P_ADD },
+ { "&", 1, P_BAND },
+ { "^", 1, P_BXOR },
+ { "|", 1, P_BOR },
+ { "?", 1, P_TERN },
+ { ",", 1, P_COMMA },
+ { "~", 1, P_PRIMARY },
+ { "!", 1, P_PRIMARY },
+ { "(", 1, P_PRIMARY },
+ { ")", 1, P_PRIMARY },
+ { ":", 1, P_PRIMARY },
+ { "", 0, P_PRIMARY } /* end of table */
+};
+
+
+typedef struct expr_state Expr_state;
+struct expr_state {
+ const char *expression; /* expression being evaluated */
+ const char *tokp; /* lexical position */
+ enum token tok; /* token from token() */
+ int noassign; /* don't do assigns (for ?:,&&,||) */
+ bool arith; /* true if evaluating an $(())
+ * expression
+ */
+ struct tbl *val; /* value from token() */
+ struct tbl *evaling; /* variable that is being recursively
+ * expanded (EXPRINEVAL flag set)
+ */
+};
+
+enum error_type {
+ ET_UNEXPECTED, ET_BADLIT, ET_RECURSIVE,
+ ET_LVALUE, ET_RDONLY, ET_STR
+};
+
+static void evalerr(Expr_state *, enum error_type, const char *)
+ __attribute__((__noreturn__));
+static struct tbl *evalexpr(Expr_state *, enum prec);
+static void token(Expr_state *);
+static struct tbl *do_ppmm(Expr_state *, enum token, struct tbl *, bool);
+static void assign_check(Expr_state *, enum token, struct tbl *);
+static struct tbl *tempvar(void);
+static struct tbl *intvar(Expr_state *, struct tbl *);
+
+/*
+ * parse and evaluate expression
+ */
+int
+evaluate(const char *expr, long int *rval, int error_ok, bool arith)
+{
+ struct tbl v;
+ int ret;
+
+ v.flag = DEFINED|INTEGER;
+ v.type = 0;
+ ret = v_evaluate(&v, expr, error_ok, arith);
+ *rval = v.val.i;
+ return ret;
+}
+
+/*
+ * parse and evaluate expression, storing result in vp.
+ */
+int
+v_evaluate(struct tbl *vp, const char *expr, volatile int error_ok,
+ bool arith)
+{
+ struct tbl *v;
+ Expr_state curstate;
+ Expr_state * const es = &curstate;
+ int i;
+
+ /* save state to allow recursive calls */
+ curstate.expression = curstate.tokp = expr;
+ curstate.noassign = 0;
+ curstate.arith = arith;
+ curstate.evaling = (struct tbl *) 0;
+ curstate.val = (struct tbl *) 0;
+
+ newenv(E_ERRH);
+ i = sigsetjmp(e->jbuf, 0);
+ if (i) {
+ /* Clear EXPRINEVAL in of any variables we were playing with */
+ if (curstate.evaling)
+ curstate.evaling->flag &= ~EXPRINEVAL;
+ quitenv(NULL);
+ if (i == LAEXPR) {
+ if (error_ok == KSH_RETURN_ERROR)
+ return 0;
+ errorf(null);
+ }
+ unwind(i);
+ /* NOTREACHED */
+ }
+
+ token(es);
+#if 1 /* ifdef-out to disallow empty expressions to be treated as 0 */
+ if (es->tok == END) {
+ es->tok = LIT;
+ es->val = tempvar();
+ }
+#endif /* 0 */
+ v = intvar(es, evalexpr(es, MAX_PREC));
+
+ if (es->tok != END)
+ evalerr(es, ET_UNEXPECTED, (char *) 0);
+
+ if (vp->flag & INTEGER)
+ setint_v(vp, v, es->arith);
+ else
+ /* can fail if readonly */
+ setstr(vp, str_val(v), error_ok);
+
+ quitenv(NULL);
+
+ return 1;
+}
+
+static void
+evalerr(Expr_state *es, enum error_type type, const char *str)
+{
+ char tbuf[2];
+ const char *s;
+
+ es->arith = false;
+ switch (type) {
+ case ET_UNEXPECTED:
+ switch (es->tok) {
+ case VAR:
+ s = es->val->name;
+ break;
+ case LIT:
+ s = str_val(es->val);
+ break;
+ case END:
+ s = "end of expression";
+ break;
+ case BAD:
+ tbuf[0] = *es->tokp;
+ tbuf[1] = '\0';
+ s = tbuf;
+ break;
+ default:
+ s = opinfo[(int)es->tok].name;
+ }
+ warningf(true, "%s: unexpected `%s'", es->expression, s);
+ break;
+
+ case ET_BADLIT:
+ warningf(true, "%s: bad number `%s'", es->expression, str);
+ break;
+
+ case ET_RECURSIVE:
+ warningf(true, "%s: expression recurses on parameter `%s'",
+ es->expression, str);
+ break;
+
+ case ET_LVALUE:
+ warningf(true, "%s: %s requires lvalue",
+ es->expression, str);
+ break;
+
+ case ET_RDONLY:
+ warningf(true, "%s: %s applied to read only variable",
+ es->expression, str);
+ break;
+
+ default: /* keep gcc happy */
+ case ET_STR:
+ warningf(true, "%s: %s", es->expression, str);
+ break;
+ }
+ unwind(LAEXPR);
+}
+
+static struct tbl *
+evalexpr(Expr_state *es, enum prec prec)
+{
+ struct tbl *vl, *vr = NULL, *vasn;
+ enum token op;
+ long res = 0;
+
+ if (prec == P_PRIMARY) {
+ op = es->tok;
+ if (op == O_BNOT || op == O_LNOT || op == O_MINUS ||
+ op == O_PLUS) {
+ token(es);
+ vl = intvar(es, evalexpr(es, P_PRIMARY));
+ if (op == O_BNOT)
+ vl->val.i = ~vl->val.i;
+ else if (op == O_LNOT)
+ vl->val.i = !vl->val.i;
+ else if (op == O_MINUS)
+ vl->val.i = -vl->val.i;
+ /* op == O_PLUS is a no-op */
+ } else if (op == OPEN_PAREN) {
+ token(es);
+ vl = evalexpr(es, MAX_PREC);
+ if (es->tok != CLOSE_PAREN)
+ evalerr(es, ET_STR, "missing )");
+ token(es);
+ } else if (op == O_PLUSPLUS || op == O_MINUSMINUS) {
+ token(es);
+ vl = do_ppmm(es, op, es->val, true);
+ token(es);
+ } else if (op == VAR || op == LIT) {
+ vl = es->val;
+ token(es);
+ } else {
+ evalerr(es, ET_UNEXPECTED, (char *) 0);
+ /* NOTREACHED */
+ }
+ if (es->tok == O_PLUSPLUS || es->tok == O_MINUSMINUS) {
+ vl = do_ppmm(es, es->tok, vl, false);
+ token(es);
+ }
+ return vl;
+ }
+ vl = evalexpr(es, ((int) prec) - 1);
+ for (op = es->tok; IS_BINOP(op) && opinfo[(int) op].prec == prec;
+ op = es->tok) {
+ token(es);
+ vasn = vl;
+ if (op != O_ASN) /* vl may not have a value yet */
+ vl = intvar(es, vl);
+ if (IS_ASSIGNOP(op)) {
+ assign_check(es, op, vasn);
+ vr = intvar(es, evalexpr(es, P_ASSIGN));
+ } else if (op != O_TERN && op != O_LAND && op != O_LOR)
+ vr = intvar(es, evalexpr(es, ((int) prec) - 1));
+ if ((op == O_DIV || op == O_MOD || op == O_DIVASN ||
+ op == O_MODASN) && vr->val.i == 0) {
+ if (es->noassign)
+ vr->val.i = 1;
+ else
+ evalerr(es, ET_STR, "zero divisor");
+ }
+ switch ((int) op) {
+ case O_TIMES:
+ case O_TIMESASN:
+ res = vl->val.i * vr->val.i;
+ break;
+ case O_DIV:
+ case O_DIVASN:
+ if (vl->val.i == LONG_MIN && vr->val.i == -1)
+ res = LONG_MIN;
+ else
+ res = vl->val.i / vr->val.i;
+ break;
+ case O_MOD:
+ case O_MODASN:
+ if (vl->val.i == LONG_MIN && vr->val.i == -1)
+ res = 0;
+ else
+ res = vl->val.i % vr->val.i;
+ break;
+ case O_PLUS:
+ case O_PLUSASN:
+ res = vl->val.i + vr->val.i;
+ break;
+ case O_MINUS:
+ case O_MINUSASN:
+ res = vl->val.i - vr->val.i;
+ break;
+ case O_LSHIFT:
+ case O_LSHIFTASN:
+ res = vl->val.i << vr->val.i;
+ break;
+ case O_RSHIFT:
+ case O_RSHIFTASN:
+ res = vl->val.i >> vr->val.i;
+ break;
+ case O_LT:
+ res = vl->val.i < vr->val.i;
+ break;
+ case O_LE:
+ res = vl->val.i <= vr->val.i;
+ break;
+ case O_GT:
+ res = vl->val.i > vr->val.i;
+ break;
+ case O_GE:
+ res = vl->val.i >= vr->val.i;
+ break;
+ case O_EQ:
+ res = vl->val.i == vr->val.i;
+ break;
+ case O_NE:
+ res = vl->val.i != vr->val.i;
+ break;
+ case O_BAND:
+ case O_BANDASN:
+ res = vl->val.i & vr->val.i;
+ break;
+ case O_BXOR:
+ case O_BXORASN:
+ res = vl->val.i ^ vr->val.i;
+ break;
+ case O_BOR:
+ case O_BORASN:
+ res = vl->val.i | vr->val.i;
+ break;
+ case O_LAND:
+ if (!vl->val.i)
+ es->noassign++;
+ vr = intvar(es, evalexpr(es, ((int) prec) - 1));
+ res = vl->val.i && vr->val.i;
+ if (!vl->val.i)
+ es->noassign--;
+ break;
+ case O_LOR:
+ if (vl->val.i)
+ es->noassign++;
+ vr = intvar(es, evalexpr(es, ((int) prec) - 1));
+ res = vl->val.i || vr->val.i;
+ if (vl->val.i)
+ es->noassign--;
+ break;
+ case O_TERN:
+ {
+ int e = vl->val.i != 0;
+
+ if (!e)
+ es->noassign++;
+ vl = evalexpr(es, MAX_PREC);
+ if (!e)
+ es->noassign--;
+ if (es->tok != CTERN)
+ evalerr(es, ET_STR, "missing :");
+ token(es);
+ if (e)
+ es->noassign++;
+ vr = evalexpr(es, P_TERN);
+ if (e)
+ es->noassign--;
+ vl = e ? vl : vr;
+ }
+ break;
+ case O_ASN:
+ res = vr->val.i;
+ break;
+ case O_COMMA:
+ res = vr->val.i;
+ break;
+ }
+ if (IS_ASSIGNOP(op)) {
+ vr->val.i = res;
+ if (vasn->flag & INTEGER)
+ setint_v(vasn, vr, es->arith);
+ else
+ setint(vasn, res);
+ vl = vr;
+ } else if (op != O_TERN)
+ vl->val.i = res;
+ }
+ return vl;
+}
+
+static void
+token(Expr_state *es)
+{
+ const char *cp;
+ int c;
+ char *tvar;
+
+ /* skip white space */
+ for (cp = es->tokp; (c = *cp), isspace((unsigned char)c); cp++)
+ ;
+ es->tokp = cp;
+
+ if (c == '\0')
+ es->tok = END;
+ else if (letter(c)) {
+ for (; letnum(c); c = *cp)
+ cp++;
+ if (c == '[') {
+ int len;
+
+ len = array_ref_len(cp);
+ if (len == 0)
+ evalerr(es, ET_STR, "missing ]");
+ cp += len;
+ } else if (c == '(' /*)*/ ) {
+ /* todo: add math functions (all take single argument):
+ * abs acos asin atan cos cosh exp int log sin sinh sqrt
+ * tan tanh
+ */
+ ;
+ }
+ if (es->noassign) {
+ es->val = tempvar();
+ es->val->flag |= EXPRLVALUE;
+ } else {
+ tvar = str_nsave(es->tokp, cp - es->tokp, ATEMP);
+ es->val = global(tvar);
+ afree(tvar, ATEMP);
+ }
+ es->tok = VAR;
+ } else if (digit(c)) {
+ for (; c != '_' && (letnum(c) || c == '#'); c = *cp++)
+ ;
+ tvar = str_nsave(es->tokp, --cp - es->tokp, ATEMP);
+ es->val = tempvar();
+ es->val->flag &= ~INTEGER;
+ es->val->type = 0;
+ es->val->val.s = tvar;
+ if (setint_v(es->val, es->val, es->arith) == NULL)
+ evalerr(es, ET_BADLIT, tvar);
+ afree(tvar, ATEMP);
+ es->tok = LIT;
+ } else {
+ int i, n0;
+
+ for (i = 0; (n0 = opinfo[i].name[0]); i++)
+ if (c == n0 &&
+ strncmp(cp, opinfo[i].name, opinfo[i].len) == 0) {
+ es->tok = (enum token) i;
+ cp += opinfo[i].len;
+ break;
+ }
+ if (!n0)
+ es->tok = BAD;
+ }
+ es->tokp = cp;
+}
+
+/* Do a ++ or -- operation */
+static struct tbl *
+do_ppmm(Expr_state *es, enum token op, struct tbl *vasn, bool is_prefix)
+{
+ struct tbl *vl;
+ int oval;
+
+ assign_check(es, op, vasn);
+
+ vl = intvar(es, vasn);
+ oval = op == O_PLUSPLUS ? vl->val.i++ : vl->val.i--;
+ if (vasn->flag & INTEGER)
+ setint_v(vasn, vl, es->arith);
+ else
+ setint(vasn, vl->val.i);
+ if (!is_prefix) /* undo the inc/dec */
+ vl->val.i = oval;
+
+ return vl;
+}
+
+static void
+assign_check(Expr_state *es, enum token op, struct tbl *vasn)
+{
+ if (es->tok == END || vasn == NULL ||
+ (vasn->name[0] == '\0' && !(vasn->flag & EXPRLVALUE)))
+ evalerr(es, ET_LVALUE, opinfo[(int) op].name);
+ else if (vasn->flag & RDONLY)
+ evalerr(es, ET_RDONLY, opinfo[(int) op].name);
+}
+
+static struct tbl *
+tempvar(void)
+{
+ struct tbl *vp;
+
+ vp = (struct tbl*) alloc(sizeof(struct tbl), ATEMP);
+ vp->flag = ISSET|INTEGER;
+ vp->type = 0;
+ vp->areap = ATEMP;
+ vp->val.i = 0;
+ vp->name[0] = '\0';
+ return vp;
+}
+
+/* cast (string) variable to temporary integer variable */
+static struct tbl *
+intvar(Expr_state *es, struct tbl *vp)
+{
+ struct tbl *vq;
+
+ /* try to avoid replacing a temp var with another temp var */
+ if (vp->name[0] == '\0' &&
+ (vp->flag & (ISSET|INTEGER|EXPRLVALUE)) == (ISSET|INTEGER))
+ return vp;
+
+ vq = tempvar();
+ if (setint_v(vq, vp, es->arith) == NULL) {
+ if (vp->flag & EXPRINEVAL)
+ evalerr(es, ET_RECURSIVE, vp->name);
+ es->evaling = vp;
+ vp->flag |= EXPRINEVAL;
+ v_evaluate(vq, str_val(vp), KSH_UNWIND_ERROR, es->arith);
+ vp->flag &= ~EXPRINEVAL;
+ es->evaling = (struct tbl *) 0;
+ }
+ return vq;
+}
diff --git a/history.c b/history.c
@@ -0,0 +1,983 @@
+/* $OpenBSD: history.c,v 1.40 2014/11/20 15:22:39 tedu Exp $ */
+
+/*
+ * command history
+ *
+ * only implements in-memory history.
+ */
+
+/*
+ * This file contains
+ * a) the original in-memory history mechanism
+ * b) a more complicated mechanism done by pc@hillside.co.uk
+ * that more closely follows the real ksh way of doing
+ * things. You need to have the mmap system call for this
+ * to work on your system
+ */
+
+#include "sh.h"
+#include <sys/stat.h>
+
+#ifdef HISTORY
+# include <sys/mman.h>
+
+/*
+ * variables for handling the data file
+ */
+static int histfd;
+static int hsize;
+
+static int hist_count_lines(unsigned char *, int);
+static int hist_shrink(unsigned char *, int);
+static unsigned char *hist_skip_back(unsigned char *,int *,int);
+static void histload(Source *, unsigned char *, int);
+static void histinsert(Source *, int, unsigned char *);
+static void writehistfile(int, char *);
+static int sprinkle(int);
+
+static int hist_execute(char *);
+static int hist_replace(char **, const char *, const char *, int);
+static char **hist_get(const char *, int, int);
+static char **hist_get_oldest(void);
+static void histbackup(void);
+
+static char **current; /* current position in history[] */
+static char *hname; /* current name of history file */
+static int hstarted; /* set after hist_init() called */
+static Source *hist_source;
+
+
+int
+c_fc(char **wp)
+{
+ struct shf *shf;
+ struct temp *tf = NULL;
+ char *p, *editor = (char *) 0;
+ int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0;
+ int optc;
+ char *first = (char *) 0, *last = (char *) 0;
+ char **hfirst, **hlast, **hp;
+
+ if (!Flag(FTALKING_I)) {
+ bi_errorf("history functions not available");
+ return 1;
+ }
+
+ while ((optc = ksh_getopt(wp, &builtin_opt,
+ "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1)
+ switch (optc) {
+ case 'e':
+ p = builtin_opt.optarg;
+ if (strcmp(p, "-") == 0)
+ sflag++;
+ else {
+ size_t len = strlen(p) + 4;
+ editor = str_nsave(p, len, ATEMP);
+ strlcat(editor, " $_", len);
+ }
+ break;
+ case 'g': /* non-at&t ksh */
+ gflag++;
+ break;
+ case 'l':
+ lflag++;
+ break;
+ case 'n':
+ nflag++;
+ break;
+ case 'r':
+ rflag++;
+ break;
+ case 's': /* posix version of -e - */
+ sflag++;
+ break;
+ /* kludge city - accept -num as -- -num (kind of) */
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ p = shf_smprintf("-%c%s",
+ optc, builtin_opt.optarg);
+ if (!first)
+ first = p;
+ else if (!last)
+ last = p;
+ else {
+ bi_errorf("too many arguments");
+ return 1;
+ }
+ break;
+ case '?':
+ return 1;
+ }
+ wp += builtin_opt.optind;
+
+ /* Substitute and execute command */
+ if (sflag) {
+ char *pat = (char *) 0, *rep = (char *) 0;
+
+ if (editor || lflag || nflag || rflag) {
+ bi_errorf("can't use -e, -l, -n, -r with -s (-e -)");
+ return 1;
+ }
+
+ /* Check for pattern replacement argument */
+ if (*wp && **wp && (p = strchr(*wp + 1, '='))) {
+ pat = str_save(*wp, ATEMP);
+ p = pat + (p - *wp);
+ *p++ = '\0';
+ rep = p;
+ wp++;
+ }
+ /* Check for search prefix */
+ if (!first && (first = *wp))
+ wp++;
+ if (last || *wp) {
+ bi_errorf("too many arguments");
+ return 1;
+ }
+
+ hp = first ? hist_get(first, false, false) :
+ hist_get_newest(false);
+ if (!hp)
+ return 1;
+ return hist_replace(hp, pat, rep, gflag);
+ }
+
+ if (editor && (lflag || nflag)) {
+ bi_errorf("can't use -l, -n with -e");
+ return 1;
+ }
+
+ if (!first && (first = *wp))
+ wp++;
+ if (!last && (last = *wp))
+ wp++;
+ if (*wp) {
+ bi_errorf("too many arguments");
+ return 1;
+ }
+ if (!first) {
+ hfirst = lflag ? hist_get("-16", true, true) :
+ hist_get_newest(false);
+ if (!hfirst)
+ return 1;
+ /* can't fail if hfirst didn't fail */
+ hlast = hist_get_newest(false);
+ } else {
+ /* POSIX says not an error if first/last out of bounds
+ * when range is specified; at&t ksh and pdksh allow out of
+ * bounds for -l as well.
+ */
+ hfirst = hist_get(first, (lflag || last) ? true : false,
+ lflag ? true : false);
+ if (!hfirst)
+ return 1;
+ hlast = last ? hist_get(last, true, lflag ? true : false) :
+ (lflag ? hist_get_newest(false) : hfirst);
+ if (!hlast)
+ return 1;
+ }
+ if (hfirst > hlast) {
+ char **temp;
+
+ temp = hfirst; hfirst = hlast; hlast = temp;
+ rflag = !rflag; /* POSIX */
+ }
+
+ /* List history */
+ if (lflag) {
+ char *s, *t;
+ const char *nfmt = nflag ? "\t" : "%d\t";
+
+ for (hp = rflag ? hlast : hfirst;
+ hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) {
+ shf_fprintf(shl_stdout, nfmt,
+ hist_source->line - (int) (histptr - hp));
+ /* print multi-line commands correctly */
+ for (s = *hp; (t = strchr(s, '\n')); s = t)
+ shf_fprintf(shl_stdout, "%.*s\t", ++t - s, s);
+ shf_fprintf(shl_stdout, "%s\n", s);
+ }
+ shf_flush(shl_stdout);
+ return 0;
+ }
+
+ /* Run editor on selected lines, then run resulting commands */
+
+ tf = maketemp(ATEMP, TT_HIST_EDIT, &e->temps);
+ if (!(shf = tf->shf)) {
+ bi_errorf("cannot create temp file %s - %s",
+ tf->name, strerror(errno));
+ return 1;
+ }
+ for (hp = rflag ? hlast : hfirst;
+ hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1)
+ shf_fprintf(shf, "%s\n", *hp);
+ if (shf_close(shf) == EOF) {
+ bi_errorf("error writing temporary file - %s", strerror(errno));
+ return 1;
+ }
+
+ /* Ignore setstr errors here (arbitrary) */
+ setstr(local("_", false), tf->name, KSH_RETURN_ERROR);
+
+ /* XXX: source should not get trashed by this.. */
+ {
+ Source *sold = source;
+ int ret;
+
+ ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_", 0);
+ source = sold;
+ if (ret)
+ return ret;
+ }
+
+ {
+ struct stat statb;
+ XString xs;
+ char *xp;
+ int n;
+
+ if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) {
+ bi_errorf("cannot open temp file %s", tf->name);
+ return 1;
+ }
+
+ n = fstat(shf_fileno(shf), &statb) < 0 ? 128 :
+ statb.st_size + 1;
+ Xinit(xs, xp, n, hist_source->areap);
+ while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) {
+ xp += n;
+ if (Xnleft(xs, xp) <= 0)
+ XcheckN(xs, xp, Xlength(xs, xp));
+ }
+ if (n < 0) {
+ bi_errorf("error reading temp file %s - %s",
+ tf->name, strerror(shf_errno(shf)));
+ shf_close(shf);
+ return 1;
+ }
+ shf_close(shf);
+ *xp = '\0';
+ strip_nuls(Xstring(xs, xp), Xlength(xs, xp));
+ return hist_execute(Xstring(xs, xp));
+ }
+}
+
+/* Save cmd in history, execute cmd (cmd gets trashed) */
+static int
+hist_execute(char *cmd)
+{
+ Source *sold;
+ int ret;
+ char *p, *q;
+
+ histbackup();
+
+ for (p = cmd; p; p = q) {
+ if ((q = strchr(p, '\n'))) {
+ *q++ = '\0'; /* kill the newline */
+ if (!*q) /* ignore trailing newline */
+ q = (char *) 0;
+ }
+ histsave(++(hist_source->line), p, 1);
+
+ shellf("%s\n", p); /* POSIX doesn't say this is done... */
+ if ((p = q)) /* restore \n (trailing \n not restored) */
+ q[-1] = '\n';
+ }
+
+ /* Commands are executed here instead of pushing them onto the
+ * input 'cause posix says the redirection and variable assignments
+ * in
+ * X=y fc -e - 42 2> /dev/null
+ * are to effect the repeated commands environment.
+ */
+ /* XXX: source should not get trashed by this.. */
+ sold = source;
+ ret = command(cmd, 0);
+ source = sold;
+ return ret;
+}
+
+static int
+hist_replace(char **hp, const char *pat, const char *rep, int global)
+{
+ char *line;
+
+ if (!pat)
+ line = str_save(*hp, ATEMP);
+ else {
+ char *s, *s1;
+ int pat_len = strlen(pat);
+ int rep_len = strlen(rep);
+ int len;
+ XString xs;
+ char *xp;
+ int any_subst = 0;
+
+ Xinit(xs, xp, 128, ATEMP);
+ for (s = *hp; (s1 = strstr(s, pat)) && (!any_subst || global);
+ s = s1 + pat_len) {
+ any_subst = 1;
+ len = s1 - s;
+ XcheckN(xs, xp, len + rep_len);
+ memcpy(xp, s, len); /* first part */
+ xp += len;
+ memcpy(xp, rep, rep_len); /* replacement */
+ xp += rep_len;
+ }
+ if (!any_subst) {
+ bi_errorf("substitution failed");
+ return 1;
+ }
+ len = strlen(s) + 1;
+ XcheckN(xs, xp, len);
+ memcpy(xp, s, len);
+ xp += len;
+ line = Xclose(xs, xp);
+ }
+ return hist_execute(line);
+}
+
+/*
+ * get pointer to history given pattern
+ * pattern is a number or string
+ */
+static char **
+hist_get(const char *str, int approx, int allow_cur)
+{
+ char **hp = (char **) 0;
+ int n;
+
+ if (getn(str, &n)) {
+ hp = histptr + (n < 0 ? n : (n - hist_source->line));
+ if ((long)hp < (long)history) {
+ if (approx)
+ hp = hist_get_oldest();
+ else {
+ bi_errorf("%s: not in history", str);
+ hp = (char **) 0;
+ }
+ } else if (hp > histptr) {
+ if (approx)
+ hp = hist_get_newest(allow_cur);
+ else {
+ bi_errorf("%s: not in history", str);
+ hp = (char **) 0;
+ }
+ } else if (!allow_cur && hp == histptr) {
+ bi_errorf("%s: invalid range", str);
+ hp = (char **) 0;
+ }
+ } else {
+ int anchored = *str == '?' ? (++str, 0) : 1;
+
+ /* the -1 is to avoid the current fc command */
+ n = findhist(histptr - history - 1, 0, str, anchored);
+ if (n < 0) {
+ bi_errorf("%s: not in history", str);
+ hp = (char **) 0;
+ } else
+ hp = &history[n];
+ }
+ return hp;
+}
+
+/* Return a pointer to the newest command in the history */
+char **
+hist_get_newest(int allow_cur)
+{
+ if (histptr < history || (!allow_cur && histptr == history)) {
+ bi_errorf("no history (yet)");
+ return (char **) 0;
+ }
+ if (allow_cur)
+ return histptr;
+ return histptr - 1;
+}
+
+/* Return a pointer to the oldest command in the history */
+static char **
+hist_get_oldest(void)
+{
+ if (histptr <= history) {
+ bi_errorf("no history (yet)");
+ return (char **) 0;
+ }
+ return history;
+}
+
+/******************************/
+/* Back up over last histsave */
+/******************************/
+static void
+histbackup(void)
+{
+ static int last_line = -1;
+
+ if (histptr >= history && last_line != hist_source->line) {
+ hist_source->line--;
+ afree((void*)*histptr, APERM);
+ histptr--;
+ last_line = hist_source->line;
+ }
+}
+
+/*
+ * Return the current position.
+ */
+char **
+histpos(void)
+{
+ return current;
+}
+
+int
+histnum(int n)
+{
+ int last = histptr - history;
+
+ if (n < 0 || n >= last) {
+ current = histptr;
+ return last;
+ } else {
+ current = &history[n];
+ return n;
+ }
+}
+
+/*
+ * This will become unnecessary if hist_get is modified to allow
+ * searching from positions other than the end, and in either
+ * direction.
+ */
+int
+findhist(int start, int fwd, const char *str, int anchored)
+{
+ char **hp;
+ int maxhist = histptr - history;
+ int incr = fwd ? 1 : -1;
+ int len = strlen(str);
+
+ if (start < 0 || start >= maxhist)
+ start = maxhist;
+
+ hp = &history[start];
+ for (; hp >= history && hp <= histptr; hp += incr)
+ if ((anchored && strncmp(*hp, str, len) == 0) ||
+ (!anchored && strstr(*hp, str)))
+ return hp - history;
+
+ return -1;
+}
+
+int
+findhistrel(const char *str)
+{
+ int maxhist = histptr - history;
+ int start = maxhist - 1;
+ int rec = atoi(str);
+
+ if (rec == 0)
+ return -1;
+ if (rec > 0) {
+ if (rec > maxhist)
+ return -1;
+ return rec - 1;
+ }
+ if (rec > maxhist)
+ return -1;
+ return start + rec + 1;
+}
+
+/*
+ * set history
+ * this means reallocating the dataspace
+ */
+void
+sethistsize(int n)
+{
+ if (n > 0 && n != histsize) {
+ int cursize = histptr - history;
+
+ /* save most recent history */
+ if (n < cursize) {
+ memmove(history, histptr - n, n * sizeof(char *));
+ cursize = n;
+ }
+
+ history = (char **)aresize(history, n*sizeof(char *), APERM);
+
+ histsize = n;
+ histptr = history + cursize;
+ }
+}
+
+/*
+ * set history file
+ * This can mean reloading/resetting/starting history file
+ * maintenance
+ */
+void
+sethistfile(const char *name)
+{
+ /* if not started then nothing to do */
+ if (hstarted == 0)
+ return;
+
+ /* if the name is the same as the name we have */
+ if (hname && strcmp(hname, name) == 0)
+ return;
+
+ /*
+ * its a new name - possibly
+ */
+ if (histfd) {
+ /* yes the file is open */
+ (void) close(histfd);
+ histfd = 0;
+ hsize = 0;
+ afree(hname, APERM);
+ hname = NULL;
+ /* let's reset the history */
+ histptr = history - 1;
+ hist_source->line = 0;
+ }
+
+ hist_init(hist_source);
+}
+
+/*
+ * initialise the history vector
+ */
+void
+init_histvec(void)
+{
+ if (history == (char **)NULL) {
+ histsize = HISTORYSIZE;
+ history = (char **)alloc(histsize*sizeof (char *), APERM);
+ histptr = history - 1;
+ }
+}
+
+
+/*
+ * Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to
+ * a) permit HISTSIZE to control number of lines of history stored
+ * b) maintain a physical history file
+ *
+ * It turns out that there is a lot of ghastly hackery here
+ */
+
+
+/*
+ * save command in history
+ */
+void
+histsave(int lno, const char *cmd, int dowrite)
+{
+ char **hp;
+ char *c, *cp;
+
+ c = str_save(cmd, APERM);
+ if ((cp = strchr(c, '\n')) != NULL)
+ *cp = '\0';
+
+ if (histfd && dowrite)
+ writehistfile(lno, c);
+
+ hp = histptr;
+
+ if (++hp >= history + histsize) { /* remove oldest command */
+ afree((void*)*history, APERM);
+ for (hp = history; hp < history + histsize - 1; hp++)
+ hp[0] = hp[1];
+ }
+ *hp = c;
+ histptr = hp;
+}
+
+/*
+ * Write history data to a file nominated by HISTFILE
+ * if HISTFILE is unset then history still happens, but
+ * the data is not written to a file
+ * All copies of ksh looking at the file will maintain the
+ * same history. This is ksh behaviour.
+ *
+ * This stuff uses mmap()
+ * if your system ain't got it - then you'll have to undef HISTORYFILE
+ */
+
+/*
+ * Open a history file
+ * Format is:
+ * Bytes 1, 2: HMAGIC - just to check that we are dealing with
+ * the correct object
+ * Then follows a number of stored commands
+ * Each command is
+ * <command byte><command number(4 bytes)><bytes><null>
+ */
+#define HMAGIC1 0xab
+#define HMAGIC2 0xcd
+#define COMMAND 0xff
+
+void
+hist_init(Source *s)
+{
+ unsigned char *base;
+ int lines;
+ int fd;
+
+ if (Flag(FTALKING) == 0)
+ return;
+
+ hstarted = 1;
+
+ hist_source = s;
+
+ hname = str_val(global("HISTFILE"));
+ if (hname == NULL)
+ return;
+ hname = str_save(hname, APERM);
+
+ retry:
+ /* we have a file and are interactive */
+ if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0)
+ return;
+
+ histfd = savefd(fd);
+ if (histfd != fd)
+ close(fd);
+
+ (void) flock(histfd, LOCK_EX);
+
+ hsize = lseek(histfd, 0L, SEEK_END);
+
+ if (hsize == 0) {
+ /* add magic */
+ if (sprinkle(histfd)) {
+ hist_finish();
+ return;
+ }
+ }
+ else if (hsize > 0) {
+ /*
+ * we have some data
+ */
+ base = (unsigned char *)mmap(0, hsize, PROT_READ,
+ MAP_FILE|MAP_PRIVATE, histfd, 0);
+ /*
+ * check on its validity
+ */
+ if (base == MAP_FAILED || *base != HMAGIC1 || base[1] != HMAGIC2) {
+ if (base != MAP_FAILED)
+ munmap((caddr_t)base, hsize);
+ hist_finish();
+ if (unlink(hname) != 0)
+ return;
+ goto retry;
+ }
+ if (hsize > 2) {
+ lines = hist_count_lines(base+2, hsize-2);
+ if (lines > histsize) {
+ /* we need to make the file smaller */
+ if (hist_shrink(base, hsize))
+ if (unlink(hname) != 0)
+ return;
+ munmap((caddr_t)base, hsize);
+ hist_finish();
+ goto retry;
+ }
+ }
+ histload(hist_source, base+2, hsize-2);
+ munmap((caddr_t)base, hsize);
+ }
+ (void) flock(histfd, LOCK_UN);
+ hsize = lseek(histfd, 0L, SEEK_END);
+}
+
+typedef enum state {
+ shdr, /* expecting a header */
+ sline, /* looking for a null byte to end the line */
+ sn1, /* bytes 1 to 4 of a line no */
+ sn2, sn3, sn4
+} State;
+
+static int
+hist_count_lines(unsigned char *base, int bytes)
+{
+ State state = shdr;
+ int lines = 0;
+
+ while (bytes--) {
+ switch (state) {
+ case shdr:
+ if (*base == COMMAND)
+ state = sn1;
+ break;
+ case sn1:
+ state = sn2; break;
+ case sn2:
+ state = sn3; break;
+ case sn3:
+ state = sn4; break;
+ case sn4:
+ state = sline; break;
+ case sline:
+ if (*base == '\0')
+ lines++, state = shdr;
+ }
+ base++;
+ }
+ return lines;
+}
+
+/*
+ * Shrink the history file to histsize lines
+ */
+static int
+hist_shrink(unsigned char *oldbase, int oldbytes)
+{
+ int fd;
+ char nfile[1024];
+ struct stat statb;
+ unsigned char *nbase = oldbase;
+ int nbytes = oldbytes;
+
+ nbase = hist_skip_back(nbase, &nbytes, histsize);
+ if (nbase == NULL)
+ return 1;
+ if (nbase == oldbase)
+ return 0;
+
+ /*
+ * create temp file
+ */
+ (void) shf_snprintf(nfile, sizeof(nfile), "%s.%d", hname, procpid);
+ if ((fd = open(nfile, O_CREAT | O_TRUNC | O_WRONLY, 0600)) < 0)
+ return 1;
+
+ if (sprinkle(fd)) {
+ close(fd);
+ unlink(nfile);
+ return 1;
+ }
+ if (write(fd, nbase, nbytes) != nbytes) {
+ close(fd);
+ unlink(nfile);
+ return 1;
+ }
+ /*
+ * worry about who owns this file
+ */
+ if (fstat(histfd, &statb) >= 0)
+ fchown(fd, statb.st_uid, statb.st_gid);
+ close(fd);
+
+ /*
+ * rename
+ */
+ if (rename(nfile, hname) < 0)
+ return 1;
+ return 0;
+}
+
+
+/*
+ * find a pointer to the data `no' back from the end of the file
+ * return the pointer and the number of bytes left
+ */
+static unsigned char *
+hist_skip_back(unsigned char *base, int *bytes, int no)
+{
+ int lines = 0;
+ unsigned char *ep;
+
+ for (ep = base + *bytes; --ep > base; ) {
+ /* this doesn't really work: the 4 byte line number that is
+ * encoded after the COMMAND byte can itself contain the
+ * COMMAND byte....
+ */
+ for (; ep > base && *ep != COMMAND; ep--)
+ ;
+ if (ep == base)
+ break;
+ if (++lines == no) {
+ *bytes = *bytes - ((char *)ep - (char *)base);
+ return ep;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * load the history structure from the stored data
+ */
+static void
+histload(Source *s, unsigned char *base, int bytes)
+{
+ State state;
+ int lno = 0;
+ unsigned char *line = NULL;
+
+ for (state = shdr; bytes-- > 0; base++) {
+ switch (state) {
+ case shdr:
+ if (*base == COMMAND)
+ state = sn1;
+ break;
+ case sn1:
+ lno = (((*base)&0xff)<<24);
+ state = sn2;
+ break;
+ case sn2:
+ lno |= (((*base)&0xff)<<16);
+ state = sn3;
+ break;
+ case sn3:
+ lno |= (((*base)&0xff)<<8);
+ state = sn4;
+ break;
+ case sn4:
+ lno |= (*base)&0xff;
+ line = base+1;
+ state = sline;
+ break;
+ case sline:
+ if (*base == '\0') {
+ /* worry about line numbers */
+ if (histptr >= history && lno-1 != s->line) {
+ /* a replacement ? */
+ histinsert(s, lno, line);
+ }
+ else {
+ s->line = lno;
+ s->cmd_offset = lno;
+ histsave(lno, (char *)line, 0);
+ }
+ state = shdr;
+ }
+ }
+ }
+}
+
+/*
+ * Insert a line into the history at a specified number
+ */
+static void
+histinsert(Source *s, int lno, unsigned char *line)
+{
+ char **hp;
+
+ if (lno >= s->line-(histptr-history) && lno <= s->line) {
+ hp = &histptr[lno-s->line];
+ if (*hp)
+ afree((void*)*hp, APERM);
+ *hp = str_save((char *)line, APERM);
+ }
+}
+
+/*
+ * write a command to the end of the history file
+ * This *MAY* seem easy but it's also necessary to check
+ * that the history file has not changed in size.
+ * If it has - then some other shell has written to it
+ * and we should read those commands to update our history
+ */
+static void
+writehistfile(int lno, char *cmd)
+{
+ int sizenow;
+ unsigned char *base;
+ unsigned char *new;
+ int bytes;
+ unsigned char hdr[5];
+
+ (void) flock(histfd, LOCK_EX);
+ sizenow = lseek(histfd, 0L, SEEK_END);
+ if (sizenow != hsize) {
+ /*
+ * Things have changed
+ */
+ if (sizenow > hsize) {
+ /* someone has added some lines */
+ bytes = sizenow - hsize;
+ base = (unsigned char *)mmap(0, sizenow,
+ PROT_READ, MAP_FILE|MAP_PRIVATE, histfd, 0);
+ if (base == MAP_FAILED)
+ goto bad;
+ new = base + hsize;
+ if (*new != COMMAND) {
+ munmap((caddr_t)base, sizenow);
+ goto bad;
+ }
+ hist_source->line--;
+ histload(hist_source, new, bytes);
+ hist_source->line++;
+ lno = hist_source->line;
+ munmap((caddr_t)base, sizenow);
+ hsize = sizenow;
+ } else {
+ /* it has shrunk */
+ /* but to what? */
+ /* we'll give up for now */
+ goto bad;
+ }
+ }
+ /*
+ * we can write our bit now
+ */
+ hdr[0] = COMMAND;
+ hdr[1] = (lno>>24)&0xff;
+ hdr[2] = (lno>>16)&0xff;
+ hdr[3] = (lno>>8)&0xff;
+ hdr[4] = lno&0xff;
+ (void) write(histfd, hdr, 5);
+ (void) write(histfd, cmd, strlen(cmd)+1);
+ hsize = lseek(histfd, 0L, SEEK_END);
+ (void) flock(histfd, LOCK_UN);
+ return;
+bad:
+ hist_finish();
+}
+
+void
+hist_finish(void)
+{
+ (void) flock(histfd, LOCK_UN);
+ (void) close(histfd);
+ histfd = 0;
+}
+
+/*
+ * add magic to the history file
+ */
+static int
+sprinkle(int fd)
+{
+ static unsigned char mag[] = { HMAGIC1, HMAGIC2 };
+
+ return(write(fd, mag, 2) != 2);
+}
+
+#else /* HISTORY */
+
+/* No history to be compiled in: dummy routines to avoid lots more ifdefs */
+void
+init_histvec(void)
+{
+}
+void
+hist_init(Source *s)
+{
+}
+void
+hist_finish(void)
+{
+}
+void
+histsave(int lno, const char *cmd, int dowrite)
+{
+ errorf("history not enabled");
+}
+#endif /* HISTORY */
diff --git a/io.c b/io.c
@@ -0,0 +1,438 @@
+/* $OpenBSD: io.c,v 1.25 2014/08/11 20:28:47 guenther Exp $ */
+
+/*
+ * shell buffered IO and formatted output
+ */
+
+#include <ctype.h>
+#include "sh.h"
+#include <sys/stat.h>
+
+static int initio_done;
+
+/*
+ * formatted output functions
+ */
+
+
+/* A shell error occurred (eg, syntax error, etc.) */
+void
+errorf(const char *fmt, ...)
+{
+ va_list va;
+
+ shl_stdout_ok = 0; /* debugging: note that stdout not valid */
+ exstat = 1;
+ if (*fmt) {
+ error_prefix(true);
+ va_start(va, fmt);
+ shf_vfprintf(shl_out, fmt, va);
+ va_end(va);
+ shf_putchar('\n', shl_out);
+ }
+ shf_flush(shl_out);
+ unwind(LERROR);
+}
+
+/* like errorf(), but no unwind is done */
+void
+warningf(int fileline, const char *fmt, ...)
+{
+ va_list va;
+
+ error_prefix(fileline);
+ va_start(va, fmt);
+ shf_vfprintf(shl_out, fmt, va);
+ va_end(va);
+ shf_putchar('\n', shl_out);
+ shf_flush(shl_out);
+}
+
+/* Used by built-in utilities to prefix shell and utility name to message
+ * (also unwinds environments for special builtins).
+ */
+void
+bi_errorf(const char *fmt, ...)
+{
+ va_list va;
+
+ shl_stdout_ok = 0; /* debugging: note that stdout not valid */
+ exstat = 1;
+ if (*fmt) {
+ error_prefix(true);
+ /* not set when main() calls parse_args() */
+ if (builtin_argv0)
+ shf_fprintf(shl_out, "%s: ", builtin_argv0);
+ va_start(va, fmt);
+ shf_vfprintf(shl_out, fmt, va);
+ va_end(va);
+ shf_putchar('\n', shl_out);
+ }
+ shf_flush(shl_out);
+ /* POSIX special builtins and ksh special builtins cause
+ * non-interactive shells to exit.
+ * XXX odd use of KEEPASN; also may not want LERROR here
+ */
+ if ((builtin_flag & SPEC_BI) ||
+ (Flag(FPOSIX) && (builtin_flag & KEEPASN))) {
+ builtin_argv0 = (char *) 0;
+ unwind(LERROR);
+ }
+}
+
+/* Called when something that shouldn't happen does */
+void
+internal_errorf(int jump, const char *fmt, ...)
+{
+ va_list va;
+
+ error_prefix(true);
+ shf_fprintf(shl_out, "internal error: ");
+ va_start(va, fmt);
+ shf_vfprintf(shl_out, fmt, va);
+ va_end(va);
+ shf_putchar('\n', shl_out);
+ shf_flush(shl_out);
+ if (jump)
+ unwind(LERROR);
+}
+
+/* used by error reporting functions to print "ksh: .kshrc[25]: " */
+void
+error_prefix(int fileline)
+{
+ /* Avoid foo: foo[2]: ... */
+ if (!fileline || !source || !source->file ||
+ strcmp(source->file, kshname) != 0)
+ shf_fprintf(shl_out, "%s: ", kshname + (*kshname == '-'));
+ if (fileline && source && source->file != NULL) {
+ shf_fprintf(shl_out, "%s[%d]: ", source->file,
+ source->errline > 0 ? source->errline : source->line);
+ source->errline = 0;
+ }
+}
+
+/* printf to shl_out (stderr) with flush */
+void
+shellf(const char *fmt, ...)
+{
+ va_list va;
+
+ if (!initio_done) /* shl_out may not be set up yet... */
+ return;
+ va_start(va, fmt);
+ shf_vfprintf(shl_out, fmt, va);
+ va_end(va);
+ shf_flush(shl_out);
+}
+
+/* printf to shl_stdout (stdout) */
+void
+shprintf(const char *fmt, ...)
+{
+ va_list va;
+
+ if (!shl_stdout_ok)
+ internal_errorf(1, "shl_stdout not valid");
+ va_start(va, fmt);
+ shf_vfprintf(shl_stdout, fmt, va);
+ va_end(va);
+}
+
+#ifdef KSH_DEBUG
+static struct shf *kshdebug_shf;
+
+void
+kshdebug_init_(void)
+{
+ if (kshdebug_shf)
+ shf_close(kshdebug_shf);
+ kshdebug_shf = shf_open("/tmp/ksh-debug.log",
+ O_WRONLY|O_APPEND|O_CREAT, 0600, SHF_WR|SHF_MAPHI);
+ if (kshdebug_shf) {
+ shf_fprintf(kshdebug_shf, "\nNew shell[pid %d]\n", getpid());
+ shf_flush(kshdebug_shf);
+ }
+}
+
+/* print to debugging log */
+void
+kshdebug_printf_(const char *fmt, ...)
+{
+ va_list va;
+
+ if (!kshdebug_shf)
+ return;
+ va_start(va, fmt);
+ shf_fprintf(kshdebug_shf, "[%d] ", getpid());
+ shf_vfprintf(kshdebug_shf, fmt, va);
+ va_end(va);
+ shf_flush(kshdebug_shf);
+}
+
+void
+kshdebug_dump_(const char *str, const void *mem, int nbytes)
+{
+ int i, j;
+ int nprow = 16;
+
+ if (!kshdebug_shf)
+ return;
+ shf_fprintf(kshdebug_shf, "[%d] %s:\n", getpid(), str);
+ for (i = 0; i < nbytes; i += nprow) {
+ char c = '\t';
+
+ for (j = 0; j < nprow && i + j < nbytes; j++) {
+ shf_fprintf(kshdebug_shf, "%c%02x", c,
+ ((const unsigned char *) mem)[i + j]);
+ c = ' ';
+ }
+ shf_fprintf(kshdebug_shf, "\n");
+ }
+ shf_flush(kshdebug_shf);
+}
+#endif /* KSH_DEBUG */
+
+/* test if we can seek backwards fd (returns 0 or SHF_UNBUF) */
+int
+can_seek(int fd)
+{
+ struct stat statb;
+
+ return fstat(fd, &statb) == 0 && !S_ISREG(statb.st_mode) ?
+ SHF_UNBUF : 0;
+}
+
+struct shf shf_iob[3];
+
+void
+initio(void)
+{
+ shf_fdopen(1, SHF_WR, shl_stdout); /* force buffer allocation */
+ shf_fdopen(2, SHF_WR, shl_out);
+ shf_fdopen(2, SHF_WR, shl_spare); /* force buffer allocation */
+ initio_done = 1;
+ kshdebug_init();
+}
+
+/* A dup2() with error checking */
+int
+ksh_dup2(int ofd, int nfd, int errok)
+{
+ int ret = dup2(ofd, nfd);
+
+ if (ret < 0 && errno != EBADF && !errok)
+ errorf("too many files open in shell");
+
+ return ret;
+}
+
+/*
+ * move fd from user space (0<=fd<10) to shell space (fd>=10),
+ * set close-on-exec flag.
+ */
+int
+savefd(int fd)
+{
+ int nfd;
+
+ if (fd < FDBASE) {
+ nfd = fcntl(fd, F_DUPFD_CLOEXEC, FDBASE);
+ if (nfd < 0) {
+ if (errno == EBADF)
+ return -1;
+ else
+ errorf("too many files open in shell");
+ }
+ } else {
+ nfd = fd;
+ fcntl(nfd, F_SETFD, FD_CLOEXEC);
+ }
+ return nfd;
+}
+
+void
+restfd(int fd, int ofd)
+{
+ if (fd == 2)
+ shf_flush(&shf_iob[fd]);
+ if (ofd < 0) /* original fd closed */
+ close(fd);
+ else if (fd != ofd) {
+ ksh_dup2(ofd, fd, true); /* XXX: what to do if this fails? */
+ close(ofd);
+ }
+}
+
+void
+openpipe(int *pv)
+{
+ int lpv[2];
+
+ if (pipe(lpv) < 0)
+ errorf("can't create pipe - try again");
+ pv[0] = savefd(lpv[0]);
+ if (pv[0] != lpv[0])
+ close(lpv[0]);
+ pv[1] = savefd(lpv[1]);
+ if (pv[1] != lpv[1])
+ close(lpv[1]);
+}
+
+void
+closepipe(int *pv)
+{
+ close(pv[0]);
+ close(pv[1]);
+}
+
+/* Called by iosetup() (deals with 2>&4, etc.), c_read, c_print to turn
+ * a string (the X in 2>&X, read -uX, print -uX) into a file descriptor.
+ */
+int
+check_fd(char *name, int mode, const char **emsgp)
+{
+ int fd, fl;
+
+ if (isdigit((unsigned char)name[0]) && !name[1]) {
+ fd = name[0] - '0';
+ if ((fl = fcntl(fd = name[0] - '0', F_GETFL, 0)) < 0) {
+ if (emsgp)
+ *emsgp = "bad file descriptor";
+ return -1;
+ }
+ fl &= O_ACCMODE;
+ /* X_OK is a kludge to disable this check for dups (x<&1):
+ * historical shells never did this check (XXX don't know what
+ * posix has to say).
+ */
+ if (!(mode & X_OK) && fl != O_RDWR &&
+ (((mode & R_OK) && fl != O_RDONLY) ||
+ ((mode & W_OK) && fl != O_WRONLY))) {
+ if (emsgp)
+ *emsgp = (fl == O_WRONLY) ?
+ "fd not open for reading" :
+ "fd not open for writing";
+ return -1;
+ }
+ return fd;
+ } else if (name[0] == 'p' && !name[1])
+ return coproc_getfd(mode, emsgp);
+ if (emsgp)
+ *emsgp = "illegal file descriptor name";
+ return -1;
+}
+
+/* Called once from main */
+void
+coproc_init(void)
+{
+ coproc.read = coproc.readw = coproc.write = -1;
+ coproc.njobs = 0;
+ coproc.id = 0;
+}
+
+/* Called by c_read() when eof is read - close fd if it is the co-process fd */
+void
+coproc_read_close(int fd)
+{
+ if (coproc.read >= 0 && fd == coproc.read) {
+ coproc_readw_close(fd);
+ close(coproc.read);
+ coproc.read = -1;
+ }
+}
+
+/* Called by c_read() and by iosetup() to close the other side of the
+ * read pipe, so reads will actually terminate.
+ */
+void
+coproc_readw_close(int fd)
+{
+ if (coproc.readw >= 0 && coproc.read >= 0 && fd == coproc.read) {
+ close(coproc.readw);
+ coproc.readw = -1;
+ }
+}
+
+/* Called by c_print when a write to a fd fails with EPIPE and by iosetup
+ * when co-process input is dup'd
+ */
+void
+coproc_write_close(int fd)
+{
+ if (coproc.write >= 0 && fd == coproc.write) {
+ close(coproc.write);
+ coproc.write = -1;
+ }
+}
+
+/* Called to check for existence of/value of the co-process file descriptor.
+ * (Used by check_fd() and by c_read/c_print to deal with -p option).
+ */
+int
+coproc_getfd(int mode, const char **emsgp)
+{
+ int fd = (mode & R_OK) ? coproc.read : coproc.write;
+
+ if (fd >= 0)
+ return fd;
+ if (emsgp)
+ *emsgp = "no coprocess";
+ return -1;
+}
+
+/* called to close file descriptors related to the coprocess (if any)
+ * Should be called with SIGCHLD blocked.
+ */
+void
+coproc_cleanup(int reuse)
+{
+ /* This to allow co-processes to share output pipe */
+ if (!reuse || coproc.readw < 0 || coproc.read < 0) {
+ if (coproc.read >= 0) {
+ close(coproc.read);
+ coproc.read = -1;
+ }
+ if (coproc.readw >= 0) {
+ close(coproc.readw);
+ coproc.readw = -1;
+ }
+ }
+ if (coproc.write >= 0) {
+ close(coproc.write);
+ coproc.write = -1;
+ }
+}
+
+
+/*
+ * temporary files
+ */
+
+struct temp *
+maketemp(Area *ap, Temp_type type, struct temp **tlist)
+{
+ struct temp *tp;
+ int len;
+ int fd;
+ char *path;
+ const char *dir;
+
+ dir = tmpdir ? tmpdir : "/tmp";
+ /* The 20 + 20 is a paranoid worst case for pid/inc */
+ len = strlen(dir) + 3 + 20 + 20 + 1;
+ tp = (struct temp *) alloc(sizeof(struct temp) + len, ap);
+ tp->name = path = (char *) &tp[1];
+ tp->shf = (struct shf *) 0;
+ tp->type = type;
+ shf_snprintf(path, len, "%s/shXXXXXXXX", dir);
+ fd = mkstemp(path);
+ if (fd >= 0)
+ tp->shf = shf_fdopen(fd, SHF_WR, (struct shf *) 0);
+ tp->pid = procpid;
+
+ tp->next = *tlist;
+ *tlist = tp;
+ return tp;
+}
diff --git a/jobs.c b/jobs.c
@@ -0,0 +1,1653 @@
+/* $OpenBSD: jobs.c,v 1.40 2013/09/04 15:49:18 millert Exp $ */
+
+/*
+ * Process and job control
+ */
+
+/*
+ * Reworked/Rewritten version of Eric Gisin's/Ron Natalie's code by
+ * Larry Bouzane (larry@cs.mun.ca) and hacked again by
+ * Michael Rendell (michael@cs.mun.ca)
+ *
+ * The interface to the rest of the shell should probably be changed
+ * to allow use of vfork() when available but that would be way too much
+ * work :)
+ *
+ */
+
+#include "sh.h"
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include "tty.h"
+
+/* Order important! */
+#define PRUNNING 0
+#define PEXITED 1
+#define PSIGNALLED 2
+#define PSTOPPED 3
+
+typedef struct proc Proc;
+struct proc {
+ Proc *next; /* next process in pipeline (if any) */
+ int state;
+ int status; /* wait status */
+ pid_t pid; /* process id */
+ char command[48]; /* process command string */
+};
+
+/* Notify/print flag - j_print() argument */
+#define JP_NONE 0 /* don't print anything */
+#define JP_SHORT 1 /* print signals processes were killed by */
+#define JP_MEDIUM 2 /* print [job-num] -/+ command */
+#define JP_LONG 3 /* print [job-num] -/+ pid command */
+#define JP_PGRP 4 /* print pgrp */
+
+/* put_job() flags */
+#define PJ_ON_FRONT 0 /* at very front */
+#define PJ_PAST_STOPPED 1 /* just past any stopped jobs */
+
+/* Job.flags values */
+#define JF_STARTED 0x001 /* set when all processes in job are started */
+#define JF_WAITING 0x002 /* set if j_waitj() is waiting on job */
+#define JF_W_ASYNCNOTIFY 0x004 /* set if waiting and async notification ok */
+#define JF_XXCOM 0x008 /* set for `command` jobs */
+#define JF_FG 0x010 /* running in foreground (also has tty pgrp) */
+#define JF_SAVEDTTY 0x020 /* j->ttystate is valid */
+#define JF_CHANGED 0x040 /* process has changed state */
+#define JF_KNOWN 0x080 /* $! referenced */
+#define JF_ZOMBIE 0x100 /* known, unwaited process */
+#define JF_REMOVE 0x200 /* flagged for removal (j_jobs()/j_noityf()) */
+#define JF_USETTYMODE 0x400 /* tty mode saved if process exits normally */
+#define JF_SAVEDTTYPGRP 0x800 /* j->saved_ttypgrp is valid */
+
+typedef struct job Job;
+struct job {
+ Job *next; /* next job in list */
+ int job; /* job number: %n */
+ int flags; /* see JF_* */
+ int state; /* job state */
+ int status; /* exit status of last process */
+ pid_t pgrp; /* process group of job */
+ pid_t ppid; /* pid of process that forked job */
+ INT32 age; /* number of jobs started */
+ struct timeval systime; /* system time used by job */
+ struct timeval usrtime; /* user time used by job */
+ Proc *proc_list; /* process list */
+ Proc *last_proc; /* last process in list */
+ Coproc_id coproc_id; /* 0 or id of coprocess output pipe */
+#ifdef JOBS
+ struct termios ttystate;/* saved tty state for stopped jobs */
+ pid_t saved_ttypgrp; /* saved tty process group for stopped jobs */
+#endif /* JOBS */
+};
+
+/* Flags for j_waitj() */
+#define JW_NONE 0x00
+#define JW_INTERRUPT 0x01 /* ^C will stop the wait */
+#define JW_ASYNCNOTIFY 0x02 /* asynchronous notification during wait ok */
+#define JW_STOPPEDWAIT 0x04 /* wait even if job stopped */
+
+/* Error codes for j_lookup() */
+#define JL_OK 0
+#define JL_NOSUCH 1 /* no such job */
+#define JL_AMBIG 2 /* %foo or %?foo is ambiguous */
+#define JL_INVALID 3 /* non-pid, non-% job id */
+
+static const char *const lookup_msgs[] = {
+ null,
+ "no such job",
+ "ambiguous",
+ "argument must be %job or process id",
+ (char *) 0
+};
+
+struct timeval j_systime, j_usrtime; /* user and system time of last j_waitjed job */
+
+static Job *job_list; /* job list */
+static Job *last_job;
+static Job *async_job;
+static pid_t async_pid;
+
+static int nzombie; /* # of zombies owned by this process */
+INT32 njobs; /* # of jobs started */
+static int child_max; /* CHILD_MAX */
+
+
+/* held_sigchld is set if sigchld occurs before a job is completely started */
+static volatile sig_atomic_t held_sigchld;
+
+#ifdef JOBS
+static struct shf *shl_j;
+static int ttypgrp_ok; /* set if can use tty pgrps */
+static pid_t restore_ttypgrp = -1;
+static pid_t our_pgrp;
+static int const tt_sigs[] = { SIGTSTP, SIGTTIN, SIGTTOU };
+#endif /* JOBS */
+
+static void j_set_async(Job *);
+static void j_startjob(Job *);
+static int j_waitj(Job *, int, const char *);
+static void j_sigchld(int);
+static void j_print(Job *, int, struct shf *);
+static Job *j_lookup(const char *, int *);
+static Job *new_job(void);
+static Proc *new_proc(void);
+static void check_job(Job *);
+static void put_job(Job *, int);
+static void remove_job(Job *, const char *);
+static int kill_job(Job *, int);
+
+/* initialize job control */
+void
+j_init(int mflagset)
+{
+ child_max = CHILD_MAX; /* so syscon() isn't always being called */
+
+ sigemptyset(&sm_default);
+ sigprocmask(SIG_SETMASK, &sm_default, (sigset_t *) 0);
+
+ sigemptyset(&sm_sigchld);
+ sigaddset(&sm_sigchld, SIGCHLD);
+
+ setsig(&sigtraps[SIGCHLD], j_sigchld,
+ SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
+
+#ifdef JOBS
+ if (!mflagset && Flag(FTALKING))
+ Flag(FMONITOR) = 1;
+
+ /* shl_j is used to do asynchronous notification (used in
+ * an interrupt handler, so need a distinct shf)
+ */
+ shl_j = shf_fdopen(2, SHF_WR, (struct shf *) 0);
+
+ if (Flag(FMONITOR) || Flag(FTALKING)) {
+ int i;
+
+ /* the TF_SHELL_USES test is a kludge that lets us know if
+ * if the signals have been changed by the shell.
+ */
+ for (i = NELEM(tt_sigs); --i >= 0; ) {
+ sigtraps[tt_sigs[i]].flags |= TF_SHELL_USES;
+ /* j_change() sets this to SS_RESTORE_DFL if FMONITOR */
+ setsig(&sigtraps[tt_sigs[i]], SIG_IGN,
+ SS_RESTORE_IGN|SS_FORCE);
+ }
+ }
+
+ /* j_change() calls tty_init() */
+ if (Flag(FMONITOR))
+ j_change();
+ else
+#endif /* JOBS */
+ if (Flag(FTALKING))
+ tty_init(true);
+}
+
+/* suspend the shell */
+void
+j_suspend(void)
+{
+ struct sigaction sa, osa;
+
+ /* Restore tty and pgrp. */
+ if (ttypgrp_ok) {
+ tcsetattr(tty_fd, TCSADRAIN, &tty_state);
+ if (restore_ttypgrp >= 0) {
+ if (tcsetpgrp(tty_fd, restore_ttypgrp) < 0) {
+ warningf(false,
+ "j_suspend: tcsetpgrp() failed: %s",
+ strerror(errno));
+ } else {
+ if (setpgid(0, restore_ttypgrp) < 0) {
+ warningf(false,
+ "j_suspend: setpgid() failed: %s",
+ strerror(errno));
+ }
+ }
+ }
+ }
+
+ /* Suspend the shell. */
+ memset(&sa, 0, sizeof(sa));
+ sigemptyset(&sa.sa_mask);
+ sa.sa_handler = SIG_DFL;
+ sigaction(SIGTSTP, &sa, &osa);
+ kill(0, SIGTSTP);
+
+ /* Back from suspend, reset signals, pgrp and tty. */
+ sigaction(SIGTSTP, &osa, NULL);
+ if (ttypgrp_ok) {
+ if (restore_ttypgrp >= 0) {
+ if (setpgid(0, kshpid) < 0) {
+ warningf(false,
+ "j_suspend: setpgid() failed: %s",
+ strerror(errno));
+ ttypgrp_ok = 0;
+ } else {
+ if (tcsetpgrp(tty_fd, kshpid) < 0) {
+ warningf(false,
+ "j_suspend: tcsetpgrp() failed: %s",
+ strerror(errno));
+ ttypgrp_ok = 0;
+ }
+ }
+ }
+ tty_init(true);
+ }
+}
+
+/* job cleanup before shell exit */
+void
+j_exit(void)
+{
+ /* kill stopped, and possibly running, jobs */
+ Job *j;
+ int killed = 0;
+
+ for (j = job_list; j != (Job *) 0; j = j->next) {
+ if (j->ppid == procpid &&
+ (j->state == PSTOPPED ||
+ (j->state == PRUNNING &&
+ ((j->flags & JF_FG) ||
+ (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid))))) {
+ killed = 1;
+ if (j->pgrp == 0)
+ kill_job(j, SIGHUP);
+ else
+ killpg(j->pgrp, SIGHUP);
+#ifdef JOBS
+ if (j->state == PSTOPPED) {
+ if (j->pgrp == 0)
+ kill_job(j, SIGCONT);
+ else
+ killpg(j->pgrp, SIGCONT);
+ }
+#endif /* JOBS */
+ }
+ }
+ if (killed)
+ sleep(1);
+ j_notify();
+
+#ifdef JOBS
+ if (kshpid == procpid && restore_ttypgrp >= 0) {
+ /* Need to restore the tty pgrp to what it was when the
+ * shell started up, so that the process that started us
+ * will be able to access the tty when we are done.
+ * Also need to restore our process group in case we are
+ * about to do an exec so that both our parent and the
+ * process we are to become will be able to access the tty.
+ */
+ tcsetpgrp(tty_fd, restore_ttypgrp);
+ setpgid(0, restore_ttypgrp);
+ }
+ if (Flag(FMONITOR)) {
+ Flag(FMONITOR) = 0;
+ j_change();
+ }
+#endif /* JOBS */
+}
+
+#ifdef JOBS
+/* turn job control on or off according to Flag(FMONITOR) */
+void
+j_change(void)
+{
+ int i;
+
+ if (Flag(FMONITOR)) {
+ int use_tty;
+
+ if (Flag(FTALKING)) {
+ /* Don't call tcgetattr() 'til we own the tty process group */
+ use_tty = 1;
+ tty_init(false);
+ } else
+ use_tty = 0;
+
+ /* no controlling tty, no SIGT* */
+ ttypgrp_ok = use_tty && tty_fd >= 0 && tty_devtty;
+
+ if (ttypgrp_ok && (our_pgrp = getpgrp()) < 0) {
+ warningf(false, "j_init: getpgrp() failed: %s",
+ strerror(errno));
+ ttypgrp_ok = 0;
+ }
+ if (ttypgrp_ok) {
+ setsig(&sigtraps[SIGTTIN], SIG_DFL,
+ SS_RESTORE_ORIG|SS_FORCE);
+ /* wait to be given tty (POSIX.1, B.2, job control) */
+ while (1) {
+ pid_t ttypgrp;
+
+ if ((ttypgrp = tcgetpgrp(tty_fd)) < 0) {
+ warningf(false,
+ "j_init: tcgetpgrp() failed: %s",
+ strerror(errno));
+ ttypgrp_ok = 0;
+ break;
+ }
+ if (ttypgrp == our_pgrp)
+ break;
+ kill(0, SIGTTIN);
+ }
+ }
+ for (i = NELEM(tt_sigs); --i >= 0; )
+ setsig(&sigtraps[tt_sigs[i]], SIG_IGN,
+ SS_RESTORE_DFL|SS_FORCE);
+ if (ttypgrp_ok && our_pgrp != kshpid) {
+ if (setpgid(0, kshpid) < 0) {
+ warningf(false,
+ "j_init: setpgid() failed: %s",
+ strerror(errno));
+ ttypgrp_ok = 0;
+ } else {
+ if (tcsetpgrp(tty_fd, kshpid) < 0) {
+ warningf(false,
+ "j_init: tcsetpgrp() failed: %s",
+ strerror(errno));
+ ttypgrp_ok = 0;
+ } else
+ restore_ttypgrp = our_pgrp;
+ our_pgrp = kshpid;
+ }
+ }
+ if (use_tty) {
+ if (!ttypgrp_ok)
+ warningf(false, "warning: won't have full job control");
+ }
+ if (tty_fd >= 0)
+ tcgetattr(tty_fd, &tty_state);
+ } else {
+ ttypgrp_ok = 0;
+ if (Flag(FTALKING))
+ for (i = NELEM(tt_sigs); --i >= 0; )
+ setsig(&sigtraps[tt_sigs[i]], SIG_IGN,
+ SS_RESTORE_IGN|SS_FORCE);
+ else
+ for (i = NELEM(tt_sigs); --i >= 0; ) {
+ if (sigtraps[tt_sigs[i]].flags &
+ (TF_ORIG_IGN | TF_ORIG_DFL))
+ setsig(&sigtraps[tt_sigs[i]],
+ (sigtraps[tt_sigs[i]].flags & TF_ORIG_IGN) ?
+ SIG_IGN : SIG_DFL,
+ SS_RESTORE_ORIG|SS_FORCE);
+ }
+ if (!Flag(FTALKING))
+ tty_close();
+ }
+}
+#endif /* JOBS */
+
+/* execute tree in child subprocess */
+int
+exchild(struct op *t, int flags, volatile int *xerrok,
+ int close_fd) /* used if XPCLOSE or XCCLOSE */
+{
+ static Proc *last_proc; /* for pipelines */
+
+ int i;
+ sigset_t omask;
+ Proc *p;
+ Job *j;
+ int rv = 0;
+ int forksleep;
+ int ischild;
+
+ if (flags & XEXEC)
+ /* Clear XFORK|XPCLOSE|XCCLOSE|XCOPROC|XPIPEO|XPIPEI|XXCOM|XBGND
+ * (also done in another execute() below)
+ */
+ return execute(t, flags & (XEXEC | XERROK), xerrok);
+
+ /* no SIGCHLD's while messing with job and process lists */
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ p = new_proc();
+ p->next = (Proc *) 0;
+ p->state = PRUNNING;
+ p->status = 0;
+ p->pid = 0;
+
+ /* link process into jobs list */
+ if (flags&XPIPEI) { /* continuing with a pipe */
+ if (!last_job)
+ internal_errorf(1,
+ "exchild: XPIPEI and no last_job - pid %d",
+ (int) procpid);
+ j = last_job;
+ last_proc->next = p;
+ last_proc = p;
+ } else {
+ j = new_job(); /* fills in j->job */
+ /* we don't consider XXCOM's foreground since they don't get
+ * tty process group and we don't save or restore tty modes.
+ */
+ j->flags = (flags & XXCOM) ? JF_XXCOM :
+ ((flags & XBGND) ? 0 : (JF_FG|JF_USETTYMODE));
+ timerclear(&j->usrtime);
+ timerclear(&j->systime);
+ j->state = PRUNNING;
+ j->pgrp = 0;
+ j->ppid = procpid;
+ j->age = ++njobs;
+ j->proc_list = p;
+ j->coproc_id = 0;
+ last_job = j;
+ last_proc = p;
+ put_job(j, PJ_PAST_STOPPED);
+ }
+
+ snptreef(p->command, sizeof(p->command), "%T", t);
+
+ /* create child process */
+ forksleep = 1;
+ while ((i = fork()) < 0 && errno == EAGAIN && forksleep < 32) {
+ if (intrsig) /* allow user to ^C out... */
+ break;
+ sleep(forksleep);
+ forksleep <<= 1;
+ }
+ if (i < 0) {
+ kill_job(j, SIGKILL);
+ remove_job(j, "fork failed");
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ errorf("cannot fork - try again");
+ }
+ ischild = i == 0;
+ if (ischild)
+ p->pid = procpid = getpid();
+ else
+ p->pid = i;
+
+#ifdef JOBS
+ /* job control set up */
+ if (Flag(FMONITOR) && !(flags&XXCOM)) {
+ int dotty = 0;
+ if (j->pgrp == 0) { /* First process */
+ j->pgrp = p->pid;
+ dotty = 1;
+ }
+
+ /* set pgrp in both parent and child to deal with race
+ * condition
+ */
+ setpgid(p->pid, j->pgrp);
+ /* YYY: should this be
+ if (ttypgrp_ok && ischild && !(flags&XBGND))
+ tcsetpgrp(tty_fd, j->pgrp);
+ instead? (see also YYY below)
+ */
+ if (ttypgrp_ok && dotty && !(flags & XBGND))
+ tcsetpgrp(tty_fd, j->pgrp);
+ }
+#endif /* JOBS */
+
+ /* used to close pipe input fd */
+ if (close_fd >= 0 && (((flags & XPCLOSE) && !ischild) ||
+ ((flags & XCCLOSE) && ischild)))
+ close(close_fd);
+ if (ischild) { /* child */
+ /* Do this before restoring signal */
+ if (flags & XCOPROC)
+ coproc_cleanup(false);
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ cleanup_parents_env();
+#ifdef JOBS
+ /* If FMONITOR or FTALKING is set, these signals are ignored,
+ * if neither FMONITOR nor FTALKING are set, the signals have
+ * their inherited values.
+ */
+ if (Flag(FMONITOR) && !(flags & XXCOM)) {
+ for (i = NELEM(tt_sigs); --i >= 0; )
+ setsig(&sigtraps[tt_sigs[i]], SIG_DFL,
+ SS_RESTORE_DFL|SS_FORCE);
+ }
+#endif /* JOBS */
+ if (Flag(FBGNICE) && (flags & XBGND))
+ nice(4);
+ if ((flags & XBGND) && !Flag(FMONITOR)) {
+ setsig(&sigtraps[SIGINT], SIG_IGN,
+ SS_RESTORE_IGN|SS_FORCE);
+ setsig(&sigtraps[SIGQUIT], SIG_IGN,
+ SS_RESTORE_IGN|SS_FORCE);
+ if (!(flags & (XPIPEI | XCOPROC))) {
+ int fd = open("/dev/null", 0);
+ if (fd != 0) {
+ (void) ksh_dup2(fd, 0, true);
+ close(fd);
+ }
+ }
+ }
+ remove_job(j, "child"); /* in case of `jobs` command */
+ nzombie = 0;
+#ifdef JOBS
+ ttypgrp_ok = 0;
+ Flag(FMONITOR) = 0;
+#endif /* JOBS */
+ Flag(FTALKING) = 0;
+ tty_close();
+ cleartraps();
+ execute(t, (flags & XERROK) | XEXEC, NULL); /* no return */
+ internal_errorf(0, "exchild: execute() returned");
+ unwind(LLEAVE);
+ /* NOTREACHED */
+ }
+
+ /* shell (parent) stuff */
+ /* Ensure next child gets a (slightly) different $RANDOM sequence */
+ change_random();
+ if (!(flags & XPIPEO)) { /* last process in a job */
+#ifdef JOBS
+ /* YYY: Is this needed? (see also YYY above)
+ if (Flag(FMONITOR) && !(flags&(XXCOM|XBGND)))
+ tcsetpgrp(tty_fd, j->pgrp);
+ */
+#endif /* JOBS */
+ j_startjob(j);
+ if (flags & XCOPROC) {
+ j->coproc_id = coproc.id;
+ coproc.njobs++; /* n jobs using co-process output */
+ coproc.job = (void *) j; /* j using co-process input */
+ }
+ if (flags & XBGND) {
+ j_set_async(j);
+ if (Flag(FTALKING)) {
+ shf_fprintf(shl_out, "[%d]", j->job);
+ for (p = j->proc_list; p; p = p->next)
+ shf_fprintf(shl_out, " %d", p->pid);
+ shf_putchar('\n', shl_out);
+ shf_flush(shl_out);
+ }
+ } else
+ rv = j_waitj(j, JW_NONE, "jw:last proc");
+ }
+
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+
+ return rv;
+}
+
+/* start the last job: only used for `command` jobs */
+void
+startlast(void)
+{
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ if (last_job) { /* no need to report error - waitlast() will do it */
+ /* ensure it isn't removed by check_job() */
+ last_job->flags |= JF_WAITING;
+ j_startjob(last_job);
+ }
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+}
+
+/* wait for last job: only used for `command` jobs */
+int
+waitlast(void)
+{
+ int rv;
+ Job *j;
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ j = last_job;
+ if (!j || !(j->flags & JF_STARTED)) {
+ if (!j)
+ warningf(true, "waitlast: no last job");
+ else
+ internal_errorf(0, "waitlast: not started");
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ return 125; /* not so arbitrary, non-zero value */
+ }
+
+ rv = j_waitj(j, JW_NONE, "jw:waitlast");
+
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+
+ return rv;
+}
+
+/* wait for child, interruptable. */
+int
+waitfor(const char *cp, int *sigp)
+{
+ int rv;
+ Job *j;
+ int ecode;
+ int flags = JW_INTERRUPT|JW_ASYNCNOTIFY;
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ *sigp = 0;
+
+ if (cp == (char *) 0) {
+ /* wait for an unspecified job - always returns 0, so
+ * don't have to worry about exited/signaled jobs
+ */
+ for (j = job_list; j; j = j->next)
+ /* at&t ksh will wait for stopped jobs - we don't */
+ if (j->ppid == procpid && j->state == PRUNNING)
+ break;
+ if (!j) {
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ return -1;
+ }
+ } else if ((j = j_lookup(cp, &ecode))) {
+ /* don't report normal job completion */
+ flags &= ~JW_ASYNCNOTIFY;
+ if (j->ppid != procpid) {
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ return -1;
+ }
+ } else {
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ if (ecode != JL_NOSUCH)
+ bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
+ return -1;
+ }
+
+ /* at&t ksh will wait for stopped jobs - we don't */
+ rv = j_waitj(j, flags, "jw:waitfor");
+
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+
+ if (rv < 0) /* we were interrupted */
+ *sigp = 128 + -rv;
+
+ return rv;
+}
+
+/* kill (built-in) a job */
+int
+j_kill(const char *cp, int sig)
+{
+ Job *j;
+ int rv = 0;
+ int ecode;
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ if ((j = j_lookup(cp, &ecode)) == (Job *) 0) {
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
+ return 1;
+ }
+
+ if (j->pgrp == 0) { /* started when !Flag(FMONITOR) */
+ if (kill_job(j, sig) < 0) {
+ bi_errorf("%s: %s", cp, strerror(errno));
+ rv = 1;
+ }
+ } else {
+#ifdef JOBS
+ if (j->state == PSTOPPED && (sig == SIGTERM || sig == SIGHUP))
+ (void) killpg(j->pgrp, SIGCONT);
+#endif /* JOBS */
+ if (killpg(j->pgrp, sig) < 0) {
+ bi_errorf("%s: %s", cp, strerror(errno));
+ rv = 1;
+ }
+ }
+
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+
+ return rv;
+}
+
+#ifdef JOBS
+/* fg and bg built-ins: called only if Flag(FMONITOR) set */
+int
+j_resume(const char *cp, int bg)
+{
+ Job *j;
+ Proc *p;
+ int ecode;
+ int running;
+ int rv = 0;
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ if ((j = j_lookup(cp, &ecode)) == (Job *) 0) {
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
+ return 1;
+ }
+
+ if (j->pgrp == 0) {
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ bi_errorf("job not job-controlled");
+ return 1;
+ }
+
+ if (bg)
+ shprintf("[%d] ", j->job);
+
+ running = 0;
+ for (p = j->proc_list; p != (Proc *) 0; p = p->next) {
+ if (p->state == PSTOPPED) {
+ p->state = PRUNNING;
+ p->status = 0;
+ running = 1;
+ }
+ shprintf("%s%s", p->command, p->next ? "| " : null);
+ }
+ shprintf(newline);
+ shf_flush(shl_stdout);
+ if (running)
+ j->state = PRUNNING;
+
+ put_job(j, PJ_PAST_STOPPED);
+ if (bg)
+ j_set_async(j);
+ else {
+# ifdef JOBS
+ /* attach tty to job */
+ if (j->state == PRUNNING) {
+ if (ttypgrp_ok && (j->flags & JF_SAVEDTTY))
+ tcsetattr(tty_fd, TCSADRAIN, &j->ttystate);
+ /* See comment in j_waitj regarding saved_ttypgrp. */
+ if (ttypgrp_ok &&
+ tcsetpgrp(tty_fd, (j->flags & JF_SAVEDTTYPGRP) ?
+ j->saved_ttypgrp : j->pgrp) < 0) {
+ if (j->flags & JF_SAVEDTTY)
+ tcsetattr(tty_fd, TCSADRAIN, &tty_state);
+ sigprocmask(SIG_SETMASK, &omask,
+ (sigset_t *) 0);
+ bi_errorf("1st tcsetpgrp(%d, %d) failed: %s",
+ tty_fd,
+ (int) ((j->flags & JF_SAVEDTTYPGRP) ?
+ j->saved_ttypgrp : j->pgrp),
+ strerror(errno));
+ return 1;
+ }
+ }
+# endif /* JOBS */
+ j->flags |= JF_FG;
+ j->flags &= ~JF_KNOWN;
+ if (j == async_job)
+ async_job = (Job *) 0;
+ }
+
+ if (j->state == PRUNNING && killpg(j->pgrp, SIGCONT) < 0) {
+ int err = errno;
+
+ if (!bg) {
+ j->flags &= ~JF_FG;
+# ifdef JOBS
+ if (ttypgrp_ok && (j->flags & JF_SAVEDTTY))
+ tcsetattr(tty_fd, TCSADRAIN, &tty_state);
+ if (ttypgrp_ok && tcsetpgrp(tty_fd, our_pgrp) < 0) {
+ warningf(true,
+ "fg: 2nd tcsetpgrp(%d, %d) failed: %s",
+ tty_fd, (int) our_pgrp,
+ strerror(errno));
+ }
+# endif /* JOBS */
+ }
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ bi_errorf("cannot continue job %s: %s",
+ cp, strerror(err));
+ return 1;
+ }
+ if (!bg) {
+# ifdef JOBS
+ if (ttypgrp_ok) {
+ j->flags &= ~(JF_SAVEDTTY | JF_SAVEDTTYPGRP);
+ }
+# endif /* JOBS */
+ rv = j_waitj(j, JW_NONE, "jw:resume");
+ }
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ return rv;
+}
+#endif /* JOBS */
+
+/* are there any running or stopped jobs ? */
+int
+j_stopped_running(void)
+{
+ Job *j;
+ int which = 0;
+
+ for (j = job_list; j != (Job *) 0; j = j->next) {
+#ifdef JOBS
+ if (j->ppid == procpid && j->state == PSTOPPED)
+ which |= 1;
+#endif /* JOBS */
+ if (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid &&
+ j->ppid == procpid && j->state == PRUNNING)
+ which |= 2;
+ }
+ if (which) {
+ shellf("You have %s%s%s jobs\n",
+ which & 1 ? "stopped" : "",
+ which == 3 ? " and " : "",
+ which & 2 ? "running" : "");
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+j_njobs(void)
+{
+ Job *j;
+ int nj = 0;
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+ for (j = job_list; j; j = j->next)
+ nj++;
+
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ return nj;
+}
+
+
+/* list jobs for jobs built-in */
+int
+j_jobs(const char *cp, int slp,
+ int nflag) /* 0: short, 1: long, 2: pgrp */
+{
+ Job *j, *tmp;
+ int how;
+ int zflag = 0;
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ if (nflag < 0) { /* kludge: print zombies */
+ nflag = 0;
+ zflag = 1;
+ }
+ if (cp) {
+ int ecode;
+
+ if ((j = j_lookup(cp, &ecode)) == (Job *) 0) {
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ bi_errorf("%s: %s", cp, lookup_msgs[ecode]);
+ return 1;
+ }
+ } else
+ j = job_list;
+ how = slp == 0 ? JP_MEDIUM : (slp == 1 ? JP_LONG : JP_PGRP);
+ for (; j; j = j->next) {
+ if ((!(j->flags & JF_ZOMBIE) || zflag) &&
+ (!nflag || (j->flags & JF_CHANGED))) {
+ j_print(j, how, shl_stdout);
+ if (j->state == PEXITED || j->state == PSIGNALLED)
+ j->flags |= JF_REMOVE;
+ }
+ if (cp)
+ break;
+ }
+ /* Remove jobs after printing so there won't be multiple + or - jobs */
+ for (j = job_list; j; j = tmp) {
+ tmp = j->next;
+ if (j->flags & JF_REMOVE)
+ remove_job(j, "jobs");
+ }
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+ return 0;
+}
+
+/* list jobs for top-level notification */
+void
+j_notify(void)
+{
+ Job *j, *tmp;
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+ for (j = job_list; j; j = j->next) {
+#ifdef JOBS
+ if (Flag(FMONITOR) && (j->flags & JF_CHANGED))
+ j_print(j, JP_MEDIUM, shl_out);
+#endif /* JOBS */
+ /* Remove job after doing reports so there aren't
+ * multiple +/- jobs.
+ */
+ if (j->state == PEXITED || j->state == PSIGNALLED)
+ j->flags |= JF_REMOVE;
+ }
+ for (j = job_list; j; j = tmp) {
+ tmp = j->next;
+ if (j->flags & JF_REMOVE)
+ remove_job(j, "notify");
+ }
+ shf_flush(shl_out);
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+}
+
+/* Return pid of last process in last asynchronous job */
+pid_t
+j_async(void)
+{
+ sigset_t omask;
+
+ sigprocmask(SIG_BLOCK, &sm_sigchld, &omask);
+
+ if (async_job)
+ async_job->flags |= JF_KNOWN;
+
+ sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0);
+
+ return async_pid;
+}
+
+/* Make j the last async process
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+j_set_async(Job *j)
+{
+ Job *jl, *oldest;
+
+ if (async_job && (async_job->flags & (JF_KNOWN|JF_ZOMBIE)) == JF_ZOMBIE)
+ remove_job(async_job, "async");
+ if (!(j->flags & JF_STARTED)) {
+ internal_errorf(0, "j_async: job not started");
+ return;
+ }
+ async_job = j;
+ async_pid = j->last_proc->pid;
+ while (nzombie > child_max) {
+ oldest = (Job *) 0;
+ for (jl = job_list; jl; jl = jl->next)
+ if (jl != async_job && (jl->flags & JF_ZOMBIE) &&
+ (!oldest || jl->age < oldest->age))
+ oldest = jl;
+ if (!oldest) {
+ /* XXX debugging */
+ if (!(async_job->flags & JF_ZOMBIE) || nzombie != 1) {
+ internal_errorf(0,
+ "j_async: bad nzombie (%d)", nzombie);
+ nzombie = 0;
+ }
+ break;
+ }
+ remove_job(oldest, "zombie");
+ }
+}
+
+/* Start a job: set STARTED, check for held signals and set j->last_proc
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+j_startjob(Job *j)
+{
+ Proc *p;
+
+ j->flags |= JF_STARTED;
+ for (p = j->proc_list; p->next; p = p->next)
+ ;
+ j->last_proc = p;
+
+ if (held_sigchld) {
+ held_sigchld = 0;
+ /* Don't call j_sigchld() as it may remove job... */
+ kill(procpid, SIGCHLD);
+ }
+}
+
+/*
+ * wait for job to complete or change state
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static int
+j_waitj(Job *j,
+ int flags, /* see JW_* */
+ const char *where)
+{
+ int rv;
+
+ /*
+ * No auto-notify on the job we are waiting on.
+ */
+ j->flags |= JF_WAITING;
+ if (flags & JW_ASYNCNOTIFY)
+ j->flags |= JF_W_ASYNCNOTIFY;
+
+ if (!Flag(FMONITOR))
+ flags |= JW_STOPPEDWAIT;
+
+ while ((volatile int) j->state == PRUNNING ||
+ ((flags & JW_STOPPEDWAIT) && (volatile int) j->state == PSTOPPED)) {
+ sigsuspend(&sm_default);
+ if (fatal_trap) {
+ int oldf = j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY);
+ j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
+ runtraps(TF_FATAL);
+ j->flags |= oldf; /* not reached... */
+ }
+ if ((flags & JW_INTERRUPT) && (rv = trap_pending())) {
+ j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
+ return -rv;
+ }
+ }
+ j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY);
+
+ if (j->flags & JF_FG) {
+ int status;
+
+ j->flags &= ~JF_FG;
+#ifdef JOBS
+ if (Flag(FMONITOR) && ttypgrp_ok && j->pgrp) {
+ /*
+ * Save the tty's current pgrp so it can be restored
+ * when the job is foregrounded. This is to
+ * deal with things like the GNU su which does
+ * a fork/exec instead of an exec (the fork means
+ * the execed shell gets a different pid from its
+ * pgrp, so naturally it sets its pgrp and gets hosed
+ * when it gets foregrounded by the parent shell, which
+ * has restored the tty's pgrp to that of the su
+ * process).
+ */
+ if (j->state == PSTOPPED &&
+ (j->saved_ttypgrp = tcgetpgrp(tty_fd)) >= 0)
+ j->flags |= JF_SAVEDTTYPGRP;
+ if (tcsetpgrp(tty_fd, our_pgrp) < 0) {
+ warningf(true,
+ "j_waitj: tcsetpgrp(%d, %d) failed: %s",
+ tty_fd, (int) our_pgrp,
+ strerror(errno));
+ }
+ if (j->state == PSTOPPED) {
+ j->flags |= JF_SAVEDTTY;
+ tcgetattr(tty_fd, &j->ttystate);
+ }
+ }
+#endif /* JOBS */
+ if (tty_fd >= 0) {
+ /* Only restore tty settings if job was originally
+ * started in the foreground. Problems can be
+ * caused by things like `more foobar &' which will
+ * typically get and save the shell's vi/emacs tty
+ * settings before setting up the tty for itself;
+ * when more exits, it restores the `original'
+ * settings, and things go down hill from there...
+ */
+ if (j->state == PEXITED && j->status == 0 &&
+ (j->flags & JF_USETTYMODE)) {
+ tcgetattr(tty_fd, &tty_state);
+ } else {
+ tcsetattr(tty_fd, TCSADRAIN, &tty_state);
+ /* Don't use tty mode if job is stopped and
+ * later restarted and exits. Consider
+ * the sequence:
+ * vi foo (stopped)
+ * ...
+ * stty something
+ * ...
+ * fg (vi; ZZ)
+ * mode should be that of the stty, not what
+ * was before the vi started.
+ */
+ if (j->state == PSTOPPED)
+ j->flags &= ~JF_USETTYMODE;
+ }
+ }
+#ifdef JOBS
+ /* If it looks like user hit ^C to kill a job, pretend we got
+ * one too to break out of for loops, etc. (at&t ksh does this
+ * even when not monitoring, but this doesn't make sense since
+ * a tty generated ^C goes to the whole process group)
+ */
+ status = j->last_proc->status;
+ if (Flag(FMONITOR) && j->state == PSIGNALLED &&
+ WIFSIGNALED(status) &&
+ (sigtraps[WTERMSIG(status)].flags & TF_TTY_INTR))
+ trapsig(WTERMSIG(status));
+#endif /* JOBS */
+ }
+
+ j_usrtime = j->usrtime;
+ j_systime = j->systime;
+ rv = j->status;
+
+ if (!(flags & JW_ASYNCNOTIFY) &&
+ (!Flag(FMONITOR) || j->state != PSTOPPED)) {
+ j_print(j, JP_SHORT, shl_out);
+ shf_flush(shl_out);
+ }
+ if (j->state != PSTOPPED &&
+ (!Flag(FMONITOR) || !(flags & JW_ASYNCNOTIFY)))
+ remove_job(j, where);
+
+ return rv;
+}
+
+/* SIGCHLD handler to reap children and update job states
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+/* ARGSUSED */
+static void
+j_sigchld(int sig)
+{
+ int errno_ = errno;
+ Job *j;
+ Proc *p = NULL;
+ int pid;
+ int status;
+ struct rusage ru0, ru1;
+
+ /* Don't wait for any processes if a job is partially started.
+ * This is so we don't do away with the process group leader
+ * before all the processes in a pipe line are started (so the
+ * setpgid() won't fail)
+ */
+ for (j = job_list; j; j = j->next)
+ if (j->ppid == procpid && !(j->flags & JF_STARTED)) {
+ held_sigchld = 1;
+ goto finished;
+ }
+
+ getrusage(RUSAGE_CHILDREN, &ru0);
+ do {
+ pid = waitpid(-1, &status, (WNOHANG|WUNTRACED));
+
+ if (pid <= 0) /* return if would block (0) ... */
+ break; /* ... or no children or interrupted (-1) */
+
+ getrusage(RUSAGE_CHILDREN, &ru1);
+
+ /* find job and process structures for this pid */
+ for (j = job_list; j != (Job *) 0; j = j->next)
+ for (p = j->proc_list; p != (Proc *) 0; p = p->next)
+ if (p->pid == pid)
+ goto found;
+found:
+ if (j == (Job *) 0) {
+ /* Can occur if process has kids, then execs shell
+ warningf(true, "bad process waited for (pid = %d)",
+ pid);
+ */
+ ru0 = ru1;
+ continue;
+ }
+
+ timeradd(&j->usrtime, &ru1.ru_utime, &j->usrtime);
+ timersub(&j->usrtime, &ru0.ru_utime, &j->usrtime);
+ timeradd(&j->systime, &ru1.ru_stime, &j->systime);
+ timersub(&j->systime, &ru0.ru_stime, &j->systime);
+ ru0 = ru1;
+ p->status = status;
+#ifdef JOBS
+ if (WIFSTOPPED(status))
+ p->state = PSTOPPED;
+ else
+#endif /* JOBS */
+ if (WIFSIGNALED(status))
+ p->state = PSIGNALLED;
+ else
+ p->state = PEXITED;
+
+ check_job(j); /* check to see if entire job is done */
+ } while (1);
+
+finished:
+ errno = errno_;
+}
+
+/*
+ * Called only when a process in j has exited/stopped (ie, called only
+ * from j_sigchld()). If no processes are running, the job status
+ * and state are updated, asynchronous job notification is done and,
+ * if unneeded, the job is removed.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+check_job(Job *j)
+{
+ int jstate;
+ Proc *p;
+
+ /* XXX debugging (nasty - interrupt routine using shl_out) */
+ if (!(j->flags & JF_STARTED)) {
+ internal_errorf(0, "check_job: job started (flags 0x%x)",
+ j->flags);
+ return;
+ }
+
+ jstate = PRUNNING;
+ for (p=j->proc_list; p != (Proc *) 0; p = p->next) {
+ if (p->state == PRUNNING)
+ return; /* some processes still running */
+ if (p->state > jstate)
+ jstate = p->state;
+ }
+ j->state = jstate;
+
+ switch (j->last_proc->state) {
+ case PEXITED:
+ j->status = WEXITSTATUS(j->last_proc->status);
+ break;
+ case PSIGNALLED:
+ j->status = 128 + WTERMSIG(j->last_proc->status);
+ break;
+ default:
+ j->status = 0;
+ break;
+ }
+
+ /* Note when co-process dies: can't be done in j_wait() nor
+ * remove_job() since neither may be called for non-interactive
+ * shells.
+ */
+ if (j->state == PEXITED || j->state == PSIGNALLED) {
+ /* No need to keep co-process input any more
+ * (at least, this is what ksh93d thinks)
+ */
+ if (coproc.job == j) {
+ coproc.job = (void *) 0;
+ /* XXX would be nice to get the closes out of here
+ * so they aren't done in the signal handler.
+ * Would mean a check in coproc_getfd() to
+ * do "if job == 0 && write >= 0, close write".
+ */
+ coproc_write_close(coproc.write);
+ }
+ /* Do we need to keep the output? */
+ if (j->coproc_id && j->coproc_id == coproc.id &&
+ --coproc.njobs == 0)
+ coproc_readw_close(coproc.read);
+ }
+
+ j->flags |= JF_CHANGED;
+#ifdef JOBS
+ if (Flag(FMONITOR) && !(j->flags & JF_XXCOM)) {
+ /* Only put stopped jobs at the front to avoid confusing
+ * the user (don't want finished jobs effecting %+ or %-)
+ */
+ if (j->state == PSTOPPED)
+ put_job(j, PJ_ON_FRONT);
+ if (Flag(FNOTIFY) &&
+ (j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY)) != JF_WAITING) {
+ /* Look for the real file descriptor 2 */
+ {
+ struct env *ep;
+ int fd = 2;
+
+ for (ep = e; ep; ep = ep->oenv)
+ if (ep->savefd && ep->savefd[2])
+ fd = ep->savefd[2];
+ shf_reopen(fd, SHF_WR, shl_j);
+ }
+ /* Can't call j_notify() as it removes jobs. The job
+ * must stay in the job list as j_waitj() may be
+ * running with this job.
+ */
+ j_print(j, JP_MEDIUM, shl_j);
+ shf_flush(shl_j);
+ if (!(j->flags & JF_WAITING) && j->state != PSTOPPED)
+ remove_job(j, "notify");
+ }
+ }
+#endif /* JOBS */
+ if (!Flag(FMONITOR) && !(j->flags & (JF_WAITING|JF_FG)) &&
+ j->state != PSTOPPED) {
+ if (j == async_job || (j->flags & JF_KNOWN)) {
+ j->flags |= JF_ZOMBIE;
+ j->job = -1;
+ nzombie++;
+ } else
+ remove_job(j, "checkjob");
+ }
+}
+
+/*
+ * Print job status in either short, medium or long format.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+j_print(Job *j, int how, struct shf *shf)
+{
+ Proc *p;
+ int state;
+ int status;
+ int coredumped;
+ char jobchar = ' ';
+ char buf[64];
+ const char *filler;
+ int output = 0;
+
+ if (how == JP_PGRP) {
+ /* POSIX doesn't say what to do it there is no process
+ * group leader (ie, !FMONITOR). We arbitrarily return
+ * last pid (which is what $! returns).
+ */
+ shf_fprintf(shf, "%d\n", j->pgrp ? j->pgrp :
+ (j->last_proc ? j->last_proc->pid : 0));
+ return;
+ }
+ j->flags &= ~JF_CHANGED;
+ filler = j->job > 10 ? "\n " : "\n ";
+ if (j == job_list)
+ jobchar = '+';
+ else if (j == job_list->next)
+ jobchar = '-';
+
+ for (p = j->proc_list; p != (Proc *) 0;) {
+ coredumped = 0;
+ switch (p->state) {
+ case PRUNNING:
+ strlcpy(buf, "Running", sizeof buf);
+ break;
+ case PSTOPPED:
+ strlcpy(buf, sigtraps[WSTOPSIG(p->status)].mess,
+ sizeof buf);
+ break;
+ case PEXITED:
+ if (how == JP_SHORT)
+ buf[0] = '\0';
+ else if (WEXITSTATUS(p->status) == 0)
+ strlcpy(buf, "Done", sizeof buf);
+ else
+ shf_snprintf(buf, sizeof(buf), "Done (%d)",
+ WEXITSTATUS(p->status));
+ break;
+ case PSIGNALLED:
+ if (WCOREDUMP(p->status))
+ coredumped = 1;
+ /* kludge for not reporting `normal termination signals'
+ * (ie, SIGINT, SIGPIPE)
+ */
+ if (how == JP_SHORT && !coredumped &&
+ (WTERMSIG(p->status) == SIGINT ||
+ WTERMSIG(p->status) == SIGPIPE)) {
+ buf[0] = '\0';
+ } else
+ strlcpy(buf, sigtraps[WTERMSIG(p->status)].mess,
+ sizeof buf);
+ break;
+ }
+
+ if (how != JP_SHORT) {
+ if (p == j->proc_list)
+ shf_fprintf(shf, "[%d] %c ", j->job, jobchar);
+ else
+ shf_fprintf(shf, "%s", filler);
+ }
+
+ if (how == JP_LONG)
+ shf_fprintf(shf, "%5d ", p->pid);
+
+ if (how == JP_SHORT) {
+ if (buf[0]) {
+ output = 1;
+ shf_fprintf(shf, "%s%s ",
+ buf, coredumped ? " (core dumped)" : null);
+ }
+ } else {
+ output = 1;
+ shf_fprintf(shf, "%-20s %s%s%s", buf, p->command,
+ p->next ? "|" : null,
+ coredumped ? " (core dumped)" : null);
+ }
+
+ state = p->state;
+ status = p->status;
+ p = p->next;
+ while (p && p->state == state && p->status == status) {
+ if (how == JP_LONG)
+ shf_fprintf(shf, "%s%5d %-20s %s%s", filler, p->pid,
+ space, p->command, p->next ? "|" : null);
+ else if (how == JP_MEDIUM)
+ shf_fprintf(shf, " %s%s", p->command,
+ p->next ? "|" : null);
+ p = p->next;
+ }
+ }
+ if (output)
+ shf_fprintf(shf, newline);
+}
+
+/* Convert % sequence to job
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static Job *
+j_lookup(const char *cp, int *ecodep)
+{
+ Job *j, *last_match;
+ Proc *p;
+ int len, job = 0;
+
+ if (digit(*cp)) {
+ job = atoi(cp);
+ /* Look for last_proc->pid (what $! returns) first... */
+ for (j = job_list; j != (Job *) 0; j = j->next)
+ if (j->last_proc && j->last_proc->pid == job)
+ return j;
+ /* ...then look for process group (this is non-POSIX),
+ * but should not break anything (so FPOSIX isn't used).
+ */
+ for (j = job_list; j != (Job *) 0; j = j->next)
+ if (j->pgrp && j->pgrp == job)
+ return j;
+ if (ecodep)
+ *ecodep = JL_NOSUCH;
+ return (Job *) 0;
+ }
+ if (*cp != '%') {
+ if (ecodep)
+ *ecodep = JL_INVALID;
+ return (Job *) 0;
+ }
+ switch (*++cp) {
+ case '\0': /* non-standard */
+ case '+':
+ case '%':
+ if (job_list != (Job *) 0)
+ return job_list;
+ break;
+
+ case '-':
+ if (job_list != (Job *) 0 && job_list->next)
+ return job_list->next;
+ break;
+
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ job = atoi(cp);
+ for (j = job_list; j != (Job *) 0; j = j->next)
+ if (j->job == job)
+ return j;
+ break;
+
+ case '?': /* %?string */
+ last_match = (Job *) 0;
+ for (j = job_list; j != (Job *) 0; j = j->next)
+ for (p = j->proc_list; p != (Proc *) 0; p = p->next)
+ if (strstr(p->command, cp+1) != (char *) 0) {
+ if (last_match) {
+ if (ecodep)
+ *ecodep = JL_AMBIG;
+ return (Job *) 0;
+ }
+ last_match = j;
+ }
+ if (last_match)
+ return last_match;
+ break;
+
+ default: /* %string */
+ len = strlen(cp);
+ last_match = (Job *) 0;
+ for (j = job_list; j != (Job *) 0; j = j->next)
+ if (strncmp(cp, j->proc_list->command, len) == 0) {
+ if (last_match) {
+ if (ecodep)
+ *ecodep = JL_AMBIG;
+ return (Job *) 0;
+ }
+ last_match = j;
+ }
+ if (last_match)
+ return last_match;
+ break;
+ }
+ if (ecodep)
+ *ecodep = JL_NOSUCH;
+ return (Job *) 0;
+}
+
+static Job *free_jobs;
+static Proc *free_procs;
+
+/* allocate a new job and fill in the job number.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static Job *
+new_job(void)
+{
+ int i;
+ Job *newj, *j;
+
+ if (free_jobs != (Job *) 0) {
+ newj = free_jobs;
+ free_jobs = free_jobs->next;
+ } else
+ newj = (Job *) alloc(sizeof(Job), APERM);
+
+ /* brute force method */
+ for (i = 1; ; i++) {
+ for (j = job_list; j && j->job != i; j = j->next)
+ ;
+ if (j == (Job *) 0)
+ break;
+ }
+ newj->job = i;
+
+ return newj;
+}
+
+/* Allocate new process struct
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static Proc *
+new_proc(void)
+{
+ Proc *p;
+
+ if (free_procs != (Proc *) 0) {
+ p = free_procs;
+ free_procs = free_procs->next;
+ } else
+ p = (Proc *) alloc(sizeof(Proc), APERM);
+
+ return p;
+}
+
+/* Take job out of job_list and put old structures into free list.
+ * Keeps nzombies, last_job and async_job up to date.
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+remove_job(Job *j, const char *where)
+{
+ Proc *p, *tmp;
+ Job **prev, *curr;
+
+ prev = &job_list;
+ curr = *prev;
+ for (; curr != (Job *) 0 && curr != j; prev = &curr->next, curr = *prev)
+ ;
+ if (curr != j) {
+ internal_errorf(0, "remove_job: job not found (%s)", where);
+ return;
+ }
+ *prev = curr->next;
+
+ /* free up proc structures */
+ for (p = j->proc_list; p != (Proc *) 0; ) {
+ tmp = p;
+ p = p->next;
+ tmp->next = free_procs;
+ free_procs = tmp;
+ }
+
+ if ((j->flags & JF_ZOMBIE) && j->ppid == procpid)
+ --nzombie;
+ j->next = free_jobs;
+ free_jobs = j;
+
+ if (j == last_job)
+ last_job = (Job *) 0;
+ if (j == async_job)
+ async_job = (Job *) 0;
+}
+
+/* put j in a particular location (taking it out job_list if it is there
+ * already)
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static void
+put_job(Job *j, int where)
+{
+ Job **prev, *curr;
+
+ /* Remove job from list (if there) */
+ prev = &job_list;
+ curr = job_list;
+ for (; curr && curr != j; prev = &curr->next, curr = *prev)
+ ;
+ if (curr == j)
+ *prev = curr->next;
+
+ switch (where) {
+ case PJ_ON_FRONT:
+ j->next = job_list;
+ job_list = j;
+ break;
+
+ case PJ_PAST_STOPPED:
+ prev = &job_list;
+ curr = job_list;
+ for (; curr && curr->state == PSTOPPED; prev = &curr->next,
+ curr = *prev)
+ ;
+ j->next = curr;
+ *prev = j;
+ break;
+ }
+}
+
+/* nuke a job (called when unable to start full job).
+ *
+ * If jobs are compiled in then this routine expects sigchld to be blocked.
+ */
+static int
+kill_job(Job *j, int sig)
+{
+ Proc *p;
+ int rval = 0;
+
+ for (p = j->proc_list; p != (Proc *) 0; p = p->next)
+ if (p->pid != 0)
+ if (kill(p->pid, sig) < 0)
+ rval = -1;
+ return rval;
+}
diff --git a/ksh.1 b/ksh.1
@@ -0,0 +1,5671 @@
+.\" $OpenBSD: ksh.1,v 1.155 2014/12/09 15:37:13 schwarze Exp $
+.\"
+.\" Public Domain
+.\"
+.Dd $Mdocdate: December 9 2014 $
+.Dt KSH 1
+.Os
+.Sh NAME
+.Nm ksh
+.Nd public domain Korn shell
+.Sh SYNOPSIS
+.Nm ksh
+.Bk -words
+.Op Fl +abCefhiklmnpruvXx
+.Op Fl +o Ar option
+.Op Fl c Ar string \*(Ba Fl s \*(Ba Ar file Op Ar argument ...
+.Ek
+.Sh DESCRIPTION
+.Nm
+is a command interpreter intended for both interactive and shell
+script use.
+Its command language is a superset of the
+.Xr sh 1
+shell language.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl c Ar string
+.Nm
+will execute the command(s) contained in
+.Ar string .
+.It Fl i
+Interactive shell.
+A shell is
+.Dq interactive
+if this
+option is used or if both standard input and standard error are attached
+to a
+.Xr tty 4 .
+An interactive shell has job control enabled, ignores the
+.Dv SIGINT ,
+.Dv SIGQUIT ,
+and
+.Dv SIGTERM
+signals, and prints prompts before reading input (see the
+.Ev PS1
+and
+.Ev PS2
+parameters).
+For non-interactive shells, the
+.Ic trackall
+option is on by default (see the
+.Ic set
+command below).
+.It Fl l
+Login shell.
+If the basename the shell is called with (i.e. argv[0])
+starts with
+.Ql -
+or if this option is used,
+the shell is assumed to be a login shell and the shell reads and executes
+the contents of
+.Pa /etc/profile
+and
+.Pa $HOME/.profile
+if they exist and are readable.
+.It Fl p
+Privileged shell.
+A shell is
+.Dq privileged
+if this option is used
+or if the real user ID or group ID does not match the
+effective user ID or group ID (see
+.Xr getuid 2
+and
+.Xr getgid 2 ) .
+A privileged shell does not process
+.Pa $HOME/.profile
+nor the
+.Ev ENV
+parameter (see below).
+Instead, the file
+.Pa /etc/suid_profile
+is processed.
+Clearing the privileged option causes the shell to set
+its effective user ID (group ID) to its real user ID (group ID).
+.It Fl r
+Restricted shell.
+A shell is
+.Dq restricted
+if this
+option is used;
+if the basename the shell was invoked with was
+.Dq rksh ;
+or if the
+.Ev SHELL
+parameter is set to
+.Dq rksh .
+The following restrictions come into effect after the shell processes any
+profile and
+.Ev ENV
+files:
+.Pp
+.Bl -bullet -compact
+.It
+The
+.Ic cd
+command is disabled.
+.It
+The
+.Ev SHELL ,
+.Ev ENV ,
+and
+.Ev PATH
+parameters cannot be changed.
+.It
+Command names can't be specified with absolute or relative paths.
+.It
+The
+.Fl p
+option of the built-in command
+.Ic command
+can't be used.
+.It
+Redirections that create files can't be used (i.e.\&
+.Ql \*(Gt ,
+.Ql \*(Gt\*(Ba ,
+.Ql \*(Gt\*(Gt ,
+.Ql \*(Lt\*(Gt ) .
+.El
+.It Fl s
+The shell reads commands from standard input; all non-option arguments
+are positional parameters.
+.El
+.Pp
+In addition to the above, the options described in the
+.Ic set
+built-in command can also be used on the command line:
+both
+.Op Fl +abCefhkmnuvXx
+and
+.Op Fl +o Ar option
+can be used for single letter or long options, respectively.
+.Pp
+If neither the
+.Fl c
+nor the
+.Fl s
+option is specified, the first non-option argument specifies the name
+of a file the shell reads commands from.
+If there are no non-option
+arguments, the shell reads commands from the standard input.
+The name of the shell (i.e. the contents of $0)
+is determined as follows: if the
+.Fl c
+option is used and there is a non-option argument, it is used as the name;
+if commands are being read from a file, the file is used as the name;
+otherwise, the basename the shell was called with (i.e. argv[0]) is used.
+.Pp
+If the
+.Ev ENV
+parameter is set when an interactive shell starts (or,
+in the case of login shells,
+after any profiles are processed), its value is subjected to parameter,
+command, arithmetic, and tilde
+.Pq Sq ~
+substitution and the resulting file
+(if any) is read and executed.
+In order to have an interactive (as opposed to login) shell
+process a startup file,
+.Ev ENV
+may be set and exported (see below) in
+.Pa $HOME/.profile
+\- future interactive shell invocations will process any file pointed to by
+.Ev $ENV :
+.Pp
+.Dl export ENV=$HOME/.kshrc
+.Pp
+.Pa $HOME/.kshrc
+is then free to specify instructions for interactive shells.
+For example, the global configuration file may be sourced:
+.Bd -literal -offset indent
+\&. /etc/ksh.kshrc
+.Ed
+.Pp
+The above strategy may be employed to keep
+setup procedures for login shells in
+.Pa $HOME/.profile
+and setup procedures for interactive shells in
+.Pa $HOME/.kshrc .
+Of course, since login shells are also interactive,
+any commands placed in
+.Pa $HOME/.kshrc
+will be executed by login shells too.
+.Pp
+The exit status of the shell is 127 if the command file specified on the
+command line could not be opened, or non-zero if a fatal syntax error
+occurred during the execution of a script.
+In the absence of fatal errors,
+the exit status is that of the last command executed, or zero, if no
+command is executed.
+.Ss Command syntax
+The shell begins parsing its input by breaking it into
+.Em words .
+Words, which are sequences of characters, are delimited by unquoted whitespace
+characters (space, tab, and newline) or meta-characters
+.Po
+.Ql \*(Lt ,
+.Ql \*(Gt ,
+.Ql \*(Ba ,
+.Ql \&; ,
+.Ql \&( ,
+.Ql \&) ,
+and
+.Ql &
+.Pc .
+Aside from delimiting words, spaces and tabs are ignored, while newlines
+usually delimit commands.
+The meta-characters are used in building the following
+.Em tokens :
+.Ql \*(Lt ,
+.Ql \*(Lt& ,
+.Ql \*(Lt\*(Lt ,
+.Ql \*(Gt ,
+.Ql \*(Gt& ,
+.Ql \*(Gt\*(Gt ,
+etc. are used to specify redirections (see
+.Sx Input/output redirection
+below);
+.Ql \*(Ba
+is used to create pipelines;
+.Ql \*(Ba&
+is used to create co-processes (see
+.Sx Co-processes
+below);
+.Ql \&;
+is used to separate commands;
+.Ql &
+is used to create asynchronous pipelines;
+.Ql &&
+and
+.Ql ||
+are used to specify conditional execution;
+.Ql ;;
+is used in
+.Ic case
+statements;
+.Ql (( .. ))
+is used in arithmetic expressions;
+and lastly,
+.Ql \&( .. )\&
+is used to create subshells.
+.Pp
+Whitespace and meta-characters can be quoted individually using a backslash
+.Pq Sq \e ,
+or in groups using double
+.Pq Sq \&"
+or single
+.Pq Sq '
+quotes.
+Note that the following characters are also treated specially by the
+shell and must be quoted if they are to represent themselves:
+.Ql \e ,
+.Ql \&" ,
+.Ql ' ,
+.Ql # ,
+.Ql $ ,
+.Ql ` ,
+.Ql ~ ,
+.Ql { ,
+.Ql } ,
+.Ql * ,
+.Ql \&? ,
+and
+.Ql \&[ .
+The first three of these are the above mentioned quoting characters (see
+.Sx Quoting
+below);
+.Ql # ,
+if used at the beginning of a word, introduces a comment \(em everything after
+the
+.Ql #
+up to the nearest newline is ignored;
+.Ql $
+is used to introduce parameter, command, and arithmetic substitutions (see
+.Sx Substitution
+below);
+.Ql `
+introduces an old-style command substitution (see
+.Sx Substitution
+below);
+.Ql ~
+begins a directory expansion (see
+.Sx Tilde expansion
+below);
+.Ql {
+and
+.Ql }
+delimit
+.Xr csh 1 Ns -style
+alterations (see
+.Sx Brace expansion
+below);
+and finally,
+.Ql * ,
+.Ql \&? ,
+and
+.Ql \&[
+are used in file name generation (see
+.Sx File name patterns
+below).
+.Pp
+As words and tokens are parsed, the shell builds commands, of which there
+are two basic types:
+.Em simple-commands ,
+typically programs that are executed, and
+.Em compound-commands ,
+such as
+.Ic for
+and
+.Ic if
+statements, grouping constructs, and function definitions.
+.Pp
+A simple-command consists of some combination of parameter assignments
+(see
+.Sx Parameters
+below),
+input/output redirections (see
+.Sx Input/output redirections
+below),
+and command words; the only restriction is that parameter assignments come
+before any command words.
+The command words, if any, define the command
+that is to be executed and its arguments.
+The command may be a shell built-in command, a function,
+or an external command
+(i.e. a separate executable file that is located using the
+.Ev PATH
+parameter; see
+.Sx Command execution
+below).
+Note that all command constructs have an exit status: for external commands,
+this is related to the status returned by
+.Xr wait 2
+(if the command could not be found, the exit status is 127; if it could not
+be executed, the exit status is 126); the exit status of other command
+constructs (built-in commands, functions, compound-commands, pipelines, lists,
+etc.) are all well-defined and are described where the construct is
+described.
+The exit status of a command consisting only of parameter
+assignments is that of the last command substitution performed during the
+parameter assignment or 0 if there were no command substitutions.
+.Pp
+Commands can be chained together using the
+.Ql |
+token to form pipelines, in which the standard output of each command but the
+last is piped (see
+.Xr pipe 2 )
+to the standard input of the following command.
+The exit status of a pipeline is that of its last command.
+A pipeline may be prefixed by the
+.Ql \&!
+reserved word, which causes the exit status of the pipeline to be logically
+complemented: if the original status was 0, the complemented status will be 1;
+if the original status was not 0, the complemented status will be 0.
+.Pp
+.Em Lists
+of commands can be created by separating pipelines by any of the following
+tokens:
+.Ql && ,
+.Ql || ,
+.Ql & ,
+.Ql |& ,
+and
+.Ql \&; .
+The first two are for conditional execution:
+.Dq Ar cmd1 No && Ar cmd2
+executes
+.Ar cmd2
+only if the exit status of
+.Ar cmd1
+is zero;
+.Ql ||
+is the opposite \(em
+.Ar cmd2
+is executed only if the exit status of
+.Ar cmd1
+is non-zero.
+.Ql &&
+and
+.Ql ||
+have equal precedence which is higher than that of
+.Ql & ,
+.Ql |& ,
+and
+.Ql \&; ,
+which also have equal precedence.
+Note that the
+.Ql &&
+and
+.Ql ||
+operators are
+.Qq left-associative .
+For example, both of these commands will print only
+.Qq bar :
+.Bd -literal -offset indent
+$ false && echo foo || echo bar
+$ true || echo foo && echo bar
+.Ed
+.Pp
+The
+.Ql &
+token causes the preceding command to be executed asynchronously; that is,
+the shell starts the command but does not wait for it to complete (the shell
+does keep track of the status of asynchronous commands; see
+.Sx Job control
+below).
+When an asynchronous command is started when job control is disabled
+(i.e. in most scripts), the command is started with signals
+.Dv SIGINT
+and
+.Dv SIGQUIT
+ignored and with input redirected from
+.Pa /dev/null
+(however, redirections specified in the asynchronous command have precedence).
+The
+.Ql |&
+operator starts a co-process which is a special kind of asynchronous process
+(see
+.Sx Co-processes
+below).
+Note that a command must follow the
+.Ql &&
+and
+.Ql ||
+operators, while it need not follow
+.Ql & ,
+.Ql |& ,
+or
+.Ql \&; .
+The exit status of a list is that of the last command executed, with the
+exception of asynchronous lists, for which the exit status is 0.
+.Pp
+Compound commands are created using the following reserved words.
+These words
+are only recognized if they are unquoted and if they are used as the first
+word of a command (i.e. they can't be preceded by parameter assignments or
+redirections):
+.Bd -literal -offset indent
+case esac in until (( }
+do fi name while ))
+done for select ! [[
+elif function then ( ]]
+else if time ) {
+.Ed
+.Pp
+.Sy Note :
+Some shells (but not this one) execute control structure commands in a
+subshell when one or more of their file descriptors are redirected, so any
+environment changes inside them may fail.
+To be portable, the
+.Ic exec
+statement should be used instead to redirect file descriptors before the
+control structure.
+.Pp
+In the following compound command descriptions, command lists (denoted as
+.Em list )
+that are followed by reserved words must end with a semicolon, a newline, or
+a (syntactically correct) reserved word.
+For example, the following are all valid:
+.Bd -literal -offset indent
+$ { echo foo; echo bar; }
+$ { echo foo; echo bar\*(Ltnewline\*(Gt }
+$ { { echo foo; echo bar; } }
+.Ed
+.Pp
+This is not valid:
+.Pp
+.Dl $ { echo foo; echo bar }
+.Bl -tag -width Ds
+.It Pq Ar list
+Execute
+.Ar list
+in a subshell.
+There is no implicit way to pass environment changes from a
+subshell back to its parent.
+.It { Ar list ; No }
+Compound construct;
+.Ar list
+is executed, but not in a subshell.
+Note that
+.Ql {
+and
+.Ql }
+are reserved words, not meta-characters.
+.It Xo case Ar word No in
+.Oo Op \&(
+.Ar \ pattern
+.Op \*(Ba Ar pattern
+.No ... No )
+.Ar list No ;;\ \& Oc ... esac
+.Xc
+The
+.Ic case
+statement attempts to match
+.Ar word
+against a specified
+.Ar pattern ;
+the
+.Ar list
+associated with the first successfully matched pattern is executed.
+Patterns used in
+.Ic case
+statements are the same as those used for file name patterns except that the
+restrictions regarding
+.Ql \&.
+and
+.Ql /
+are dropped.
+Note that any unquoted space before and after a pattern is
+stripped; any space within a pattern must be quoted.
+Both the word and the
+patterns are subject to parameter, command, and arithmetic substitution, as
+well as tilde substitution.
+For historical reasons, open and close braces may be used instead of
+.Ic in
+and
+.Ic esac
+e.g.\&
+.Ic case $foo { *) echo bar; } .
+The exit status of a
+.Ic case
+statement is that of the executed
+.Ar list ;
+if no
+.Ar list
+is executed, the exit status is zero.
+.It Xo for Ar name
+.Oo in Ar word No ... Oc ;
+.No do Ar list ; No done
+.Xc
+For each
+.Ar word
+in the specified word list, the parameter
+.Ar name
+is set to the word and
+.Ar list
+is executed.
+If
+.Ic in
+is not used to specify a word list, the positional parameters
+($1, $2, etc.)\&
+are used instead.
+For historical reasons, open and close braces may be used instead of
+.Ic do
+and
+.Ic done
+e.g.\&
+.Ic for i; { echo $i; } .
+The exit status of a
+.Ic for
+statement is the last exit status of
+.Ar list ;
+if
+.Ar list
+is never executed, the exit status is zero.
+.It Xo if Ar list ;
+.No then Ar list ;
+.Oo elif Ar list ;
+.No then Ar list ; Oc
+.No ...
+.Oo else Ar list ; Oc
+.No fi
+.Xc
+If the exit status of the first
+.Ar list
+is zero, the second
+.Ar list
+is executed; otherwise, the
+.Ar list
+following the
+.Ic elif ,
+if any, is executed with similar consequences.
+If all the lists following the
+.Ic if
+and
+.Ic elif Ns s
+fail (i.e. exit with non-zero status), the
+.Ar list
+following the
+.Ic else
+is executed.
+The exit status of an
+.Ic if
+statement is that of non-conditional
+.Ar list
+that is executed; if no non-conditional
+.Ar list
+is executed, the exit status is zero.
+.It Xo select Ar name
+.Oo in Ar word No ... Oc ;
+.No do Ar list ; No done
+.Xc
+The
+.Ic select
+statement provides an automatic method of presenting the user with a menu and
+selecting from it.
+An enumerated list of the specified
+.Ar word Ns (s)
+is printed on standard error, followed by a prompt
+.Po
+.Ev PS3: normally
+.Sq #?\ \&
+.Pc .
+A number corresponding to one of the enumerated words is then read from
+standard input,
+.Ar name
+is set to the selected word (or unset if the selection is not valid),
+.Ev REPLY
+is set to what was read (leading/trailing space is stripped), and
+.Ar list
+is executed.
+If a blank line (i.e. zero or more
+.Ev IFS
+characters) is entered, the menu is reprinted without executing
+.Ar list .
+.Pp
+When
+.Ar list
+completes, the enumerated list is printed if
+.Ev REPLY
+is
+.Dv NULL ,
+the prompt is printed, and so on.
+This process continues until an end-of-file
+is read, an interrupt is received, or a
+.Ic break
+statement is executed inside the loop.
+If
+.Dq in word ...
+is omitted, the positional parameters are used
+(i.e. $1, $2, etc.).
+For historical reasons, open and close braces may be used instead of
+.Ic do
+and
+.Ic done
+e.g.\&
+.Ic select i; { echo $i; } .
+The exit status of a
+.Ic select
+statement is zero if a
+.Ic break
+statement is used to exit the loop, non-zero otherwise.
+.It Xo until Ar list ;
+.No do Ar list ;
+.No done
+.Xc
+This works like
+.Ic while ,
+except that the body is executed only while the exit status of the first
+.Ar list
+is non-zero.
+.It Xo while Ar list ;
+.No do Ar list ;
+.No done
+.Xc
+A
+.Ic while
+is a pre-checked loop.
+Its body is executed as often as the exit status of the first
+.Ar list
+is zero.
+The exit status of a
+.Ic while
+statement is the last exit status of the
+.Ar list
+in the body of the loop; if the body is not executed, the exit status is zero.
+.It Xo function Ar name
+.No { Ar list ; No }
+.Xc
+Defines the function
+.Ar name
+(see
+.Sx Functions
+below).
+Note that redirections specified after a function definition are
+performed whenever the function is executed, not when the function definition
+is executed.
+.It Ar name Ns () Ar command
+Mostly the same as
+.Ic function
+(see
+.Sx Functions
+below).
+.It Xo Ic time Op Fl p
+.Op Ar pipeline
+.Xc
+The
+.Ic time
+reserved word is described in the
+.Sx Command execution
+section.
+.It (( Ar expression No ))
+The arithmetic expression
+.Ar expression
+is evaluated; equivalent to
+.Dq let expression
+(see
+.Sx Arithmetic expressions
+and the
+.Ic let
+command, below).
+.It Bq Bq Ar \ \&expression\ \&
+Similar to the
+.Ic test
+and
+.Ic \&[ ... \&]
+commands (described later), with the following exceptions:
+.Bl -bullet -offset indent
+.It
+Field splitting and file name generation are not performed on arguments.
+.It
+The
+.Fl a
+.Pq AND
+and
+.Fl o
+.Pq OR
+operators are replaced with
+.Ql &&
+and
+.Ql || ,
+respectively.
+.It
+Operators (e.g.\&
+.Sq Fl f ,
+.Sq = ,
+.Sq \&! )
+must be unquoted.
+.It
+The second operand of the
+.Sq !=
+and
+.Sq =
+expressions are patterns (e.g. the comparison
+.Ic [[ foobar = f*r ]]
+succeeds).
+.It
+There are two additional binary operators,
+.Ql \*(Lt
+and
+.Ql \*(Gt ,
+which return true if their first string operand is less than, or greater than,
+their second string operand, respectively.
+.It
+The single argument form of
+.Ic test ,
+which tests if the argument has a non-zero length, is not valid; explicit
+operators must always be used e.g. instead of
+.No \&[ Ar str No \&]
+use
+.No \&[[ -n Ar str No \&]] .
+.It
+Parameter, command, and arithmetic substitutions are performed as expressions
+are evaluated and lazy expression evaluation is used for the
+.Ql &&
+and
+.Ql ||
+operators.
+This means that in the following statement,
+.Ic $(\*(Lt foo)
+is evaluated if and only if the file
+.Pa foo
+exists and is readable:
+.Bd -literal -offset indent
+$ [[ -r foo && $(\*(Lt foo) = b*r ]]
+.Ed
+.El
+.El
+.Ss Quoting
+Quoting is used to prevent the shell from treating characters or words
+specially.
+There are three methods of quoting.
+First,
+.Ql \e
+quotes the following character, unless it is at the end of a line, in which
+case both the
+.Ql \e
+and the newline are stripped.
+Second, a single quote
+.Pq Sq '
+quotes everything up to the next single quote (this may span lines).
+Third, a double quote
+.Pq Sq \&"
+quotes all characters, except
+.Ql $ ,
+.Ql `
+and
+.Ql \e ,
+up to the next unquoted double quote.
+.Ql $
+and
+.Ql `
+inside double quotes have their usual meaning (i.e. parameter, command, or
+arithmetic substitution) except no field splitting is carried out on the
+results of double-quoted substitutions.
+If a
+.Ql \e
+inside a double-quoted string is followed by
+.Ql \e ,
+.Ql $ ,
+.Ql ` ,
+or
+.Ql \&" ,
+it is replaced by the second character; if it is followed by a newline, both
+the
+.Ql \e
+and the newline are stripped; otherwise, both the
+.Ql \e
+and the character following are unchanged.
+.Pp
+.Sy Note :
+See
+.Sx POSIX mode
+below for a special rule regarding
+differences in quoting when the shell is in POSIX mode.
+.Ss Aliases
+There are two types of aliases: normal command aliases and tracked aliases.
+Command aliases are normally used as a short hand for a long or often used
+command.
+The shell expands command aliases (i.e. substitutes the alias name
+for its value) when it reads the first word of a command.
+An expanded alias is re-processed to check for more aliases.
+If a command alias ends in a
+space or tab, the following word is also checked for alias expansion.
+The alias expansion process stops when a word that is not an alias is found,
+when a quoted word is found, or when an alias word that is currently being
+expanded is found.
+.Pp
+The following command aliases are defined automatically by the shell:
+.Bd -literal -offset indent
+autoload='typeset -fu'
+functions='typeset -f'
+hash='alias -t'
+history='fc -l'
+integer='typeset -i'
+local='typeset'
+login='exec login'
+nohup='nohup '
+r='fc -e -'
+stop='kill -STOP'
+type='whence -v'
+.Ed
+.Pp
+Tracked aliases allow the shell to remember where it found a particular
+command.
+The first time the shell does a path search for a command that is
+marked as a tracked alias, it saves the full path of the command.
+The next
+time the command is executed, the shell checks the saved path to see that it
+is still valid, and if so, avoids repeating the path search.
+Tracked aliases can be listed and created using
+.Ic alias -t .
+Note that changing the
+.Ev PATH
+parameter clears the saved paths for all tracked aliases.
+If the
+.Ic trackall
+option is set (i.e.\&
+.Ic set -o Ic trackall
+or
+.Ic set -h ) ,
+the shell tracks all commands.
+This option is set automatically for non-interactive shells.
+For interactive shells, only the following commands are
+automatically tracked:
+.Xr cat 1 ,
+.Xr cc 1 ,
+.Xr chmod 1 ,
+.Xr cp 1 ,
+.Xr date 1 ,
+.Xr ed 1 ,
+.Xr emacs 1 ,
+.Xr grep 1 ,
+.Xr ls 1 ,
+.Xr mail 1 ,
+.Xr make 1 ,
+.Xr mv 1 ,
+.Xr pr 1 ,
+.Xr rm 1 ,
+.Xr sed 1 ,
+.Xr sh 1 ,
+.Xr vi 1 ,
+and
+.Xr who 1 .
+.Ss Substitution
+The first step the shell takes in executing a simple-command is to perform
+substitutions on the words of the command.
+There are three kinds of
+substitution: parameter, command, and arithmetic.
+Parameter substitutions,
+which are described in detail in the next section, take the form
+.Pf $ Ns Ar name
+or
+.Pf ${ Ns Ar ... Ns } ;
+command substitutions take the form
+.Pf $( Ns Ar command Ns \&)
+or
+.Pf ` Ns Ar command Ns ` ;
+and arithmetic substitutions take the form
+.Pf $(( Ns Ar expression Ns )) .
+.Pp
+If a substitution appears outside of double quotes, the results of the
+substitution are generally subject to word or field splitting according to
+the current value of the
+.Ev IFS
+parameter.
+The
+.Ev IFS
+parameter specifies a list of characters which are used to break a string up
+into several words; any characters from the set space, tab, and newline that
+appear in the
+.Ev IFS
+characters are called
+.Dq IFS whitespace .
+Sequences of one or more
+.Ev IFS
+whitespace characters, in combination with zero or one
+.Pf non- Ev IFS
+whitespace
+characters, delimit a field.
+As a special case, leading and trailing
+.Ev IFS
+whitespace is stripped (i.e. no leading or trailing empty field is created by
+it); leading
+.Pf non- Ev IFS
+whitespace does create an empty field.
+.Pp
+Example: If
+.Ev IFS
+is set to
+.Dq \*(Ltspace\*(Gt: ,
+and VAR is set to
+.Dq \*(Ltspace\*(GtA\*(Ltspace\*(Gt:\*(Ltspace\*(Gt\*(Ltspace\*(GtB::D ,
+the substitution for $VAR results in four fields:
+.Sq A ,
+.Sq B ,
+.Sq
+(an empty field),
+and
+.Sq D .
+Note that if the
+.Ev IFS
+parameter is set to the
+.Dv NULL
+string, no field splitting is done; if the parameter is unset, the default
+value of space, tab, and newline is used.
+.Pp
+Also, note that the field splitting applies only to the immediate result of
+the substitution.
+Using the previous example, the substitution for $VAR:E
+results in the fields:
+.Sq A ,
+.Sq B ,
+.Sq ,
+and
+.Sq D:E ,
+not
+.Sq A ,
+.Sq B ,
+.Sq ,
+.Sq D ,
+and
+.Sq E .
+This behavior is POSIX compliant, but incompatible with some other shell
+implementations which do field splitting on the word which contained the
+substitution or use
+.Dv IFS
+as a general whitespace delimiter.
+.Pp
+The results of substitution are, unless otherwise specified, also subject to
+brace expansion and file name expansion (see the relevant sections below).
+.Pp
+A command substitution is replaced by the output generated by the specified
+command, which is run in a subshell.
+For
+.Pf $( Ns Ar command Ns \&)
+substitutions, normal quoting rules are used when
+.Ar command
+is parsed; however, for the
+.Pf ` Ns Ar command Ns `
+form, a
+.Ql \e
+followed by any of
+.Ql $ ,
+.Ql ` ,
+or
+.Ql \e
+is stripped (a
+.Ql \e
+followed by any other character is unchanged).
+As a special case in command substitutions, a command of the form
+.Pf \*(Lt Ar file
+is interpreted to mean substitute the contents of
+.Ar file .
+Note that
+.Ic $(\*(Lt foo)
+has the same effect as
+.Ic $(cat foo) ,
+but it is carried out more efficiently because no process is started.
+.Pp
+.Sy Note :
+.Pf $( Ns Ar command Ns \&)
+expressions are currently parsed by finding the matching parenthesis,
+regardless of quoting.
+This should be fixed soon.
+.Pp
+Arithmetic substitutions are replaced by the value of the specified expression.
+For example, the command
+.Ic echo $((2+3*4))
+prints 14.
+See
+.Sx Arithmetic expressions
+for a description of an expression.
+.Ss Parameters
+Parameters are shell variables; they can be assigned values and their values
+can be accessed using a parameter substitution.
+A parameter name is either one
+of the special single punctuation or digit character parameters described
+below, or a letter followed by zero or more letters or digits
+.Po
+.Ql _
+counts as a letter
+.Pc .
+The latter form can be treated as arrays by appending an array index of the
+form
+.Op Ar expr
+where
+.Ar expr
+is an arithmetic expression.
+Parameter substitutions take the form
+.Pf $ Ns Ar name ,
+.Pf ${ Ns Ar name Ns } ,
+or
+.Sm off
+.Pf ${ Ar name Oo Ar expr Oc }
+.Sm on
+where
+.Ar name
+is a parameter name.
+If
+.Ar expr
+is a literal
+.Ql @
+then the named array is expanded using the same quoting rules as
+.Ql $@ ,
+while if
+.Ar expr
+is a literal
+.Ql *
+then the named array is expanded using the same quoting rules as
+.Ql $* .
+If substitution is performed on a parameter
+(or an array parameter element)
+that is not set, a null string is substituted unless the
+.Ic nounset
+option
+.Po
+.Ic set Fl o Ic nounset
+or
+.Ic set Fl u
+.Pc
+is set, in which case an error occurs.
+.Pp
+Parameters can be assigned values in a number of ways.
+First, the shell implicitly sets some parameters like
+.Ql # ,
+.Ql PWD ,
+and
+.Ql $ ;
+this is the only way the special single character parameters are set.
+Second, parameters are imported from the shell's environment at startup.
+Third, parameters can be assigned values on the command line: for example,
+.Ic FOO=bar
+sets the parameter
+.Dq FOO
+to
+.Dq bar ;
+multiple parameter assignments can be given on a single command line and they
+can be followed by a simple-command, in which case the assignments are in
+effect only for the duration of the command (such assignments are also
+exported; see below for the implications of this).
+Note that both the parameter name and the
+.Ql =
+must be unquoted for the shell to recognize a parameter assignment.
+The fourth way of setting a parameter is with the
+.Ic export ,
+.Ic readonly ,
+and
+.Ic typeset
+commands; see their descriptions in the
+.Sx Command execution
+section.
+Fifth,
+.Ic for
+and
+.Ic select
+loops set parameters as well as the
+.Ic getopts ,
+.Ic read ,
+and
+.Ic set -A
+commands.
+Lastly, parameters can be assigned values using assignment operators
+inside arithmetic expressions (see
+.Sx Arithmetic expressions
+below) or using the
+.Sm off
+.Pf ${ Ar name No = Ar value No }
+.Sm on
+form of the parameter substitution (see below).
+.Pp
+Parameters with the export attribute (set using the
+.Ic export
+or
+.Ic typeset Fl x
+commands, or by parameter assignments followed by simple commands) are put in
+the environment (see
+.Xr environ 7 )
+of commands run by the shell as
+.Ar name Ns = Ns Ar value
+pairs.
+The order in which parameters appear in the environment of a command is
+unspecified.
+When the shell starts up, it extracts parameters and their values
+from its environment and automatically sets the export attribute for those
+parameters.
+.Pp
+Modifiers can be applied to the
+.Pf ${ Ns Ar name Ns }
+form of parameter substitution:
+.Bl -tag -width Ds
+.Sm off
+.It ${ Ar name No :- Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+it is substituted; otherwise,
+.Ar word
+is substituted.
+.Sm off
+.It ${ Ar name No :+ Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+.Ar word
+is substituted; otherwise, nothing is substituted.
+.Sm off
+.It ${ Ar name No := Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+it is substituted; otherwise, it is assigned
+.Ar word
+and the resulting value of
+.Ar name
+is substituted.
+.Sm off
+.It ${ Ar name No :? Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+it is substituted; otherwise,
+.Ar word
+is printed on standard error (preceded by
+.Ar name : )
+and an error occurs (normally causing termination of a shell script, function,
+or script sourced using the
+.Sq \&.
+built-in).
+If
+.Ar word
+is omitted, the string
+.Dq parameter null or not set
+is used instead.
+.El
+.Pp
+In the above modifiers, the
+.Ql \&:
+can be omitted, in which case the conditions only depend on
+.Ar name
+being set (as opposed to set and not
+.Dv NULL ) .
+If
+.Ar word
+is needed, parameter, command, arithmetic, and tilde substitution are performed
+on it; if
+.Ar word
+is not needed, it is not evaluated.
+.Pp
+The following forms of parameter substitution can also be used:
+.Pp
+.Bl -tag -width Ds -compact
+.It Pf ${# Ns Ar name Ns \&}
+The number of positional parameters if
+.Ar name
+is
+.Ql * ,
+.Ql @ ,
+or not specified; otherwise the length of the string value of parameter
+.Ar name .
+.Pp
+.It Pf ${# Ns Ar name Ns [*]}
+.It Pf ${# Ns Ar name Ns [@]}
+The number of elements in the array
+.Ar name .
+.Pp
+.Sm off
+.It Xo
+.Pf ${ Ar name
+.Pf # Ar pattern No }
+.Xc
+.It Xo
+.Pf ${ Ar name
+.Pf ## Ar pattern No }
+.Xc
+.Sm on
+If
+.Ar pattern
+matches the beginning of the value of parameter
+.Ar name ,
+the matched text is deleted from the result of substitution.
+A single
+.Ql #
+results in the shortest match, and two
+of them result in the longest match.
+.Pp
+.Sm off
+.It Xo
+.Pf ${ Ar name
+.Pf % Ar pattern No }
+.Xc
+.It Xo
+.Pf ${ Ar name
+.Pf %% Ar pattern No }
+.Xc
+.Sm on
+Like ${..#..} substitution, but it deletes from the end of the value.
+.El
+.Pp
+The following special parameters are implicitly set by the shell and cannot be
+set directly using assignments:
+.Bl -tag -width "1 ... 9"
+.It Ev \&!
+Process ID of the last background process started.
+If no background processes have been started, the parameter is not set.
+.It Ev \&#
+The number of positional parameters ($1, $2, etc.).
+.It Ev \&$
+The PID of the shell, or the PID of the original shell if it is a subshell.
+Do
+.Em NOT
+use this mechanism for generating temporary file names; see
+.Xr mktemp 1
+instead.
+.It Ev -
+The concatenation of the current single letter options (see the
+.Ic set
+command below for a list of options).
+.It Ev \&?
+The exit status of the last non-asynchronous command executed.
+If the last command was killed by a signal,
+.Ic $?\&
+is set to 128 plus the signal number.
+.It Ev 0
+The name of the shell, determined as follows:
+the first argument to
+.Nm
+if it was invoked with the
+.Fl c
+option and arguments were given; otherwise the
+.Ar file
+argument, if it was supplied;
+or else the basename the shell was invoked with (i.e.\&
+.Li argv[0] ) .
+.Ev $0
+is also set to the name of the current script or
+the name of the current function, if it was defined with the
+.Ic function
+keyword (i.e. a Korn shell style function).
+.It Ev 1 No ... Ev 9
+The first nine positional parameters that were supplied to the shell, function,
+or script sourced using the
+.Sq \&.
+built-in.
+Further positional parameters may be accessed using
+.Pf ${ Ar number Ns } .
+.It Ev *
+All positional parameters (except parameter 0) i.e. $1, $2, $3, ...
+If used
+outside of double quotes, parameters are separate words (which are subjected
+to word splitting); if used within double quotes, parameters are separated
+by the first character of the
+.Ev IFS
+parameter (or the empty string if
+.Ev IFS
+is
+.Dv NULL ) .
+.It Ev @
+Same as
+.Ic $* ,
+unless it is used inside double quotes, in which case a separate word is
+generated for each positional parameter.
+If there are no positional parameters, no word is generated.
+.Ic $@
+can be used to access arguments, verbatim, without losing
+.Dv NULL
+arguments or splitting arguments with spaces.
+.El
+.Pp
+The following parameters are set and/or used by the shell:
+.Bl -tag -width "EXECSHELL"
+.It Ev _ No (underscore)
+When an external command is executed by the shell, this parameter is set in the
+environment of the new process to the path of the executed command.
+In interactive use, this parameter is also set in the parent shell to the last
+word of the previous command.
+When
+.Ev MAILPATH
+messages are evaluated, this parameter contains the name of the file that
+changed (see the
+.Ev MAILPATH
+parameter, below).
+.It Ev CDPATH
+Search path for the
+.Ic cd
+built-in command.
+It works the same way as
+.Ev PATH
+for those directories not beginning with
+.Ql /
+in
+.Ic cd
+commands.
+Note that if
+.Ev CDPATH
+is set and does not contain
+.Sq \&.
+or contains an empty path, the current directory is not searched.
+Also, the
+.Ic cd
+built-in command will display the resulting directory when a match is found
+in any search path other than the empty path.
+.It Ev COLUMNS
+Set to the number of columns on the terminal or window.
+Currently set to the
+.Dq cols
+value as reported by
+.Xr stty 1
+if that value is non-zero.
+This parameter is used by the interactive line editing modes, and by the
+.Ic select ,
+.Ic set -o ,
+and
+.Ic kill -l
+commands to format information columns.
+.It Ev EDITOR
+If the
+.Ev VISUAL
+parameter is not set, this parameter controls the command-line editing mode for
+interactive shells.
+See the
+.Ev VISUAL
+parameter below for how this works.
+.Pp
+Note:
+traditionally,
+.Ev EDITOR
+was used to specify the name of an (old-style) line editor, such as
+.Xr ed 1 ,
+and
+.Ev VISUAL
+was used to specify a (new-style) screen editor, such as
+.Xr vi 1 .
+Hence if
+.Ev VISUAL
+is set, it overrides
+.Ev EDITOR .
+.It Ev ENV
+If this parameter is found to be set after any profile files are executed, the
+expanded value is used as a shell startup file.
+It typically contains function and alias definitions.
+.It Ev ERRNO
+Integer value of the shell's
+.Va errno
+variable.
+It indicates the reason the last system call failed.
+Not yet implemented.
+.It Ev EXECSHELL
+If set, this parameter is assumed to contain the shell that is to be used to
+execute commands that
+.Xr execve 2
+fails to execute and which do not start with a
+.Dq #! Ns Ar shell
+sequence.
+.It Ev FCEDIT
+The editor used by the
+.Ic fc
+command (see below).
+.It Ev FPATH
+Like
+.Ev PATH ,
+but used when an undefined function is executed to locate the file defining the
+function.
+It is also searched when a command can't be found using
+.Ev PATH .
+See
+.Sx Functions
+below for more information.
+.It Ev HISTFILE
+The name of the file used to store command history.
+When assigned to, history is loaded from the specified file.
+Also, several invocations of the shell
+running on the same machine will share history if their
+.Ev HISTFILE
+parameters all point to the same file.
+.Pp
+.Sy Note :
+If
+.Ev HISTFILE
+isn't set, no history file is used.
+This is different from the original Korn shell, which uses
+.Pa $HOME/.sh_history ;
+in the future,
+.Nm pdksh
+may also use a default history file.
+.It Ev HISTSIZE
+The number of commands normally stored for history.
+The default is 500.
+.It Ev HOME
+The default directory for the
+.Ic cd
+command and the value substituted for an unqualified
+.Ic ~
+(see
+.Sx Tilde expansion
+below).
+.It Ev IFS
+Internal field separator, used during substitution and by the
+.Ic read
+command, to split values into distinct arguments; normally set to space, tab,
+and newline.
+See
+.Sx Substitution
+above for details.
+.Pp
+.Sy Note :
+This parameter is not imported from the environment when the shell is
+started.
+.It Ev KSH_VERSION
+The version of the shell and the date the version was created (read-only).
+.It Ev LINENO
+The line number of the function or shell script that is currently being
+executed.
+.It Ev LINES
+Set to the number of lines on the terminal or window.
+.It Ev MAIL
+If set, the user will be informed of the arrival of mail in the named file.
+This parameter is ignored if the
+.Ev MAILPATH
+parameter is set.
+.It Ev MAILCHECK
+How often, in seconds, the shell will check for mail in the file(s) specified
+by
+.Ev MAIL
+or
+.Ev MAILPATH .
+If set to 0, the shell checks before each prompt.
+The default is 600 (10 minutes).
+.It Ev MAILPATH
+A list of files to be checked for mail.
+The list is colon separated, and each file may be followed by a
+.Ql \&?
+and a message to be printed if new mail has arrived.
+Command, parameter, and
+arithmetic substitution is performed on the message, and, during substitution,
+the parameter
+.Ic $_
+contains the name of the file.
+The default message is
+.Dq you have mail in $_ .
+.It Ev OLDPWD
+The previous working directory.
+Unset if
+.Ic cd
+has not successfully changed directories since the shell started, or if the
+shell doesn't know where it is.
+.It Ev OPTARG
+When using
+.Ic getopts ,
+it contains the argument for a parsed option, if it requires one.
+.It Ev OPTIND
+The index of the next argument to be processed when using
+.Ic getopts .
+Assigning 1 to this parameter causes
+.Ic getopts
+to process arguments from the beginning the next time it is invoked.
+.It Ev PATH
+A colon separated list of directories that are searched when looking for
+commands and files sourced using the
+.Sq \&.
+command (see below).
+An empty string resulting from a leading or trailing
+colon, or two adjacent colons, is treated as a
+.Sq \&.
+(the current directory).
+.It Ev POSIXLY_CORRECT
+If set, this parameter causes the
+.Ic posix
+option to be enabled.
+See
+.Sx POSIX mode
+below.
+.It Ev PPID
+The process ID of the shell's parent (read-only).
+.It Ev PS1
+The primary prompt for interactive shells.
+Parameter, command, and arithmetic
+substitutions are performed,
+and the prompt string can be customised using
+backslash-escaped special characters.
+.Pp
+Note that since the command-line editors try to figure out how long the prompt
+is (so they know how far it is to the edge of the screen), escape codes in
+the prompt tend to mess things up.
+You can tell the shell not to count certain
+sequences (such as escape codes) by using the
+.Li \e[ Ns Ar ... Ns Li \e]
+substitution (see below) or by prefixing your prompt with a non-printing
+character (such as control-A) followed by a carriage return and then delimiting
+the escape codes with this non-printing character.
+By the way, don't blame me for
+this hack; it's in the original
+.Nm .
+.Pp
+The default prompt is
+.Sq $\ \&
+for non-root users,
+.Sq #\ \&
+for root.
+If
+.Nm
+is invoked by root and
+.Ev PS1
+does not contain a
+.Sq #
+character, the default value will be used even if
+.Ev PS1
+already exists in the environment.
+.Pp
+The following backslash-escaped special characters can be used
+to customise the prompt:
+.Pp
+.Bl -tag -width "\eD{format}XX" -compact
+.It Li \ea
+Insert an ASCII bell character.
+.It Li \ed
+The current date, in the format
+.Dq Day Month Date
+for example
+.Dq Wed Nov 03 .
+.It Li \eD{ Ns Ar format Ns Li }
+The current date, with
+.Ar format
+converted by
+.Xr strftime 3 .
+The braces must be specified.
+.It Li \ee
+Insert an ASCII escape character.
+.It Li \eh
+The hostname, minus domain name.
+.It Li \eH
+The full hostname, including domain name.
+.It Li \ej
+Current number of jobs running
+(see
+.Sx Job control
+below).
+.It Li \el
+The controlling terminal.
+.It Li \en
+Insert a newline character.
+.It Li \er
+Insert a carriage return character.
+.It Li \es
+The name of the shell.
+.It Li \et
+The current time, in 24-hour HH:MM:SS format.
+.It Li \eT
+The current time, in 12-hour HH:MM:SS format.
+.It Li \e@
+The current time, in 12-hour HH:MM:SS AM/PM format.
+.It Li \eA
+The current time, in 24-hour HH:MM format.
+.It Li \eu
+The current user's username.
+.It Li \ev
+The current version of
+.Nm .
+.It Li \eV
+Like
+.Sq \ev ,
+but more verbose.
+.It Li \ew
+The current working directory.
+.Dv $HOME
+is abbreviated as
+.Sq ~ .
+.It Li \eW
+The basename of
+the current working directory.
+.Dv $HOME
+is abbreviated as
+.Sq ~ .
+.It Li \e!
+The current history number.
+An unescaped
+.Ql !\&
+will produce the current history number too,
+as per the POSIX specification.
+A literal
+.Ql \&!
+can be put in the prompt by placing
+.Ql !!
+in
+.Ev PS1 .
+.It Li \e#
+The current command number.
+This could be different to the current history number,
+if
+.Ev HISTFILE
+contains a history list from a previous session.
+.It Li \e$
+The default prompt i.e.\&
+.Sq # \&
+if the effective UID is 0,
+otherwise
+.Sq $ \& .
+Since the shell interprets
+.Sq $
+as a special character within double quotes,
+it is safer in this case to escape the backslash
+than to try quoting it.
+.It Li \e Ns Ar nnn
+The octal character
+.Ar nnn .
+.It Li \e\e
+Insert a single backslash character.
+.It Li \e[
+Normally the shell keeps track of the number of characters in the prompt.
+Use of this sequence turns off that count.
+.It Li \e]
+Use of this sequence turns the count back on.
+.El
+.Pp
+Note that the backslash itself may be interpreted by the shell.
+Hence, to set
+.Ev PS1
+either escape the backslash itself,
+or use double quotes.
+The latter is more practical:
+.Bd -literal -offset indent
+PS1="\eu "
+.Ed
+.Pp
+This is a more complex example,
+which does not rely on the above backslash-escaped sequences.
+It embeds the current working directory,
+in reverse video,
+in the prompt string:
+.Bd -literal -offset indent
+x=$(print \e\e001)
+PS1="$x$(print \e\er)$x$(tput so)$x\e$PWD$x$(tput se)$x\*(Gt "
+.Ed
+.It Ev PS2
+Secondary prompt string, by default
+.Sq \*(Gt\ \& ,
+used when more input is needed to complete a command.
+.It Ev PS3
+Prompt used by the
+.Ic select
+statement when reading a menu selection.
+The default is
+.Sq #?\ \& .
+.It Ev PS4
+Used to prefix commands that are printed during execution tracing (see the
+.Ic set Fl x
+command below).
+Parameter, command, and arithmetic substitutions are performed
+before it is printed.
+The default is
+.Sq +\ \& .
+.It Ev PWD
+The current working directory.
+May be unset or
+.Dv NULL
+if the shell doesn't know where it is.
+.It Ev RANDOM
+A random number generator.
+Every time
+.Ev RANDOM
+is referenced, it is assigned the next random number in the range
+0\-32767.
+By default,
+.Xr arc4random 3
+is used to produce values.
+If the variable
+.Ev RANDOM
+is assigned a value, the value is used as the seed to
+.Xr srand 3
+and subsequent references of
+.Ev RANDOM
+will use
+.Xr rand 3
+to produce values, resulting in a predictable sequence.
+.It Ev REPLY
+Default parameter for the
+.Ic read
+command if no names are given.
+Also used in
+.Ic select
+loops to store the value that is read from standard input.
+.It Ev SECONDS
+The number of seconds since the shell started or, if the parameter has been
+assigned an integer value, the number of seconds since the assignment plus the
+value that was assigned.
+.It Ev TMOUT
+If set to a positive integer in an interactive shell, it specifies the maximum
+number of seconds the shell will wait for input after printing the primary
+prompt
+.Pq Ev PS1 .
+If the time is exceeded, the shell exits.
+.It Ev TMPDIR
+The directory temporary shell files are created in.
+If this parameter is not
+set, or does not contain the absolute path of a writable directory, temporary
+files are created in
+.Pa /tmp .
+.It Ev VISUAL
+If set, this parameter controls the command-line editing mode for interactive
+shells.
+If the last component of the path specified in this parameter contains
+the string
+.Dq vi ,
+.Dq emacs ,
+or
+.Dq gmacs ,
+the
+.Xr vi 1 ,
+emacs, or gmacs (Gosling emacs) editing mode is enabled, respectively.
+See also the
+.Ev EDITOR
+parameter, above.
+.El
+.Ss Tilde expansion
+Tilde expansion, which is done in parallel with parameter substitution, is done
+on words starting with an unquoted
+.Ql ~ .
+The characters following the tilde, up to the first
+.Ql / ,
+if any, are assumed to be a login name.
+If the login name is empty,
+.Ql + ,
+or
+.Ql - ,
+the value of the
+.Ev HOME ,
+.Ev PWD ,
+or
+.Ev OLDPWD
+parameter is substituted, respectively.
+Otherwise, the password file is
+searched for the login name, and the tilde expression is substituted with the
+user's home directory.
+If the login name is not found in the password file or
+if any quoting or parameter substitution occurs in the login name, no
+substitution is performed.
+.Pp
+In parameter assignments
+(such as those preceding a simple-command or those occurring
+in the arguments of
+.Ic alias ,
+.Ic export ,
+.Ic readonly ,
+and
+.Ic typeset ) ,
+tilde expansion is done after any assignment
+(i.e. after the equals sign)
+or after an unquoted colon
+.Pq Sq \&: ;
+login names are also delimited by colons.
+.Pp
+The home directory of previously expanded login names are cached and re-used.
+The
+.Ic alias -d
+command may be used to list, change, and add to this cache (e.g.\&
+.Ic alias -d fac=/usr/local/facilities; cd ~fac/bin ) .
+.Ss Brace expansion (alteration)
+Brace expressions take the following form:
+.Bd -unfilled -offset indent
+.Sm off
+.Xo
+.Ar prefix No { Ar str1 No ,...,
+.Ar strN No } Ar suffix
+.Xc
+.Sm on
+.Ed
+.Pp
+The expressions are expanded to
+.Ar N
+words, each of which is the concatenation of
+.Ar prefix ,
+.Ar str Ns i ,
+and
+.Ar suffix
+(e.g.\&
+.Dq a{c,b{X,Y},d}e
+expands to four words:
+.Dq ace ,
+.Dq abXe ,
+.Dq abYe ,
+and
+.Dq ade ) .
+As noted in the example, brace expressions can be nested and the resulting
+words are not sorted.
+Brace expressions must contain an unquoted comma
+.Pq Sq \&,
+for expansion to occur (e.g.\&
+.Ic {}
+and
+.Ic {foo}
+are not expanded).
+Brace expansion is carried out after parameter substitution
+and before file name generation.
+.Ss File name patterns
+A file name pattern is a word containing one or more unquoted
+.Ql \&? ,
+.Ql * ,
+.Ql + ,
+.Ql @ ,
+or
+.Ql \&!
+characters or
+.Dq [..]
+sequences.
+Once brace expansion has been performed, the shell replaces file
+name patterns with the sorted names of all the files that match the pattern
+(if no files match, the word is left unchanged).
+The pattern elements have the following meaning:
+.Bl -tag -width Ds
+.It \&?
+Matches any single character.
+.It \&*
+Matches any sequence of characters.
+.It [..]
+Matches any of the characters inside the brackets.
+Ranges of characters can be
+specified by separating two characters by a
+.Ql -
+(e.g.\&
+.Dq [a0-9]
+matches the letter
+.Sq a
+or any digit).
+In order to represent itself, a
+.Ql -
+must either be quoted or the first or last character in the character list.
+Similarly, a
+.Ql \&]
+must be quoted or the first character in the list if it is to represent itself
+instead of the end of the list.
+Also, a
+.Ql \&!
+appearing at the start of the list has special meaning (see below), so to
+represent itself it must be quoted or appear later in the list.
+.Pp
+Within a bracket expression, the name of a
+.Em character class
+enclosed in
+.Sq [:
+and
+.Sq :]
+stands for the list of all characters belonging to that class.
+Supported character classes:
+.Bd -literal -offset indent
+alnum cntrl lower space
+alpha digit print upper
+blank graph punct xdigit
+.Ed
+.Pp
+These match characters using the macros specified in
+.Xr isalnum 3 ,
+.Xr isalpha 3 ,
+and so on.
+A character class may not be used as an endpoint of a range.
+.It [!..]
+Like [..],
+except it matches any character not inside the brackets.
+.Sm off
+.It *( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches any string of characters that matches zero or more occurrences of the
+specified patterns.
+Example: The pattern
+.Ic *(foo|bar)
+matches the strings
+.Dq ,
+.Dq foo ,
+.Dq bar ,
+.Dq foobarfoo ,
+etc.
+.Sm off
+.It +( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches any string of characters that matches one or more occurrences of the
+specified patterns.
+Example: The pattern
+.Ic +(foo|bar)
+matches the strings
+.Dq foo ,
+.Dq bar ,
+.Dq foobar ,
+etc.
+.Sm off
+.It ?( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches the empty string or a string that matches one of the specified
+patterns.
+Example: The pattern
+.Ic ?(foo|bar)
+only matches the strings
+.Dq ,
+.Dq foo ,
+and
+.Dq bar .
+.Sm off
+.It @( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches a string that matches one of the specified patterns.
+Example: The pattern
+.Ic @(foo|bar)
+only matches the strings
+.Dq foo
+and
+.Dq bar .
+.Sm off
+.It !( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches any string that does not match one of the specified patterns.
+Examples: The pattern
+.Ic !(foo|bar)
+matches all strings except
+.Dq foo
+and
+.Dq bar ;
+the pattern
+.Ic !(*)
+matches no strings; the pattern
+.Ic !(?)*\&
+matches all strings (think about it).
+.El
+.Pp
+Note that
+.Nm pdksh
+currently never matches
+.Sq \&.
+and
+.Sq .. ,
+but the original
+.Nm ksh ,
+Bourne
+.Nm sh ,
+and bash do, so this may have to change (too bad).
+.Pp
+Note that none of the above pattern elements match either a period
+.Pq Sq \&.
+at the start of a file name or a slash
+.Pq Sq / ,
+even if they are explicitly used in a [..] sequence; also, the names
+.Sq \&.
+and
+.Sq ..
+are never matched, even by the pattern
+.Sq .* .
+.Pp
+If the
+.Ic markdirs
+option is set, any directories that result from file name generation are marked
+with a trailing
+.Ql / .
+.Ss Input/output redirection
+When a command is executed, its standard input, standard output, and standard
+error (file descriptors 0, 1, and 2, respectively) are normally inherited from
+the shell.
+Three exceptions to this are commands in pipelines, for which
+standard input and/or standard output are those set up by the pipeline,
+asynchronous commands created when job control is disabled, for which standard
+input is initially set to be from
+.Pa /dev/null ,
+and commands for which any of the following redirections have been specified:
+.Bl -tag -width Ds
+.It \*(Gt Ar file
+Standard output is redirected to
+.Ar file .
+If
+.Ar file
+does not exist, it is created; if it does exist, is a regular file, and the
+.Ic noclobber
+option is set, an error occurs; otherwise, the file is truncated.
+Note that this means the command
+.Ic cmd \*(Lt foo \*(Gt foo
+will open
+.Ar foo
+for reading and then truncate it when it opens it for writing, before
+.Ar cmd
+gets a chance to actually read
+.Ar foo .
+.It \*(Gt\*(Ba Ar file
+Same as
+.Ic \*(Gt ,
+except the file is truncated, even if the
+.Ic noclobber
+option is set.
+.It \*(Gt\*(Gt Ar file
+Same as
+.Ic \*(Gt ,
+except if
+.Ar file
+exists it is appended to instead of being truncated.
+Also, the file is opened
+in append mode, so writes always go to the end of the file (see
+.Xr open 2 ) .
+.It \*(Lt Ar file
+Standard input is redirected from
+.Ar file ,
+which is opened for reading.
+.It \*(Lt\*(Gt Ar file
+Same as
+.Ic \*(Lt ,
+except the file is opened for reading and writing.
+.It \*(Lt\*(Lt Ar marker
+After reading the command line containing this kind of redirection (called a
+.Dq here document ) ,
+the shell copies lines from the command source into a temporary file until a
+line matching
+.Ar marker
+is read.
+When the command is executed, standard input is redirected from the
+temporary file.
+If
+.Ar marker
+contains no quoted characters, the contents of the temporary file are processed
+as if enclosed in double quotes each time the command is executed, so
+parameter, command, and arithmetic substitutions are performed, along with
+backslash
+.Pq Sq \e
+escapes for
+.Ql $ ,
+.Ql ` ,
+.Ql \e ,
+and
+.Ql \enewline .
+If multiple here documents are used on the same command line, they are saved in
+order.
+.It \*(Lt\*(Lt- Ar marker
+Same as
+.Ic \*(Lt\*(Lt ,
+except leading tabs are stripped from lines in the here document.
+.It \*(Lt& Ar fd
+Standard input is duplicated from file descriptor
+.Ar fd .
+.Ar fd
+can be a single digit, indicating the number of an existing file descriptor;
+the letter
+.Ql p ,
+indicating the file descriptor associated with the output of the current
+co-process; or the character
+.Ql - ,
+indicating standard input is to be closed.
+.It \*(Gt& Ar fd
+Same as
+.Ic \*(Lt& ,
+except the operation is done on standard output.
+.El
+.Pp
+In any of the above redirections, the file descriptor that is redirected
+(i.e. standard input or standard output)
+can be explicitly given by preceding the
+redirection with a single digit.
+Parameter, command, and arithmetic
+substitutions, tilde substitutions, and (if the shell is interactive)
+file name generation are all performed on the
+.Ar file ,
+.Ar marker ,
+and
+.Ar fd
+arguments of redirections.
+Note, however, that the results of any file name
+generation are only used if a single file is matched; if multiple files match,
+the word with the expanded file name generation characters is used.
+Note
+that in restricted shells, redirections which can create files cannot be used.
+.Pp
+For simple-commands, redirections may appear anywhere in the command; for
+compound-commands
+.Po
+.Ic if
+statements, etc.
+.Pc ,
+any redirections must appear at the end.
+Redirections are processed after
+pipelines are created and in the order they are given, so the following
+will print an error with a line number prepended to it:
+.Pp
+.D1 $ cat /foo/bar 2\*(Gt&1 \*(Gt /dev/null \*(Ba cat -n
+.Ss Arithmetic expressions
+Integer arithmetic expressions can be used with the
+.Ic let
+command, inside $((..)) expressions, inside array references (e.g.\&
+.Ar name Ns Bq Ar expr ) ,
+as numeric arguments to the
+.Ic test
+command, and as the value of an assignment to an integer parameter.
+.Pp
+Expressions may contain alpha-numeric parameter identifiers, array references,
+and integer constants and may be combined with the following C operators
+(listed and grouped in increasing order of precedence):
+.Pp
+Unary operators:
+.Bd -literal -offset indent
++ - ! ~ ++ --
+.Ed
+.Pp
+Binary operators:
+.Bd -literal -offset indent
+,
+= *= /= %= += -= \*(Lt\*(Lt= \*(Gt\*(Gt= &= ^= \*(Ba=
+\*(Ba\*(Ba
+&&
+\*(Ba
+^
+&
+== !=
+\*(Lt \*(Lt= \*(Gt= \*(Gt
+\*(Lt\*(Lt \*(Gt\*(Gt
++ -
+* / %
+.Ed
+.Pp
+Ternary operators:
+.Bd -literal -offset indent
+?: (precedence is immediately higher than assignment)
+.Ed
+.Pp
+Grouping operators:
+.Bd -literal -offset indent
+( )
+.Ed
+.Pp
+A parameter that is NULL or unset evaluates to 0.
+Integer constants may be specified with arbitrary bases using the notation
+.Ar base Ns # Ns Ar number ,
+where
+.Ar base
+is a decimal integer specifying the base, and
+.Ar number
+is a number in the specified base.
+Additionally,
+integers may be prefixed with
+.Sq 0X
+or
+.Sq 0x
+(specifying base 16)
+or
+.Sq 0
+(base 8)
+in all forms of arithmetic expressions,
+except as numeric arguments to the
+.Ic test
+command.
+.Pp
+The operators are evaluated as follows:
+.Bl -tag -width Ds -offset indent
+.It unary +
+Result is the argument (included for completeness).
+.It unary -
+Negation.
+.It \&!
+Logical NOT;
+the result is 1 if argument is zero, 0 if not.
+.It ~
+Arithmetic (bit-wise) NOT.
+.It ++
+Increment; must be applied to a parameter (not a literal or other expression).
+The parameter is incremented by 1.
+When used as a prefix operator, the result
+is the incremented value of the parameter; when used as a postfix operator, the
+result is the original value of the parameter.
+.It --
+Similar to
+.Ic ++ ,
+except the parameter is decremented by 1.
+.It \&,
+Separates two arithmetic expressions; the left-hand side is evaluated first,
+then the right.
+The result is the value of the expression on the right-hand side.
+.It =
+Assignment; the variable on the left is set to the value on the right.
+.It Xo
+.No *= /= += -= \*(Lt\*(Lt=
+.No \*(Gt\*(Gt= &= ^= \*(Ba=
+.Xc
+Assignment operators.
+.Sm off
+.Ao Ar var Ac Xo
+.Aq Ar op
+.No = Aq Ar expr
+.Xc
+.Sm on
+is the same as
+.Sm off
+.Ao Ar var Ac Xo
+.No = Aq Ar var
+.Aq Ar op
+.Aq Ar expr ,
+.Xc
+.Sm on
+with any operator precedence in
+.Aq Ar expr
+preserved.
+For example,
+.Dq var1 *= 5 + 3
+is the same as specifying
+.Dq var1 = var1 * (5 + 3) .
+.It \*(Ba\*(Ba
+Logical OR;
+the result is 1 if either argument is non-zero, 0 if not.
+The right argument is evaluated only if the left argument is zero.
+.It &&
+Logical AND;
+the result is 1 if both arguments are non-zero, 0 if not.
+The right argument is evaluated only if the left argument is non-zero.
+.It \*(Ba
+Arithmetic (bit-wise) OR.
+.It ^
+Arithmetic (bit-wise) XOR
+(exclusive-OR).
+.It &
+Arithmetic (bit-wise) AND.
+.It ==
+Equal; the result is 1 if both arguments are equal, 0 if not.
+.It !=
+Not equal; the result is 0 if both arguments are equal, 1 if not.
+.It \*(Lt
+Less than; the result is 1 if the left argument is less than the right, 0 if
+not.
+.It \*(Lt= \*(Gt= \*(Gt
+Less than or equal, greater than or equal, greater than.
+See
+.Ic \*(Lt .
+.It \*(Lt\*(Lt \*(Gt\*(Gt
+Shift left (right); the result is the left argument with its bits shifted left
+(right) by the amount given in the right argument.
+.It + - * /
+Addition, subtraction, multiplication, and division.
+.It %
+Remainder; the result is the remainder of the division of the left argument by
+the right.
+The sign of the result is unspecified if either argument is negative.
+.It Xo
+.Sm off
+.Aq Ar arg1 ?
+.Aq Ar arg2 :
+.Aq Ar arg3
+.Sm on
+.Xc
+If
+.Aq Ar arg1
+is non-zero, the result is
+.Aq Ar arg2 ;
+otherwise the result is
+.Aq Ar arg3 .
+.El
+.Ss Co-processes
+A co-process, which is a pipeline created with the
+.Sq \*(Ba&
+operator, is an asynchronous process that the shell can both write to (using
+.Ic print -p )
+and read from (using
+.Ic read -p ) .
+The input and output of the co-process can also be manipulated using
+.Ic \*(Gt&p
+and
+.Ic \*(Lt&p
+redirections, respectively.
+Once a co-process has been started, another can't
+be started until the co-process exits, or until the co-process's input has been
+redirected using an
+.Ic exec Ar n Ns Ic \*(Gt&p
+redirection.
+If a co-process's input is redirected in this way, the next
+co-process to be started will share the output with the first co-process,
+unless the output of the initial co-process has been redirected using an
+.Ic exec Ar n Ns Ic \*(Lt&p
+redirection.
+.Pp
+Some notes concerning co-processes:
+.Bl -bullet
+.It
+The only way to close the co-process's input (so the co-process reads an
+end-of-file) is to redirect the input to a numbered file descriptor and then
+close that file descriptor e.g.\&
+.Ic exec 3\*(Gt&p; exec 3\*(Gt&- .
+.It
+In order for co-processes to share a common output, the shell must keep the
+write portion of the output pipe open.
+This means that end-of-file will not be
+detected until all co-processes sharing the co-process's output have exited
+(when they all exit, the shell closes its copy of the pipe).
+This can be
+avoided by redirecting the output to a numbered file descriptor (as this also
+causes the shell to close its copy).
+Note that this behaviour is slightly
+different from the original Korn shell which closes its copy of the write
+portion of the co-process output when the most recently started co-process
+(instead of when all sharing co-processes) exits.
+.It
+.Ic print -p
+will ignore
+.Dv SIGPIPE
+signals during writes if the signal is not being trapped or ignored; the same
+is true if the co-process input has been duplicated to another file descriptor
+and
+.Ic print -u Ns Ar n
+is used.
+.El
+.Ss Functions
+Functions are defined using either Korn shell
+.Ic function Ar function-name
+syntax or the Bourne/POSIX shell
+.Ar function-name Ns ()
+syntax (see below for the difference between the two forms).
+Functions are like
+.Li .-scripts
+(i.e. scripts sourced using the
+.Sq \&.
+built-in)
+in that they are executed in the current environment.
+However, unlike
+.Li .-scripts ,
+shell arguments (i.e. positional parameters $1, $2, etc.)\&
+are never visible inside them.
+When the shell is determining the location of a command, functions
+are searched after special built-in commands, before regular and
+non-regular built-ins, and before the
+.Ev PATH
+is searched.
+.Pp
+An existing function may be deleted using
+.Ic unset Fl f Ar function-name .
+A list of functions can be obtained using
+.Ic typeset +f
+and the function definitions can be listed using
+.Ic typeset -f .
+The
+.Ic autoload
+command (which is an alias for
+.Ic typeset -fu )
+may be used to create undefined functions: when an undefined function is
+executed, the shell searches the path specified in the
+.Ev FPATH
+parameter for a file with the same name as the function, which, if found, is
+read and executed.
+If after executing the file the named function is found to
+be defined, the function is executed; otherwise, the normal command search is
+continued (i.e. the shell searches the regular built-in command table and
+.Ev PATH ) .
+Note that if a command is not found using
+.Ev PATH ,
+an attempt is made to autoload a function using
+.Ev FPATH
+(this is an undocumented feature of the original Korn shell).
+.Pp
+Functions can have two attributes,
+.Dq trace
+and
+.Dq export ,
+which can be set with
+.Ic typeset -ft
+and
+.Ic typeset -fx ,
+respectively.
+When a traced function is executed, the shell's
+.Ic xtrace
+option is turned on for the function's duration; otherwise, the
+.Ic xtrace
+option is turned off.
+The
+.Dq export
+attribute of functions is currently not used.
+In the original Korn shell,
+exported functions are visible to shell scripts that are executed.
+.Pp
+Since functions are executed in the current shell environment, parameter
+assignments made inside functions are visible after the function completes.
+If this is not the desired effect, the
+.Ic typeset
+command can be used inside a function to create a local parameter.
+Note that special parameters (e.g.\&
+.Ic \&$$ , $! )
+can't be scoped in this way.
+.Pp
+The exit status of a function is that of the last command executed in the
+function.
+A function can be made to finish immediately using the
+.Ic return
+command; this may also be used to explicitly specify the exit status.
+.Pp
+Functions defined with the
+.Ic function
+reserved word are treated differently in the following ways from functions
+defined with the
+.Ic ()
+notation:
+.Bl -bullet
+.It
+The $0 parameter is set to the name of the function
+(Bourne-style functions leave $0 untouched).
+.It
+Parameter assignments preceding function calls are not kept in the shell
+environment (executing Bourne-style functions will keep assignments).
+.It
+.Ev OPTIND
+is saved/reset and restored on entry and exit from the function so
+.Ic getopts
+can be used properly both inside and outside the function (Bourne-style
+functions leave
+.Ev OPTIND
+untouched, so using
+.Ic getopts
+inside a function interferes with using
+.Ic getopts
+outside the function).
+.El
+.Pp
+In the future, the following differences will also be added:
+.Bl -bullet
+.It
+A separate trap/signal environment will be used during the execution of
+functions.
+This will mean that traps set inside a function will not affect the
+shell's traps and signals that are not ignored in the shell (but may be
+trapped) will have their default effect in a function.
+.It
+The EXIT trap, if set in a function, will be executed after the function
+returns.
+.El
+.Ss POSIX mode
+The shell is intended to be POSIX compliant;
+however, in some cases, POSIX behaviour is contrary either to
+the original Korn shell behaviour or to user convenience.
+How the shell behaves in these cases is determined by the state of the
+.Ic posix
+option
+.Pq Ic set -o posix .
+If it is on, the POSIX behaviour is followed; otherwise, it is not.
+The
+.Ic posix
+option is set automatically when the shell starts up if the environment
+contains the
+.Ev POSIXLY_CORRECT
+parameter.
+The shell can also be compiled so that it is in POSIX mode by default;
+however, this is usually not desirable.
+.Pp
+The following is a list of things that are affected by the state of the
+.Ic posix
+option:
+.Bl -bullet
+.It
+Occurrences of
+.Ic \e\&"
+inside double quoted
+.Ic `..`
+command substitutions.
+In POSIX mode, the
+.Ic \e\&"
+is interpreted when the command is interpreted;
+in non-POSIX mode,
+the backslash is stripped before the command substitution is interpreted.
+For example,
+.Ic echo \&"`echo \e\&"hi\e\&"`\&"
+produces
+.Dq \&"hi\&"
+in POSIX mode,
+.Dq hi
+in non-POSIX mode.
+To avoid problems, use the
+.Ic $(...)\&
+form of command substitution.
+.It
+.Ic kill -l
+output.
+In POSIX mode, only signal names are listed (in a single line);
+in non-POSIX mode,
+signal numbers, names, and descriptions are printed (in columns).
+In the future, a new option
+.Pq Fl v No perhaps
+will be added to distinguish the two behaviours.
+.It
+.Ic echo
+options.
+In POSIX mode,
+.Fl e
+and
+.Fl E
+are not treated as options, but printed like other arguments;
+in non-POSIX mode, these options control the interpretation
+of backslash sequences.
+.It
+.Ic fg
+exit status.
+In POSIX mode, the exit status is 0 if no errors occur;
+in non-POSIX mode, the exit status is that of the last foregrounded job.
+.It
+.Ic eval
+exit status.
+If
+.Ic eval
+gets to see an empty command (i.e.\&
+.Ic eval `false` ) ,
+its exit status in POSIX mode will be 0.
+In non-POSIX mode,
+it will be the exit status of the last command substitution that was
+done in the processing of the arguments to
+.Ic eval
+(or 0 if there were no command substitutions).
+.It
+.Ic getopts .
+In POSIX mode, options must start with a
+.Ql - ;
+in non-POSIX mode, options can start with either
+.Ql -
+or
+.Ql + .
+.It
+Brace expansion (also known as alternation).
+In POSIX mode, brace expansion is disabled;
+in non-POSIX mode, brace expansion is enabled.
+Note that
+.Ic set -o posix
+(or setting the
+.Ev POSIXLY_CORRECT
+parameter) automatically turns the
+.Ic braceexpand
+option off; however, it can be explicitly turned on later.
+.It
+.Ic set - .
+In POSIX mode, this does not clear the
+.Ic verbose
+or
+.Ic xtrace
+options; in non-POSIX mode, it does.
+.It
+.Ic set
+exit status.
+In POSIX mode, the exit status of
+.Ic set
+is 0 if there are no errors;
+in non-POSIX mode, the exit status is that of any
+command substitutions performed in generating the
+.Ic set
+command.
+For example,
+.Ic set -- `false`; echo $?\&
+prints 0 in POSIX mode, 1 in non-POSIX mode.
+This construct is used in most shell scripts that use the old
+.Xr getopt 1
+command.
+.It
+Argument expansion of the
+.Ic alias ,
+.Ic export ,
+.Ic readonly ,
+and
+.Ic typeset
+commands.
+In POSIX mode, normal argument expansion is done; in non-POSIX mode,
+field splitting, file globbing, brace expansion, and (normal) tilde expansion
+are turned off, while assignment tilde expansion is turned on.
+.It
+Signal specification.
+In POSIX mode, signals can be specified as digits, only
+if signal numbers match POSIX values
+(i.e. HUP=1, INT=2, QUIT=3, ABRT=6, KILL=9, ALRM=14, and TERM=15);
+in non-POSIX mode, signals can always be digits.
+.It
+Alias expansion.
+In POSIX mode, alias expansion is only carried out when reading command words;
+in non-POSIX mode, alias expansion is carried out on any
+word following an alias that ended in a space.
+For example, the following
+.Ic for
+loop uses parameter
+.Sq i
+in POSIX mode and
+.Sq j
+in non-POSIX mode:
+.Bd -literal -offset indent
+alias a='for ' i='j'
+a i in 1 2; do echo i=$i j=$j; done
+.Ed
+.It
+.Ic test .
+In POSIX mode, the expression
+.Sq Fl t
+(preceded by some number of
+.Sq \&!
+arguments) is always true as it is a non-zero length string;
+in non-POSIX mode, it tests if file descriptor 1 is a
+.Xr tty 4
+(i.e. the
+.Ar fd
+argument to the
+.Fl t
+test may be left out and defaults to 1).
+.El
+.Ss Strict Bourne shell mode
+When the
+.Ic sh
+option is enabled (see the
+.Ic set
+command),
+.Nm
+will behave like
+.Xr sh 1
+in the following ways:
+.Bl -bullet
+.It
+The parameter
+.Ic $_
+is not set to:
+.Pp
+.Bl -dash -compact
+.It
+the expanded alias' full program path after entering commands
+that are tracked aliases
+.It
+the last argument on the command line after entering external
+commands
+.It
+the file that changed when
+.Ev MAILPATH
+is set to monitor a mailbox
+.El
+.It
+File descriptors are left untouched when executing
+.Ic exec
+with no arguments.
+.It
+Backslash-escaped special characters are not substituted in
+.Ev PS1 .
+.It
+Sequences of
+.Sq ((...))
+are not interpreted as arithmetic expressions.
+.El
+.Ss Command execution
+After evaluation of command-line arguments, redirections, and parameter
+assignments, the type of command is determined: a special built-in, a
+function, a regular built-in, or the name of a file to execute found using the
+.Ev PATH
+parameter.
+The checks are made in the above order.
+Special built-in commands differ from other commands in that the
+.Ev PATH
+parameter is not used to find them, an error during their execution can
+cause a non-interactive shell to exit, and parameter assignments that are
+specified before the command are kept after the command completes.
+Just to confuse things, if the
+.Ic posix
+option is turned off (see the
+.Ic set
+command below), some special commands are very special in that no field
+splitting, file globbing, brace expansion, nor tilde expansion is performed
+on arguments that look like assignments.
+Regular built-in commands are different only in that the
+.Ev PATH
+parameter is not used to find them.
+.Pp
+The original
+.Nm ksh
+and POSIX differ somewhat in which commands are considered
+special or regular:
+.Pp
+POSIX special commands
+.Pp
+.Ic \&. , \&: , break , continue ,
+.Ic eval , exec , exit , export ,
+.Ic readonly , return , set , shift ,
+.Ic times , trap , unset
+.Pp
+Additional
+.Nm
+special commands
+.Pp
+.Ic builtin , typeset
+.Pp
+Very special commands
+.Pq when POSIX mode is off
+.Pp
+.Ic alias , readonly , set , typeset
+.Pp
+POSIX regular commands
+.Pp
+.Ic alias , bg , cd , command ,
+.Ic false , fc , fg , getopts ,
+.Ic jobs , kill , pwd , read ,
+.Ic true , umask , unalias , wait
+.Pp
+Additional
+.Nm
+regular commands
+.Pp
+.Ic \&[ , echo , let , mknod ,
+.Ic print , suspend , test ,
+.Ic ulimit , whence
+.Pp
+In the future, the additional
+.Nm
+special and regular commands may be treated
+differently from the POSIX special and regular commands.
+.Pp
+Once the type of command has been determined, any command-line parameter
+assignments are performed and exported for the duration of the command.
+.Pp
+The following describes the special and regular built-in commands:
+.Pp
+.Bl -tag -width Ds -compact
+.It Ic \&. Ar file Op Ar arg ...
+Execute the commands in
+.Ar file
+in the current environment.
+The file is searched for in the directories of
+.Ev PATH .
+If arguments are given, the positional parameters may be used to access them
+while
+.Ar file
+is being executed.
+If no arguments are given, the positional parameters are
+those of the environment the command is used in.
+.Pp
+.It Ic \&: Op Ar ...
+The null command.
+Exit status is set to zero.
+.Pp
+.It Xo Ic alias
+.Oo Fl d \*(Ba t Oo Fl r Oc \*(Ba
+.Cm +-x Oc
+.Op Fl p
+.Op Cm +
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+Without arguments,
+.Ic alias
+lists all aliases.
+For any name without a value, the existing alias is listed.
+Any name with a value defines an alias (see
+.Sx Aliases
+above).
+.Pp
+When listing aliases, one of two formats is used.
+Normally, aliases are listed as
+.Ar name Ns = Ns Ar value ,
+where
+.Ar value
+is quoted.
+If options were preceded with
+.Ql + ,
+or a lone
+.Ql +
+is given on the command line, only
+.Ar name
+is printed.
+.Pp
+The
+.Fl d
+option causes directory aliases, which are used in tilde expansion, to be
+listed or set (see
+.Sx Tilde expansion
+above).
+.Pp
+If the
+.Fl p
+option is used, each alias is prefixed with the string
+.Dq alias\ \& .
+.Pp
+The
+.Fl t
+option indicates that tracked aliases are to be listed/set (values specified on
+the command line are ignored for tracked aliases).
+The
+.Fl r
+option indicates that all tracked aliases are to be reset.
+.Pp
+The
+.Fl x
+option sets
+.Pq Ic +x No clears
+the export attribute of an alias, or, if no names are given, lists the aliases
+with the export attribute (exporting an alias has no effect).
+.Pp
+.It Ic bg Op Ar job ...
+Resume the specified stopped job(s) in the background.
+If no jobs are specified,
+.Ic %+
+is assumed.
+See
+.Sx Job control
+below for more information.
+.Pp
+.It Ic bind Op Fl l
+The current bindings are listed.
+If the
+.Fl l
+flag is given,
+.Ic bind
+instead lists the names of the functions to which keys may be bound.
+See
+.Sx Emacs editing mode
+for more information.
+.Pp
+.It Xo Ic bind Op Fl m
+.Ar string Ns = Ns Op Ar substitute
+.Ar ...
+.Xc
+.It Xo Ic bind
+.Ar string Ns = Ns Op Ar editing-command
+.Ar ...
+.Xc
+The specified editing command is bound to the given
+.Ar string ,
+which should consist of a control character
+optionally preceded by one of the two prefix characters.
+Future input of the
+.Ar string
+will cause the editing command to be immediately invoked.
+If the
+.Fl m
+flag is given, the specified input
+.Ar string
+will afterwards be immediately replaced by the given
+.Ar substitute
+string, which may contain editing commands.
+.Pp
+Control characters may be written using caret notation
+i.e. ^X represents Control-X.
+Multi-character sequences are supported.
+.Pp
+The following default bindings show how the arrow keys
+on an ANSI terminal or xterm are bound
+(of course some escape sequences won't work out quite this nicely):
+.Bd -literal -offset indent
+bind '^[[A'=up-history
+bind '^[[B'=down-history
+bind '^[[C'=forward-char
+bind '^[[D'=backward-char
+.Ed
+.Pp
+.It Ic break Op Ar level
+Exit the
+.Ar level Ns th
+inner-most
+.Ic for ,
+.Ic select ,
+.Ic until ,
+or
+.Ic while
+loop.
+.Ar level
+defaults to 1.
+.Pp
+.It Ic builtin Ar command Op Ar arg ...
+Execute the built-in command
+.Ar command .
+.Pp
+.It Xo
+.Ic cd
+.Op Fl LP
+.Op Ar dir
+.Xc
+Set the working directory to
+.Ar dir .
+If the parameter
+.Ev CDPATH
+is set, it lists the search path for the directory containing
+.Ar dir .
+A
+.Dv NULL
+path means the current directory.
+If
+.Ar dir
+is found in any component of the
+.Ev CDPATH
+search path other than the
+.Dv NULL
+path, the name of the new working directory will be written to standard output.
+If
+.Ar dir
+is missing, the home directory
+.Ev HOME
+is used.
+If
+.Ar dir
+is
+.Ql - ,
+the previous working directory is used (see the
+.Ev OLDPWD
+parameter).
+.Pp
+If the
+.Fl L
+option (logical path) is used or if the
+.Ic physical
+option isn't set (see the
+.Ic set
+command below), references to
+.Sq ..
+in
+.Ar dir
+are relative to the path used to get to the directory.
+If the
+.Fl P
+option (physical path) is used or if the
+.Ic physical
+option is set,
+.Sq ..
+is relative to the filesystem directory tree.
+The
+.Ev PWD
+and
+.Ev OLDPWD
+parameters are updated to reflect the current and old working directory,
+respectively.
+.Pp
+.It Xo
+.Ic cd
+.Op Fl LP
+.Ar old new
+.Xc
+The string
+.Ar new
+is substituted for
+.Ar old
+in the current directory, and the shell attempts to change to the new
+directory.
+.Pp
+.It Xo
+.Ic command
+.Op Fl pVv
+.Ar cmd
+.Op Ar arg ...
+.Xc
+If neither the
+.Fl v
+nor
+.Fl V
+option is given,
+.Ar cmd
+is executed exactly as if
+.Ic command
+had not been specified, with two exceptions:
+firstly,
+.Ar cmd
+cannot be a shell function;
+and secondly, special built-in commands lose their specialness
+(i.e. redirection and utility errors do not cause the shell to
+exit, and command assignments are not permanent).
+.Pp
+If the
+.Fl p
+option is given, a default search path is used instead of the current value of
+.Ev PATH
+(the actual value of the default path is system dependent: on
+POSIX-ish systems, it is the value returned by
+.Ic getconf CS_PATH ) .
+.Pp
+If the
+.Fl v
+option is given, instead of executing
+.Ar cmd ,
+information about what would be executed is given (and the same is done for
+.Ar arg ... ) .
+For special and regular built-in commands and functions, their names are simply
+printed; for aliases, a command that defines them is printed; and for commands
+found by searching the
+.Ev PATH
+parameter, the full path of the command is printed.
+If no command is found
+(i.e. the path search fails), nothing is printed and
+.Ic command
+exits with a non-zero status.
+The
+.Fl V
+option is like the
+.Fl v
+option, except it is more verbose.
+.Pp
+.It Ic continue Op Ar level
+Jumps to the beginning of the
+.Ar level Ns th
+inner-most
+.Ic for ,
+.Ic select ,
+.Ic until ,
+or
+.Ic while
+loop.
+.Ar level
+defaults to 1.
+.Pp
+.It Xo
+.Ic echo
+.Op Fl Een
+.Op Ar arg ...
+.Xc
+Prints its arguments (separated by spaces) followed by a newline, to the
+standard output.
+The newline is suppressed if any of the arguments contain the
+backslash sequence
+.Ql \ec .
+See the
+.Ic print
+command below for a list of other backslash sequences that are recognized.
+.Pp
+The options are provided for compatibility with
+.Bx
+shell scripts.
+The
+.Fl n
+option suppresses the trailing newline,
+.Fl e
+enables backslash interpretation (a no-op, since this is normally done), and
+.Fl E
+suppresses backslash interpretation.
+If the
+.Ic posix
+option is set, only the first argument is treated as an option, and only
+if it is exactly
+.Dq -n .
+.Pp
+.It Ic eval Ar command ...
+The arguments are concatenated (with spaces between them) to form a single
+string which the shell then parses and executes in the current environment.
+.Pp
+.It Xo
+.Ic exec
+.Op Ar command Op Ar arg ...
+.Xc
+The command is executed without forking, replacing the shell process.
+.Pp
+If no command is given except for I/O redirection, the I/O redirection is
+permanent and the shell is
+not replaced.
+Any file descriptors greater than 2 which are opened or
+.Xr dup 2 Ns 'd
+in this way are not made available to other executed commands (i.e. commands
+that are not built-in to the shell).
+Note that the Bourne shell differs here;
+it does pass these file descriptors on.
+.Pp
+.It Ic exit Op Ar status
+The shell exits with the specified exit status.
+If
+.Ar status
+is not specified, the exit status is the current value of the
+.Ic $?\&
+parameter.
+.Pp
+.It Xo
+.Ic export
+.Op Fl p
+.Op Ar parameter Ns Op = Ns Ar value
+.Xc
+Sets the export attribute of the named parameters.
+Exported parameters are passed in the environment to executed commands.
+If values are specified, the named parameters are also assigned.
+.Pp
+If no parameters are specified, the names of all parameters with the export
+attribute are printed one per line, unless the
+.Fl p
+option is used, in which case
+.Ic export
+commands defining all exported parameters, including their values, are printed.
+.Pp
+.It Ic false
+A command that exits with a non-zero status.
+.Pp
+.It Xo
+.Ic fc
+.Oo
+.Fl e Ar editor \*(Ba
+.Fl l Op Fl n
+.Oc
+.Op Fl r
+.Op Ar first Op Ar last
+.Xc
+.Ar first
+and
+.Ar last
+select commands from the history.
+Commands can be selected by history number
+or a string specifying the most recent command starting with that string.
+The
+.Fl l
+option lists the command on standard output, and
+.Fl n
+inhibits the default command numbers.
+The
+.Fl r
+option reverses the order of the list.
+Without
+.Fl l ,
+the selected commands are edited by the editor specified with the
+.Fl e
+option, or if no
+.Fl e
+is specified, the editor specified by the
+.Ev FCEDIT
+parameter (if this parameter is not set,
+.Pa /bin/ed
+is used), and then executed by the shell.
+.Pp
+.It Xo
+.Ic fc
+.Cm -e - \*(Ba Fl s
+.Op Fl g
+.Op Ar old Ns = Ns Ar new
+.Op Ar prefix
+.Xc
+Re-execute the selected command (the previous command by default) after
+performing the optional substitution of
+.Ar old
+with
+.Ar new .
+If
+.Fl g
+is specified, all occurrences of
+.Ar old
+are replaced with
+.Ar new .
+The meaning of
+.Cm -e -
+and
+.Fl s
+is identical: re-execute the selected command without invoking an editor.
+This command is usually accessed with the predefined
+.Ic alias r='fc -e -' .
+.Pp
+.It Ic fg Op Ar job ...
+Resume the specified job(s) in the foreground.
+If no jobs are specified,
+.Ic %+
+is assumed.
+See
+.Sx Job control
+below for more information.
+.Pp
+.It Xo
+.Ic getopts
+.Ar optstring name
+.Op Ar arg ...
+.Xc
+Used by shell procedures to parse the specified arguments (or positional
+parameters, if no arguments are given) and to check for legal options.
+.Ar optstring
+contains the option letters that
+.Ic getopts
+is to recognize.
+If a letter is followed by a colon, the option is expected to
+have an argument.
+Options that do not take arguments may be grouped in a single argument.
+If an option takes an argument and the option character is not the
+last character of the argument it is found in, the remainder of the argument is
+taken to be the option's argument; otherwise, the next argument is the option's
+argument.
+.Pp
+Each time
+.Ic getopts
+is invoked, it places the next option in the shell parameter
+.Ar name
+and the index of the argument to be processed by the next call to
+.Ic getopts
+in the shell parameter
+.Ev OPTIND .
+If the option was introduced with a
+.Ql + ,
+the option placed in
+.Ar name
+is prefixed with a
+.Ql + .
+When an option requires an argument,
+.Ic getopts
+places it in the shell parameter
+.Ev OPTARG .
+.Pp
+When an illegal option or a missing option argument is encountered, a question
+mark or a colon is placed in
+.Ar name
+(indicating an illegal option or missing argument, respectively) and
+.Ev OPTARG
+is set to the option character that caused the problem.
+Furthermore, if
+.Ar optstring
+does not begin with a colon, a question mark is placed in
+.Ar name ,
+.Ev OPTARG
+is unset, and an error message is printed to standard error.
+.Pp
+When the end of the options is encountered,
+.Ic getopts
+exits with a non-zero exit status.
+Options end at the first (non-option
+argument) argument that does not start with a
+.Ql - ,
+or when a
+.Ql --
+argument is encountered.
+.Pp
+Option parsing can be reset by setting
+.Ev OPTIND
+to 1 (this is done automatically whenever the shell or a shell procedure is
+invoked).
+.Pp
+Warning: Changing the value of the shell parameter
+.Ev OPTIND
+to a value other than 1, or parsing different sets of arguments without
+resetting
+.Ev OPTIND ,
+may lead to unexpected results.
+.Pp
+.It Xo
+.Ic hash
+.Op Fl r
+.Op Ar name ...
+.Xc
+Without arguments, any hashed executable command pathnames are listed.
+The
+.Fl r
+option causes all hashed commands to be removed from the hash table.
+Each
+.Ar name
+is searched as if it were a command name and added to the hash table if it is
+an executable command.
+.Pp
+.It Xo
+.Ic jobs
+.Op Fl lnp
+.Op Ar job ...
+.Xc
+Display information about the specified job(s); if no jobs are specified, all
+jobs are displayed.
+The
+.Fl n
+option causes information to be displayed only for jobs that have changed
+state since the last notification.
+If the
+.Fl l
+option is used, the process ID of each process in a job is also listed.
+The
+.Fl p
+option causes only the process group of each job to be printed.
+See
+.Sx Job control
+below for the format of
+.Ar job
+and the displayed job.
+.Pp
+.It Xo
+.Ic kill
+.Oo Fl s Ar signame \*(Ba
+.No - Ns Ar signum \*(Ba
+.No - Ns Ar signame Oc
+.No { Ar job \*(Ba pid \*(Ba pgrp No }
+.Ar ...
+.Xc
+Send the specified signal to the specified jobs, process IDs, or process
+groups.
+If no signal is specified, the
+.Dv TERM
+signal is sent.
+If a job is specified, the signal is sent to the job's process group.
+See
+.Sx Job control
+below for the format of
+.Ar job .
+.Pp
+.It Xo
+.Ic kill
+.Fl l
+.Op Ar exit-status ...
+.Xc
+Print the signal name corresponding to
+.Ar exit-status .
+If no arguments are specified, a list of all the signals, their numbers, and
+a short description of them are printed.
+.Pp
+.It Ic let Op Ar expression ...
+Each expression is evaluated (see
+.Sx Arithmetic expressions
+above).
+If all expressions are successfully evaluated, the exit status is 0 (1)
+if the last expression evaluated to non-zero (zero).
+If an error occurs during
+the parsing or evaluation of an expression, the exit status is greater than 1.
+Since expressions may need to be quoted,
+.No (( Ar expr No ))
+is syntactic sugar for
+.No let \&" Ns Ar expr Ns \&" .
+.Pp
+.It Xo
+.Ic mknod
+.Op Fl m Ar mode
+.Ar name
+.Cm b\*(Bac
+.Ar major minor
+.Xc
+.It Xo
+.Ic mknod
+.Op Fl m Ar mode
+.Ar name
+.Cm p
+.Xc
+Create a device special file.
+The file type may be
+.Cm b
+(block type device),
+.Cm c
+(character type device),
+or
+.Cm p
+(named pipe).
+The file created may be modified according to its
+.Ar mode
+(via the
+.Fl m
+option),
+.Ar major
+(major device number),
+and
+.Ar minor
+(minor device number).
+.Pp
+See
+.Xr mknod 8
+for further information.
+.Pp
+.It Xo
+.Ic print
+.Oo
+.Fl nprsu Ns Oo Ar n Oc \*(Ba
+.Fl R Op Fl en
+.Oc
+.Op Ar argument ...
+.Xc
+.Ic print
+prints its arguments on the standard output, separated by spaces and
+terminated with a newline.
+The
+.Fl n
+option suppresses the newline.
+By default, certain C escapes are translated.
+These include
+.Ql \eb ,
+.Ql \ef ,
+.Ql \en ,
+.Ql \er ,
+.Ql \et ,
+.Ql \ev ,
+and
+.Ql \e0###
+.Po
+.Ql #
+is an octal digit, of which there may be 0 to 3
+.Pc .
+.Ql \ec
+is equivalent to using the
+.Fl n
+option.
+.Ql \e
+expansion may be inhibited with the
+.Fl r
+option.
+The
+.Fl s
+option prints to the history file instead of standard output; the
+.Fl u
+option prints to file descriptor
+.Ar n
+.Po
+.Ar n
+defaults to 1 if omitted
+.Pc ;
+and the
+.Fl p
+option prints to the co-process (see
+.Sx Co-processes
+above).
+.Pp
+The
+.Fl R
+option is used to emulate, to some degree, the
+.Bx
+.Xr echo 1
+command, which does not process
+.Ql \e
+sequences unless the
+.Fl e
+option is given.
+As above, the
+.Fl n
+option suppresses the trailing newline.
+.Pp
+.It Ic pwd Op Fl LP
+Print the present working directory.
+If the
+.Fl L
+option is used or if the
+.Ic physical
+option isn't set (see the
+.Ic set
+command below), the logical path is printed (i.e. the path used to
+.Ic cd
+to the current directory).
+If the
+.Fl P
+option (physical path) is used or if the
+.Ic physical
+option is set, the path determined from the filesystem (by following
+.Sq ..
+directories to the root directory) is printed.
+.Pp
+.It Xo
+.Ic read
+.Op Fl prsu Ns Op Ar n
+.Op Ar parameter ...
+.Xc
+Reads a line of input from the standard input, separates the line into fields
+using the
+.Ev IFS
+parameter (see
+.Sx Substitution
+above), and assigns each field to the specified parameters.
+If there are more parameters than fields, the extra parameters are set to
+.Dv NULL ,
+or alternatively, if there are more fields than parameters, the last parameter
+is assigned the remaining fields (inclusive of any separating spaces).
+If no parameters are specified, the
+.Ev REPLY
+parameter is used.
+If the input line ends in a backslash and the
+.Fl r
+option was not used, the backslash and the newline are stripped and more input
+is read.
+If no input is read,
+.Ic read
+exits with a non-zero status.
+.Pp
+The first parameter may have a question mark and a string appended to it, in
+which case the string is used as a prompt (printed to standard error before
+any input is read) if the input is a
+.Xr tty 4
+(e.g.\&
+.Ic read nfoo?'number of foos: ' ) .
+.Pp
+The
+.Fl u Ns Ar n
+and
+.Fl p
+options cause input to be read from file descriptor
+.Ar n
+.Pf ( Ar n
+defaults to 0 if omitted)
+or the current co-process (see
+.Sx Co-processes
+above for comments on this), respectively.
+If the
+.Fl s
+option is used, input is saved to the history file.
+.Pp
+.It Xo
+.Ic readonly
+.Op Fl p
+.Oo Ar parameter
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+Sets the read-only attribute of the named parameters.
+If values are given,
+parameters are set to them before setting the attribute.
+Once a parameter is
+made read-only, it cannot be unset and its value cannot be changed.
+.Pp
+If no parameters are specified, the names of all parameters with the read-only
+attribute are printed one per line, unless the
+.Fl p
+option is used, in which case
+.Ic readonly
+commands defining all read-only parameters, including their values, are
+printed.
+.Pp
+.It Ic return Op Ar status
+Returns from a function or
+.Ic .\&
+script, with exit status
+.Ar status .
+If no
+.Ar status
+is given, the exit status of the last executed command is used.
+If used outside of a function or
+.Ic .\&
+script, it has the same effect as
+.Ic exit .
+Note that
+.Nm pdksh
+treats both profile and
+.Ev ENV
+files as
+.Ic .\&
+scripts, while the original Korn shell only treats profiles as
+.Ic .\&
+scripts.
+.Pp
+.It Xo
+.Ic set Op Ic +-abCefhkmnpsuvXx
+.Op Ic +-o Ar option
+.Op Ic +-A Ar name
+.Op Fl -
+.Op Ar arg ...
+.Xc
+The
+.Ic set
+command can be used to set
+.Pq Ic -
+or clear
+.Pq Ic +
+shell options, set the positional parameters, or set an array parameter.
+Options can be changed using the
+.Cm +-o Ar option
+syntax, where
+.Ar option
+is the long name of an option, or using the
+.Cm +- Ns Ar letter
+syntax, where
+.Ar letter
+is the option's single letter name (not all options have a single letter name).
+The following table lists both option letters (if they exist) and long names
+along with a description of what the option does:
+.Bl -tag -width 15n
+.It Fl A Ar name
+Sets the elements of the array parameter
+.Ar name
+to
+.Ar arg ...
+If
+.Fl A
+is used, the array is reset (i.e. emptied) first; if
+.Ic +A
+is used, the first N elements are set (where N is the number of arguments);
+the rest are left untouched.
+.It Fl a \*(Ba Ic allexport
+All new parameters are created with the export attribute.
+.It Fl b \*(Ba Ic notify
+Print job notification messages asynchronously, instead of just before the
+prompt.
+Only used if job control is enabled
+.Pq Fl m .
+.It Fl C \*(Ba Ic noclobber
+Prevent \*(Gt redirection from overwriting existing files.
+Instead, \*(Gt\*(Ba must be used to force an overwrite.
+.It Fl e \*(Ba Ic errexit
+Exit (after executing the
+.Dv ERR
+trap) as soon as an error occurs or a command fails (i.e. exits with a
+non-zero status).
+This does not apply to commands whose exit status is
+explicitly tested by a shell construct such as
+.Ic if ,
+.Ic until ,
+.Ic while ,
+or
+.Ic !\&
+statements.
+For
+.Ic &&
+or
+.Ic || ,
+only the status of the last command is tested.
+.It Fl f \*(Ba Ic noglob
+Do not expand file name patterns.
+.It Fl h \*(Ba Ic trackall
+Create tracked aliases for all executed commands (see
+.Sx Aliases
+above).
+Enabled by default for non-interactive shells.
+.It Fl k \*(Ba Ic keyword
+Parameter assignments are recognized anywhere in a command.
+.It Fl m \*(Ba Ic monitor
+Enable job control (default for interactive shells).
+.It Fl n \*(Ba Ic noexec
+Do not execute any commands.
+Useful for checking the syntax of scripts
+(ignored if interactive).
+.It Fl p \*(Ba Ic privileged
+The shell is a privileged shell.
+It is set automatically if, when the shell starts,
+the real UID or GID does not match
+the effective UID (EUID) or GID (EGID), respectively.
+See above for a description of what this means.
+.It Fl s \*(Ba Ic stdin
+If used when the shell is invoked, commands are read from standard input.
+Set automatically if the shell is invoked with no arguments.
+.Pp
+When
+.Fl s
+is used with the
+.Ic set
+command it causes the specified arguments to be sorted before assigning them to
+the positional parameters (or to array
+.Ar name ,
+if
+.Fl A
+is used).
+.It Fl u \*(Ba Ic nounset
+Referencing of an unset parameter is treated as an error, unless one of the
+.Ql - ,
+.Ql + ,
+or
+.Ql =
+modifiers is used.
+.It Fl v \*(Ba Ic verbose
+Write shell input to standard error as it is read.
+.It Fl X \*(Ba Ic markdirs
+Mark directories with a trailing
+.Ql /
+during file name generation.
+.It Fl x \*(Ba Ic xtrace
+Print commands and parameter assignments when they are executed, preceded by
+the value of
+.Ev PS4 .
+.It Ic bgnice
+Background jobs are run with lower priority.
+.It Ic braceexpand
+Enable brace expansion (a.k.a. alternation).
+.It Ic csh-history
+Enables a subset of
+.Xr csh 1 Ns -style
+history editing using the
+.Ql !\&
+character.
+.It Ic emacs
+Enable BRL emacs-like command-line editing (interactive shells only); see
+.Sx Emacs editing mode .
+.It Ic emacs-usemeta
+In emacs command-line editing, use the 8th bit as meta (^[) prefix.
+This is the default.
+.It Ic gmacs
+Enable gmacs-like command-line editing (interactive shells only).
+Currently identical to emacs editing except that transpose (^T) acts slightly
+differently.
+.It Ic ignoreeof
+The shell will not (easily) exit when end-of-file is read;
+.Ic exit
+must be used.
+To avoid infinite loops, the shell will exit if
+.Dv EOF
+is read 13 times in a row.
+.It Ic interactive
+The shell is an interactive shell.
+This option can only be used when the shell is invoked.
+See above for a description of what this means.
+.It Ic login
+The shell is a login shell.
+This option can only be used when the shell is invoked.
+See above for a description of what this means.
+.It Ic nohup
+Do not kill running jobs with a
+.Dv SIGHUP
+signal when a login shell exits.
+Currently set by default, but this will
+change in the future to be compatible with the original Korn shell (which
+doesn't have this option, but does send the
+.Dv SIGHUP
+signal).
+.It Ic nolog
+No effect.
+In the original Korn shell, this prevents function definitions from
+being stored in the history file.
+.It Ic physical
+Causes the
+.Ic cd
+and
+.Ic pwd
+commands to use
+.Dq physical
+(i.e. the filesystem's)
+.Sq ..
+directories instead of
+.Dq logical
+directories (i.e. the shell handles
+.Sq .. ,
+which allows the user to be oblivious of symbolic links to directories).
+Clear by default.
+Note that setting this option does not affect the current value of the
+.Ev PWD
+parameter; only the
+.Ic cd
+command changes
+.Ev PWD .
+See the
+.Ic cd
+and
+.Ic pwd
+commands above for more details.
+.It Ic posix
+Enable POSIX mode.
+See
+.Sx POSIX mode
+above.
+.It Ic restricted
+The shell is a restricted shell.
+This option can only be used when the shell is invoked.
+See above for a description of what this means.
+.It Ic sh
+Enable strict Bourne shell mode (see
+.Sx Strict Bourne shell mode
+above).
+.It Ic vi
+Enable
+.Xr vi 1 Ns -like
+command-line editing (interactive shells only).
+.It Ic vi-esccomplete
+In vi command-line editing, do command and file name completion when escape
+(^[) is entered in command mode.
+.It Ic vi-show8
+Prefix characters with the eighth bit set with
+.Sq M- .
+If this option is not set, characters in the range 128\-160 are printed as is,
+which may cause problems.
+.It Ic vi-tabcomplete
+In vi command-line editing, do command and file name completion when tab (^I)
+is entered in insert mode.
+This is the default.
+.It Ic viraw
+No effect.
+In the original Korn shell, unless
+.Ic viraw
+was set, the vi command-line mode would let the
+.Xr tty 4
+driver do the work until ESC (^[) was entered.
+.Nm pdksh
+is always in viraw mode.
+.El
+.Pp
+These options can also be used upon invocation of the shell.
+The current set of
+options (with single letter names) can be found in the parameter
+.Sq $- .
+.Ic set Fl o
+with no option name will list all the options and whether each is on or off;
+.Ic set +o
+will print the long names of all options that are currently on.
+.Pp
+Remaining arguments, if any, are positional parameters and are assigned, in
+order, to the positional parameters (i.e. $1, $2, etc.).
+If options end with
+.Ql --
+and there are no remaining arguments, all positional parameters are cleared.
+If no options or arguments are given, the values of all names are printed.
+For unknown historical reasons, a lone
+.Ql -
+option is treated specially \- it clears both the
+.Fl x
+and
+.Fl v
+options.
+.Pp
+.It Ic shift Op Ar number
+The positional parameters
+.Ar number Ns +1 ,
+.Ar number Ns +2 ,
+etc. are renamed to
+.Sq 1 ,
+.Sq 2 ,
+etc.
+.Ar number
+defaults to 1.
+.Pp
+.It Ic suspend
+Stops the shell as if it had received the suspend character from
+the terminal.
+It is not possible to suspend a login shell unless the parent process
+is a member of the same terminal session but is a member of a different
+process group.
+As a general rule, if the shell was started by another shell or via
+.Xr su 1 ,
+it can be suspended.
+.Pp
+.It Ic test Ar expression
+.It Ic \&[ Ar expression Ic \&]
+.Ic test
+evaluates the
+.Ar expression
+and returns zero status if true, 1 if false, or greater than 1 if there
+was an error.
+It is normally used as the condition command of
+.Ic if
+and
+.Ic while
+statements.
+Symbolic links are followed for all
+.Ar file
+expressions except
+.Fl h
+and
+.Fl L .
+.Pp
+The following basic expressions are available:
+.Bl -tag -width 17n
+.It Fl a Ar file
+.Ar file
+exists.
+.It Fl b Ar file
+.Ar file
+is a block special device.
+.It Fl c Ar file
+.Ar file
+is a character special device.
+.It Fl d Ar file
+.Ar file
+is a directory.
+.It Fl e Ar file
+.Ar file
+exists.
+.It Fl f Ar file
+.Ar file
+is a regular file.
+.It Fl G Ar file
+.Ar file Ns 's
+group is the shell's effective group ID.
+.It Fl g Ar file
+.Ar file Ns 's
+mode has the setgid bit set.
+.It Fl h Ar file
+.Ar file
+is a symbolic link.
+.It Fl k Ar file
+.Ar file Ns 's
+mode has the
+.Xr sticky 8
+bit set.
+.It Fl L Ar file
+.Ar file
+is a symbolic link.
+.It Fl O Ar file
+.Ar file Ns 's
+owner is the shell's effective user ID.
+.It Fl o Ar option
+Shell
+.Ar option
+is set (see the
+.Ic set
+command above for a list of options).
+As a non-standard extension, if the option starts with a
+.Ql \&! ,
+the test is negated; the test always fails if
+.Ar option
+doesn't exist (so [ -o foo -o -o !foo ] returns true if and only if option
+.Ar foo
+exists).
+.It Fl p Ar file
+.Ar file
+is a named pipe.
+.It Fl r Ar file
+.Ar file
+exists and is readable.
+.It Fl S Ar file
+.Ar file
+is a
+.Xr unix 4 Ns -domain
+socket.
+.It Fl s Ar file
+.Ar file
+is not empty.
+.It Fl t Op Ar fd
+File descriptor
+.Ar fd
+is a
+.Xr tty 4
+device.
+If the
+.Ic posix
+option is not set,
+.Ar fd
+may be left out, in which case it is taken to be 1 (the behaviour differs due
+to the special POSIX rules described above).
+.It Fl u Ar file
+.Ar file Ns 's
+mode has the setuid bit set.
+.It Fl w Ar file
+.Ar file
+exists and is writable.
+.It Fl x Ar file
+.Ar file
+exists and is executable.
+.It Ar file1 Fl nt Ar file2
+.Ar file1
+is newer than
+.Ar file2
+or
+.Ar file1
+exists and
+.Ar file2
+does not.
+.It Ar file1 Fl ot Ar file2
+.Ar file1
+is older than
+.Ar file2
+or
+.Ar file2
+exists and
+.Ar file1
+does not.
+.It Ar file1 Fl ef Ar file2
+.Ar file1
+is the same file as
+.Ar file2 .
+.It Ar string
+.Ar string
+has non-zero length.
+.It Fl n Ar string
+.Ar string
+is not empty.
+.It Fl z Ar string
+.Ar string
+is empty.
+.It Ar string No = Ar string
+Strings are equal.
+.It Ar string No == Ar string
+Strings are equal.
+.It Ar string No != Ar string
+Strings are not equal.
+.It Ar number Fl eq Ar number
+Numbers compare equal.
+.It Ar number Fl ne Ar number
+Numbers compare not equal.
+.It Ar number Fl ge Ar number
+Numbers compare greater than or equal.
+.It Ar number Fl gt Ar number
+Numbers compare greater than.
+.It Ar number Fl le Ar number
+Numbers compare less than or equal.
+.It Ar number Fl \< Ar number
+Numbers compare less than.
+.El
+.Pp
+The above basic expressions, in which unary operators have precedence over
+binary operators, may be combined with the following operators (listed in
+increasing order of precedence):
+.Bd -literal -offset indent
+expr -o expr Logical OR.
+expr -a expr Logical AND.
+! expr Logical NOT.
+( expr ) Grouping.
+.Ed
+.Pp
+On operating systems not supporting
+.Pa /dev/fd/ Ns Ar n
+devices (where
+.Ar n
+is a file descriptor number), the
+.Ic test
+command will attempt to fake it for all tests that operate on files (except the
+.Fl e
+test).
+For example,
+[ -w /dev/fd/2 ] tests if file descriptor 2 is writable.
+.Pp
+Note that some special rules are applied (courtesy of POSIX)
+if the number of
+arguments to
+.Ic test
+or
+.Ic \&[ ... \&]
+is less than five: if leading
+.Ql \&!
+arguments can be stripped such that only one argument remains then a string
+length test is performed (again, even if the argument is a unary operator); if
+leading
+.Ql \&!
+arguments can be stripped such that three arguments remain and the second
+argument is a binary operator, then the binary operation is performed (even
+if the first argument is a unary operator, including an unstripped
+.Ql \&! ) .
+.Pp
+.Sy Note :
+A common mistake is to use
+.Dq if \&[ $foo = bar \&]
+which fails if parameter
+.Dq foo
+is
+.Dv NULL
+or unset, if it has embedded spaces (i.e.\&
+.Ev IFS
+characters), or if it is a unary operator like
+.Sq \&!
+or
+.Sq Fl n .
+Use tests like
+.Dq if \&[ \&"X$foo\&" = Xbar \&]
+instead.
+.Pp
+.It Xo
+.Ic time
+.Op Fl p
+.Op Ar pipeline
+.Xc
+If a
+.Ar pipeline
+is given, the times used to execute the pipeline are reported.
+If no pipeline
+is given, then the user and system time used by the shell itself, and all the
+commands it has run since it was started, are reported.
+The times reported are the real time (elapsed time from start to finish),
+the user CPU time (time spent running in user mode), and the system CPU time
+(time spent running in kernel mode).
+Times are reported to standard error; the format of the output is:
+.Pp
+.Dl "0m0.00s real 0m0.00s user 0m0.00s system"
+.Pp
+If the
+.Fl p
+option is given the output is slightly longer:
+.Bd -literal -offset indent
+real 0.00
+user 0.00
+sys 0.00
+.Ed
+.Pp
+It is an error to specify the
+.Fl p
+option unless
+.Ar pipeline
+is a simple command.
+.Pp
+Simple redirections of standard error do not affect the output of the
+.Ic time
+command:
+.Pp
+.Dl $ time sleep 1 2\*(Gt afile
+.Dl $ { time sleep 1; } 2\*(Gt afile
+.Pp
+Times for the first command do not go to
+.Dq afile ,
+but those of the second command do.
+.Pp
+.It Ic times
+Print the accumulated user and system times used both by the shell
+and by processes that the shell started which have exited.
+The format of the output is:
+.Bd -literal -offset indent
+0m0.00s 0m0.00s
+0m0.00s 0m0.00s
+.Ed
+.Pp
+.It Ic trap Op Ar handler signal ...
+Sets a trap handler that is to be executed when any of the specified signals are
+received.
+.Ar handler
+is either a
+.Dv NULL
+string, indicating the signals are to be ignored, a minus sign
+.Pq Sq - ,
+indicating that the default action is to be taken for the signals (see
+.Xr signal 3 ) ,
+or a string containing shell commands to be evaluated and executed at the first
+opportunity (i.e. when the current command completes, or before printing the
+next
+.Ev PS1
+prompt) after receipt of one of the signals.
+.Ar signal
+is the name of a signal (e.g.\&
+.Dv PIPE
+or
+.Dv ALRM )
+or the number of the signal (see the
+.Ic kill -l
+command above).
+.Pp
+There are two special signals:
+.Dv EXIT
+(also known as 0), which is executed when the shell is about to exit, and
+.Dv ERR ,
+which is executed after an error occurs (an error is something that would cause
+the shell to exit if the
+.Fl e
+or
+.Ic errexit
+option were set \- see the
+.Ic set
+command above).
+.Dv EXIT
+handlers are executed in the environment of the last executed command.
+Note
+that for non-interactive shells, the trap handler cannot be changed for signals
+that were ignored when the shell started.
+.Pp
+With no arguments,
+.Ic trap
+lists, as a series of
+.Ic trap
+commands, the current state of the traps that have been set since the shell
+started.
+Note that the output of
+.Ic trap
+cannot be usefully piped to another process (an artifact of the fact that
+traps are cleared when subprocesses are created).
+.Pp
+The original Korn shell's
+.Dv DEBUG
+trap and the handling of
+.Dv ERR
+and
+.Dv EXIT
+traps in functions are not yet implemented.
+.Pp
+.It Ic true
+A command that exits with a zero value.
+.Pp
+.It Xo
+.Ic typeset
+.Oo
+.Op Ic +-lprtUux
+.Op Fl L Ns Op Ar n
+.Op Fl R Ns Op Ar n
+.Op Fl Z Ns Op Ar n
+.Op Fl i Ns Op Ar n
+.No \*(Ba Fl f Op Fl tux
+.Oc
+.Oo
+.Ar name
+.Op Ns = Ns Ar value
+.Ar ...
+.Oc
+.Xc
+Display or set parameter attributes.
+With no
+.Ar name
+arguments, parameter attributes are displayed; if no options are used, the
+current attributes of all parameters are printed as
+.Ic typeset
+commands; if an option is given (or
+.Ql -
+with no option letter), all parameters and their values with the specified
+attributes are printed; if options are introduced with
+.Ql + ,
+parameter values are not printed.
+.Pp
+If
+.Ar name
+arguments are given, the attributes of the named parameters are set
+.Pq Ic -
+or cleared
+.Pq Ic + .
+Values for parameters may optionally be specified.
+If
+.Ic typeset
+is used inside a function, any newly created parameters are local to the
+function.
+.Pp
+When
+.Fl f
+is used,
+.Ic typeset
+operates on the attributes of functions.
+As with parameters, if no
+.Ar name
+arguments are given,
+functions are listed with their values (i.e. definitions) unless
+options are introduced with
+.Ql + ,
+in which case only the function names are reported.
+.Bl -tag -width Ds
+.It Fl f
+Function mode.
+Display or set functions and their attributes, instead of parameters.
+.It Fl i Ns Op Ar n
+Integer attribute.
+.Ar n
+specifies the base to use when displaying the integer (if not specified, the
+base given in the first assignment is used).
+Parameters with this attribute may
+be assigned values containing arithmetic expressions.
+.It Fl L Ns Op Ar n
+Left justify attribute.
+.Ar n
+specifies the field width.
+If
+.Ar n
+is not specified, the current width of a parameter (or the width of its first
+assigned value) is used.
+Leading whitespace (and zeros, if used with the
+.Fl Z
+option) is stripped.
+If necessary, values are either truncated or space padded
+to fit the field width.
+.It Fl l
+Lower case attribute.
+All upper case characters in values are converted to lower case.
+(In the original Korn shell, this parameter meant
+.Dq long integer
+when used with the
+.Fl i
+option.)
+.It Fl p
+Print complete
+.Ic typeset
+commands that can be used to re-create the attributes (but not the values) of
+parameters.
+This is the default action (option exists for ksh93 compatibility).
+.It Fl R Ns Op Ar n
+Right justify attribute.
+.Ar n
+specifies the field width.
+If
+.Ar n
+is not specified, the current width of a parameter (or the width of its first
+assigned value) is used.
+Trailing whitespace is stripped.
+If necessary, values are either stripped of leading characters or space
+padded to make them fit the field width.
+.It Fl r
+Read-only attribute.
+Parameters with this attribute may not be assigned to or unset.
+Once this attribute is set, it cannot be turned off.
+.It Fl t
+Tag attribute.
+Has no meaning to the shell; provided for application use.
+.Pp
+For functions,
+.Fl t
+is the trace attribute.
+When functions with the trace attribute are executed, the
+.Ic xtrace
+.Pq Fl x
+shell option is temporarily turned on.
+.It Fl U
+Unsigned integer attribute.
+Integers are printed as unsigned values (only
+useful when combined with the
+.Fl i
+option).
+This option is not in the original Korn shell.
+.It Fl u
+Upper case attribute.
+All lower case characters in values are converted to upper case.
+(In the original Korn shell, this parameter meant
+.Dq unsigned integer
+when used with the
+.Fl i
+option, which meant upper case letters would never be used for bases greater
+than 10.
+See the
+.Fl U
+option.)
+.Pp
+For functions,
+.Fl u
+is the undefined attribute.
+See
+.Sx Functions
+above for the implications of this.
+.It Fl x
+Export attribute.
+Parameters (or functions) are placed in the environment of
+any executed commands.
+Exported functions are not yet implemented.
+.It Fl Z Ns Op Ar n
+Zero fill attribute.
+If not combined with
+.Fl L ,
+this is the same as
+.Fl R ,
+except zero padding is used instead of space padding.
+.El
+.Pp
+.It Xo
+.Ic ulimit
+.Op Fl acdfHlmnpSst Op Ar value
+.Ar ...
+.Xc
+Display or set process limits.
+If no options are used, the file size limit
+.Pq Fl f
+is assumed.
+.Ar value ,
+if specified, may be either an arithmetic expression starting with a
+number or the word
+.Dq unlimited .
+The limits affect the shell and any processes created by the shell after a
+limit is imposed; limits may not be increased once they are set.
+.Bl -tag -width 5n
+.It Fl a
+Display all limits; unless
+.Fl H
+is used, soft limits are displayed.
+.It Fl c Ar n
+Impose a size limit of
+.Ar n
+blocks on the size of core dumps.
+.It Fl d Ar n
+Impose a size limit of
+.Ar n
+kilobytes on the size of the data area.
+.It Fl f Ar n
+Impose a size limit of
+.Ar n
+blocks on files written by the shell and its child processes (files of any
+size may be read).
+.It Fl H
+Set the hard limit only (the default is to set both hard and soft limits).
+.It Fl l Ar n
+Impose a limit of
+.Ar n
+kilobytes on the amount of locked (wired) physical memory.
+.It Fl m Ar n
+Impose a limit of
+.Ar n
+kilobytes on the amount of physical memory used.
+.It Fl n Ar n
+Impose a limit of
+.Ar n
+file descriptors that can be open at once.
+.It Fl p Ar n
+Impose a limit of
+.Ar n
+processes that can be run by the user at any one time.
+.It Fl S
+Set the soft limit only (the default is to set both hard and soft limits).
+.It Fl s Ar n
+Impose a size limit of
+.Ar n
+kilobytes on the size of the stack area.
+.It Fl t Ar n
+Impose a time limit of
+.Ar n
+CPU seconds spent in user mode to be used by each process.
+.\".It Fl v Ar n
+.\"Impose a limit of
+.\"Ar n
+.\"kilobytes on the amount of virtual memory used.
+.El
+.Pp
+As far as
+.Ic ulimit
+is concerned, a block is 512 bytes.
+.Pp
+.It Xo
+.Ic umask
+.Op Fl S
+.Op Ar mask
+.Xc
+Display or set the file permission creation mask, or umask (see
+.Xr umask 2 ) .
+If the
+.Fl S
+option is used, the mask displayed or set is symbolic; otherwise, it is an
+octal number.
+.Pp
+Symbolic masks are like those used by
+.Xr chmod 1 .
+When used, they describe what permissions may be made available (as opposed to
+octal masks in which a set bit means the corresponding bit is to be cleared).
+For example,
+.Dq ug=rwx,o=
+sets the mask so files will not be readable, writable, or executable by
+.Dq others ,
+and is equivalent (on most systems) to the octal mask
+.Dq 007 .
+.Pp
+.It Xo
+.Ic unalias
+.Op Fl adt
+.Op Ar name ...
+.Xc
+The aliases for the given names are removed.
+If the
+.Fl a
+option is used, all aliases are removed.
+If the
+.Fl t
+or
+.Fl d
+options are used, the indicated operations are carried out on tracked or
+directory aliases, respectively.
+.Pp
+.It Xo
+.Ic unset
+.Op Fl fv
+.Ar parameter ...
+.Xc
+Unset the named parameters
+.Po
+.Fl v ,
+the default
+.Pc
+or functions
+.Pq Fl f .
+The exit status is non-zero if any of the parameters have the read-only
+attribute set, zero otherwise.
+.Pp
+.It Ic wait Op Ar job ...
+Wait for the specified job(s) to finish.
+The exit status of
+.Ic wait
+is that of the last specified job; if the last job is killed by a signal, the
+exit status is 128 + the number of the signal (see
+.Ic kill -l Ar exit-status
+above); if the last specified job can't be found (because it never existed, or
+had already finished), the exit status of
+.Ic wait
+is 127.
+See
+.Sx Job control
+below for the format of
+.Ar job .
+.Ic wait
+will return if a signal for which a trap has been set is received, or if a
+.Dv SIGHUP ,
+.Dv SIGINT ,
+or
+.Dv SIGQUIT
+signal is received.
+.Pp
+If no jobs are specified,
+.Ic wait
+waits for all currently running jobs (if any) to finish and exits with a zero
+status.
+If job monitoring is enabled, the completion status of jobs is printed
+(this is not the case when jobs are explicitly specified).
+.Pp
+.It Xo
+.Ic whence
+.Op Fl pv
+.Op Ar name ...
+.Xc
+For each
+.Ar name ,
+the type of command is listed (reserved word, built-in, alias,
+function, tracked alias, or executable).
+If the
+.Fl p
+option is used, a path search is performed even if
+.Ar name
+is a reserved word, alias, etc.
+Without the
+.Fl v
+option,
+.Ic whence
+is similar to
+.Ic command Fl v
+except that
+.Ic whence
+will find reserved words and won't print aliases as alias commands.
+With the
+.Fl v
+option,
+.Ic whence
+is the same as
+.Ic command Fl V .
+Note that for
+.Ic whence ,
+the
+.Fl p
+option does not affect the search path used, as it does for
+.Ic command .
+If the type of one or more of the names could not be determined, the exit
+status is non-zero.
+.El
+.Ss Job control
+Job control refers to the shell's ability to monitor and control jobs, which
+are processes or groups of processes created for commands or pipelines.
+At a minimum, the shell keeps track of the status of the background (i.e.\&
+asynchronous) jobs that currently exist; this information can be displayed
+using the
+.Ic jobs
+commands.
+If job control is fully enabled (using
+.Ic set -m
+or
+.Ic set -o monitor ) ,
+as it is for interactive shells, the processes of a job are placed in their
+own process group.
+Foreground jobs can be stopped by typing the suspend
+character from the terminal (normally ^Z), jobs can be restarted in either the
+foreground or background using the
+.Ic fg
+and
+.Ic bg
+commands, and the state of the terminal is saved or restored when a foreground
+job is stopped or restarted, respectively.
+.Pp
+Note that only commands that create processes (e.g. asynchronous commands,
+subshell commands, and non-built-in, non-function commands) can be stopped;
+commands like
+.Ic read
+cannot be.
+.Pp
+When a job is created, it is assigned a job number.
+For interactive shells, this number is printed inside
+.Dq [..] ,
+followed by the process IDs of the processes in the job when an asynchronous
+command is run.
+A job may be referred to in the
+.Ic bg ,
+.Ic fg ,
+.Ic jobs ,
+.Ic kill ,
+and
+.Ic wait
+commands either by the process ID of the last process in the command pipeline
+(as stored in the
+.Ic $!\&
+parameter) or by prefixing the job number with a percent
+sign
+.Pq Sq % .
+Other percent sequences can also be used to refer to jobs:
+.Bl -tag -width "%+ | %% | %XX"
+.It %+ \*(Ba %% \*(Ba %
+The most recently stopped job, or, if there are no stopped jobs, the oldest
+running job.
+.It %-
+The job that would be the
+.Ic %+
+job if the latter did not exist.
+.It % Ns Ar n
+The job with job number
+.Ar n .
+.It %? Ns Ar string
+The job with its command containing the string
+.Ar string
+(an error occurs if multiple jobs are matched).
+.It % Ns Ar string
+The job with its command starting with the string
+.Ar string
+(an error occurs if multiple jobs are matched).
+.El
+.Pp
+When a job changes state (e.g. a background job finishes or foreground job is
+stopped), the shell prints the following status information:
+.Pp
+.D1 [ Ns Ar number ] Ar flag status command
+.Pp
+where...
+.Bl -tag -width "command"
+.It Ar number
+is the job number of the job;
+.It Ar flag
+is the
+.Ql +
+or
+.Ql -
+character if the job is the
+.Ic %+
+or
+.Ic %-
+job, respectively, or space if it is neither;
+.It Ar status
+indicates the current state of the job and can be:
+.Bl -tag -width "RunningXX"
+.It Done Op Ar number
+The job exited.
+.Ar number
+is the exit status of the job, which is omitted if the status is zero.
+.It Running
+The job has neither stopped nor exited (note that running does not necessarily
+mean consuming CPU time \-
+the process could be blocked waiting for some event).
+.It Stopped Op Ar signal
+The job was stopped by the indicated
+.Ar signal
+(if no signal is given, the job was stopped by
+.Dv SIGTSTP ) .
+.It Ar signal-description Op Dq core dumped
+The job was killed by a signal (e.g. memory fault, hangup); use
+.Ic kill -l
+for a list of signal descriptions.
+The
+.Dq core dumped
+message indicates the process created a core file.
+.El
+.It Ar command
+is the command that created the process.
+If there are multiple processes in
+the job, each process will have a line showing its
+.Ar command
+and possibly its
+.Ar status ,
+if it is different from the status of the previous process.
+.El
+.Pp
+When an attempt is made to exit the shell while there are jobs in the stopped
+state, the shell warns the user that there are stopped jobs and does not exit.
+If another attempt is immediately made to exit the shell, the stopped jobs are
+sent a
+.Dv SIGHUP
+signal and the shell exits.
+Similarly, if the
+.Ic nohup
+option is not set and there are running jobs when an attempt is made to exit
+a login shell, the shell warns the user and does not exit.
+If another attempt
+is immediately made to exit the shell, the running jobs are sent a
+.Dv SIGHUP
+signal and the shell exits.
+.Ss Interactive input line editing
+The shell supports three modes of reading command lines from a
+.Xr tty 4
+in an interactive session, controlled by the
+.Ic emacs ,
+.Ic gmacs ,
+and
+.Ic vi
+options (at most one of these can be set at once).
+The default is
+.Ic emacs .
+Editing modes can be set explicitly using the
+.Ic set
+built-in, or implicitly via the
+.Ev EDITOR
+and
+.Ev VISUAL
+environment variables.
+If none of these options are enabled,
+the shell simply reads lines using the normal
+.Xr tty 4
+driver.
+If the
+.Ic emacs
+or
+.Ic gmacs
+option is set, the shell allows emacs-like editing of the command; similarly,
+if the
+.Ic vi
+option is set, the shell allows vi-like editing of the command.
+These modes are described in detail in the following sections.
+.Pp
+In these editing modes, if a line is longer than the screen width (see the
+.Ev COLUMNS
+parameter),
+a
+.Ql \*(Gt ,
+.Ql + ,
+or
+.Ql \*(Lt
+character is displayed in the last column indicating that there are more
+characters after, before and after, or before the current position,
+respectively.
+The line is scrolled horizontally as necessary.
+.Ss Emacs editing mode
+When the
+.Ic emacs
+option is set, interactive input line editing is enabled.
+Warning: This mode is
+slightly different from the emacs mode in the original Korn shell.
+In this mode, various editing commands
+(typically bound to one or more control characters) cause immediate actions
+without waiting for a newline.
+Several editing commands are bound to particular
+control characters when the shell is invoked; these bindings can be changed
+using the
+.Ic bind
+command.
+.Pp
+The following is a list of available editing commands.
+Each description starts with the name of the command,
+suffixed with a colon;
+an
+.Op Ar n
+(if the command can be prefixed with a count); and any keys the command is
+bound to by default, written using caret notation
+e.g. the ASCII ESC character is written as ^[.
+^[A-Z] sequences are not case sensitive.
+A count prefix for a command is entered using the sequence
+.Pf ^[ Ns Ar n ,
+where
+.Ar n
+is a sequence of 1 or more digits.
+Unless otherwise specified, if a count is
+omitted, it defaults to 1.
+.Pp
+Note that editing command names are used only with the
+.Ic bind
+command.
+Furthermore, many editing commands are useful only on terminals with
+a visible cursor.
+The default bindings were chosen to resemble corresponding
+Emacs key bindings.
+The user's
+.Xr tty 4
+characters (e.g.\&
+.Dv ERASE )
+are bound to
+reasonable substitutes and override the default bindings.
+.Bl -tag -width Ds
+.It abort: ^C, ^G
+Useful as a response to a request for a
+.Ic search-history
+pattern in order to abort the search.
+.It auto-insert: Op Ar n
+Simply causes the character to appear as literal input.
+Most ordinary characters are bound to this.
+.It Xo backward-char:
+.Op Ar n
+.No ^B , ^XD
+.Xc
+Moves the cursor backward
+.Ar n
+characters.
+.It Xo backward-word:
+.Op Ar n
+.No ^[b
+.Xc
+Moves the cursor backward to the beginning of the word; words consist of
+alphanumerics, underscore
+.Pq Sq _ ,
+and dollar sign
+.Pq Sq $
+characters.
+.It beginning-of-history: ^[\*(Lt
+Moves to the beginning of the history.
+.It beginning-of-line: ^A
+Moves the cursor to the beginning of the edited input line.
+.It Xo capitalize-word:
+.Op Ar n
+.No ^[C , ^[c
+.Xc
+Uppercase the first character in the next
+.Ar n
+words, leaving the cursor past the end of the last word.
+.It comment: ^[#
+If the current line does not begin with a comment character, one is added at
+the beginning of the line and the line is entered (as if return had been
+pressed); otherwise, the existing comment characters are removed and the cursor
+is placed at the beginning of the line.
+.It complete: ^[^[
+Automatically completes as much as is unique of the command name or the file
+name containing the cursor.
+If the entire remaining command or file name is
+unique, a space is printed after its completion, unless it is a directory name
+in which case
+.Ql /
+is appended.
+If there is no command or file name with the current partial word
+as its prefix, a bell character is output (usually causing a beep to be
+sounded).
+.It complete-command: ^X^[
+Automatically completes as much as is unique of the command name having the
+partial word up to the cursor as its prefix, as in the
+.Ic complete
+command above.
+.It complete-file: ^[^X
+Automatically completes as much as is unique of the file name having the
+partial word up to the cursor as its prefix, as in the
+.Ic complete
+command described above.
+.It complete-list: ^I, ^[=
+Complete as much as is possible of the current word,
+and list the possible completions for it.
+If only one completion is possible,
+match as in the
+.Ic complete
+command above.
+.It Xo delete-char-backward:
+.Op Ar n
+.No ERASE , ^? , ^H
+.Xc
+Deletes
+.Ar n
+characters before the cursor.
+.It delete-char-forward: Op Ar n
+Deletes
+.Ar n
+characters after the cursor.
+.It Xo delete-word-backward:
+.Op Ar n
+.No ERASE , ^[^? , ^[^H , ^[h
+.Xc
+Deletes
+.Ar n
+words before the cursor.
+.It Xo delete-word-forward:
+.Op Ar n
+.No ^[d
+.Xc
+Deletes characters after the cursor up to the end of
+.Ar n
+words.
+.It Xo down-history:
+.Op Ar n
+.No ^N , ^XB
+.Xc
+Scrolls the history buffer forward
+.Ar n
+lines (later).
+Each input line originally starts just after the last entry
+in the history buffer, so
+.Ic down-history
+is not useful until either
+.Ic search-history
+or
+.Ic up-history
+has been performed.
+.It Xo downcase-word:
+.Op Ar n
+.No ^[L , ^[l
+.Xc
+Lowercases the next
+.Ar n
+words.
+.It end-of-history: ^[\*(Gt
+Moves to the end of the history.
+.It end-of-line: ^E
+Moves the cursor to the end of the input line.
+.It eot: ^_
+Acts as an end-of-file; this is useful because edit-mode input disables
+normal terminal input canonicalization.
+.It Xo eot-or-delete:
+.Op Ar n
+.No ^D
+.Xc
+Acts as
+.Ic eot
+if alone on a line; otherwise acts as
+.Ic delete-char-forward .
+.It error:
+Error (ring the bell).
+.It exchange-point-and-mark: ^X^X
+Places the cursor where the mark is and sets the mark to where the cursor was.
+.It expand-file: ^[*
+Appends a
+.Ql *
+to the current word and replaces the word with the result of performing file
+globbing on the word.
+If no files match the pattern, the bell is rung.
+.It Xo forward-char:
+.Op Ar n
+.No ^F , ^XC
+.Xc
+Moves the cursor forward
+.Ar n
+characters.
+.It Xo forward-word:
+.Op Ar n
+.No ^[f
+.Xc
+Moves the cursor forward to the end of the
+.Ar n Ns th
+word.
+.It Xo goto-history:
+.Op Ar n
+.No ^[g
+.Xc
+Goes to history number
+.Ar n .
+.It kill-line: KILL
+Deletes the entire input line.
+.It kill-region: ^W
+Deletes the input between the cursor and the mark.
+.It Xo kill-to-eol:
+.Op Ar n
+.No ^K
+.Xc
+Deletes the input from the cursor to the end of the line if
+.Ar n
+is not specified; otherwise deletes characters between the cursor and column
+.Ar n .
+.It list: ^[?
+Prints a sorted, columnated list of command names or file names (if any) that
+can complete the partial word containing the cursor.
+Directory names have
+.Ql /
+appended to them.
+.It list-command: ^X?
+Prints a sorted, columnated list of command names (if any) that can complete
+the partial word containing the cursor.
+.It list-file: ^X^Y
+Prints a sorted, columnated list of file names (if any) that can complete the
+partial word containing the cursor.
+File type indicators are appended as described under
+.Ic list
+above.
+.It newline: ^J , ^M
+Causes the current input line to be processed by the shell.
+The current cursor position may be anywhere on the line.
+.It newline-and-next: ^O
+Causes the current input line to be processed by the shell, and the next line
+from history becomes the current line.
+This is only useful after an
+.Ic up-history
+or
+.Ic search-history .
+.It no-op: QUIT
+This does nothing.
+.It Xo prev-hist-word:
+.Op Ar n
+.No ^[. , ^[_
+.Xc
+The last
+.Pq Ar n Ns th
+word of the previous command is inserted at the cursor.
+.It quote: ^^
+The following character is taken literally rather than as an editing command.
+.It redraw: ^L
+Reprints the prompt string and the current input line.
+.It Xo search-character-backward:
+.Op Ar n
+.No ^[^]
+.Xc
+Search backward in the current line for the
+.Ar n Ns th
+occurrence of the next character typed.
+.It Xo search-character-forward:
+.Op Ar n
+.No ^]
+.Xc
+Search forward in the current line for the
+.Ar n Ns th
+occurrence of the next character typed.
+.It search-history: ^R
+Enter incremental search mode.
+The internal history list is searched
+backwards for commands matching the input.
+An initial
+.Ql ^
+in the search string anchors the search.
+The abort key will leave search mode.
+Other commands will be executed after leaving search mode.
+Successive
+.Ic search-history
+commands continue searching backward to the next previous occurrence of the
+pattern.
+The history buffer retains only a finite number of lines; the oldest
+are discarded as necessary.
+.It set-mark-command: ^[ Ns Aq space
+Set the mark at the cursor position.
+.It stuff:
+On systems supporting it, pushes the bound character back onto the terminal
+input where it may receive special processing by the terminal handler.
+This is useful for the BRL ^T mini-systat feature, for example.
+.It stuff-reset:
+Acts like
+.Ic stuff ,
+then aborts input the same as an interrupt.
+.It transpose-chars: ^T
+If at the end of line, or if the
+.Ic gmacs
+option is set, this exchanges the two previous characters; otherwise, it
+exchanges the previous and current characters and moves the cursor one
+character to the right.
+.It Xo up-history:
+.Op Ar n
+.No ^P , ^XA
+.Xc
+Scrolls the history buffer backward
+.Ar n
+lines (earlier).
+.It Xo upcase-word:
+.Op Ar n
+.No ^[U , ^[u
+.Xc
+Uppercase the next
+.Ar n
+words.
+.It quote: ^V
+Synonym for ^^.
+.It yank: ^Y
+Inserts the most recently killed text string at the current cursor position.
+.It yank-pop: ^[y
+Immediately after a
+.Ic yank ,
+replaces the inserted text string with the next previously killed text string.
+.El
+.Ss Vi editing mode
+The vi command-line editor in
+.Nm
+has basically the same commands as the
+.Xr vi 1
+editor with the following exceptions:
+.Bl -bullet
+.It
+You start out in insert mode.
+.It
+There are file name and command completion commands:
+=, \e, *, ^X, ^E, ^F, and, optionally,
+.Aq tab
+and
+.Aq esc .
+.It
+The
+.Ic _
+command is different (in
+.Nm
+it is the last argument command; in
+.Xr vi 1
+it goes to the start of the current line).
+.It
+The
+.Ic /
+and
+.Ic G
+commands move in the opposite direction to the
+.Ic j
+command.
+.It
+Commands which don't make sense in a single line editor are not available
+(e.g. screen movement commands and
+.Xr ex 1 Ns -style
+colon
+.Pq Ic \&:
+commands).
+.El
+.Pp
+Note that the ^X stands for control-X; also
+.Aq esc ,
+.Aq space ,
+and
+.Aq tab
+are used for escape, space, and tab, respectively (no kidding).
+.Pp
+Like
+.Xr vi 1 ,
+there are two modes:
+.Dq insert
+mode and
+.Dq command
+mode.
+In insert mode, most characters are simply put in the buffer at the
+current cursor position as they are typed; however, some characters are
+treated specially.
+In particular, the following characters are taken from current
+.Xr tty 4
+settings
+(see
+.Xr stty 1 )
+and have their usual meaning (normal values are in parentheses): kill (^U),
+erase (^?), werase (^W), eof (^D), intr (^C), and quit (^\e).
+In addition to
+the above, the following characters are also treated specially in insert mode:
+.Bl -tag -width 10n
+.It ^E
+Command and file name enumeration (see below).
+.It ^F
+Command and file name completion (see below).
+If used twice in a row, the
+list of possible completions is displayed; if used a third time, the completion
+is undone.
+.It ^H
+Erases previous character.
+.It ^J \*(Ba ^M
+End of line.
+The current line is read, parsed, and executed by the shell.
+.It ^V
+Literal next.
+The next character typed is not treated specially (can be used
+to insert the characters being described here).
+.It ^X
+Command and file name expansion (see below).
+.It Aq esc
+Puts the editor in command mode (see below).
+.It Aq tab
+Optional file name and command completion (see
+.Ic ^F
+above), enabled with
+.Ic set -o vi-tabcomplete .
+.El
+.Pp
+In command mode, each character is interpreted as a command.
+Characters that
+don't correspond to commands, are illegal combinations of commands, or are
+commands that can't be carried out, all cause beeps.
+In the following command descriptions, an
+.Op Ar n
+indicates the command may be prefixed by a number (e.g.\&
+.Ic 10l
+moves right 10 characters); if no number prefix is used,
+.Ar n
+is assumed to be 1 unless otherwise specified.
+The term
+.Dq current position
+refers to the position between the cursor and the character preceding the
+cursor.
+A
+.Dq word
+is a sequence of letters, digits, and underscore characters or a sequence of
+non-letter, non-digit, non-underscore, and non-whitespace characters (e.g.\&
+.Dq ab2*&^
+contains two words) and a
+.Dq big-word
+is a sequence of non-whitespace characters.
+.Pp
+Special
+.Nm
+vi commands:
+.Pp
+The following commands are not in, or are different from, the normal vi file
+editor:
+.Bl -tag -width 10n
+.It Xo
+.Oo Ar n Oc Ns _
+.Xc
+Insert a space followed by the
+.Ar n Ns th
+big-word from the last command in the history at the current position and enter
+insert mode; if
+.Ar n
+is not specified, the last word is inserted.
+.It #
+Insert the comment character
+.Pq Sq #
+at the start of the current line and return the line to the shell (equivalent
+to
+.Ic I#^J ) .
+.It Xo
+.Oo Ar n Oc Ns g
+.Xc
+Like
+.Ic G ,
+except if
+.Ar n
+is not specified, it goes to the most recent remembered line.
+.It Xo
+.Oo Ar n Oc Ns v
+.Xc
+Edit line
+.Ar n
+using the
+.Xr vi 1
+editor; if
+.Ar n
+is not specified, the current line is edited.
+The actual command executed is
+.Ic fc -e ${VISUAL:-${EDITOR:-vi}} Ar n .
+.It * and ^X
+Command or file name expansion is applied to the current big-word (with an
+appended
+.Ql *
+if the word contains no file globbing characters) \- the big-word is replaced
+with the resulting words.
+If the current big-word is the first on the line
+or follows one of the characters
+.Ql \&; ,
+.Ql | ,
+.Ql & ,
+.Ql \&( ,
+or
+.Ql \&) ,
+and does not contain a slash
+.Pq Sq / ,
+then command expansion is done; otherwise file name expansion is done.
+Command expansion will match the big-word against all aliases, functions, and
+built-in commands as well as any executable files found by searching the
+directories in the
+.Ev PATH
+parameter.
+File name expansion matches the big-word against the files in the
+current directory.
+After expansion, the cursor is placed just past the last
+word and the editor is in insert mode.
+.It Xo
+.Oo Ar n Oc Ns \e ,
+.Oo Ar n Oc Ns ^F ,
+.Oo Ar n Oc Ns Aq tab ,
+.No and
+.Oo Ar n Oc Ns Aq esc
+.Xc
+Command/file name completion.
+Replace the current big-word with the
+longest unique match obtained after performing command and file name expansion.
+.Aq tab
+is only recognized if the
+.Ic vi-tabcomplete
+option is set, while
+.Aq esc
+is only recognized if the
+.Ic vi-esccomplete
+option is set (see
+.Ic set -o ) .
+If
+.Ar n
+is specified, the
+.Ar n Ns th
+possible completion is selected (as reported by the command/file name
+enumeration command).
+.It = and ^E
+Command/file name enumeration.
+List all the commands or files that match the current big-word.
+.It @ Ns Ar c
+Macro expansion.
+Execute the commands found in the alias
+.Ar c .
+.El
+.Pp
+Intra-line movement commands:
+.Bl -tag -width Ds
+.It Xo
+.Oo Ar n Oc Ns h and
+.Oo Ar n Oc Ns ^H
+.Xc
+Move left
+.Ar n
+characters.
+.It Xo
+.Oo Ar n Oc Ns l and
+.Oo Ar n Oc Ns Aq space
+.Xc
+Move right
+.Ar n
+characters.
+.It 0
+Move to column 0.
+.It ^
+Move to the first non-whitespace character.
+.It Xo
+.Oo Ar n Oc Ns \*(Ba
+.Xc
+Move to column
+.Ar n .
+.It $
+Move to the last character.
+.It Xo
+.Oo Ar n Oc Ns b
+.Xc
+Move back
+.Ar n
+words.
+.It Xo
+.Oo Ar n Oc Ns B
+.Xc
+Move back
+.Ar n
+big-words.
+.It Xo
+.Oo Ar n Oc Ns e
+.Xc
+Move forward to the end of the word,
+.Ar n
+times.
+.It Xo
+.Oo Ar n Oc Ns E
+.Xc
+Move forward to the end of the big-word,
+.Ar n
+times.
+.It Xo
+.Oo Ar n Oc Ns w
+.Xc
+Move forward
+.Ar n
+words.
+.It Xo
+.Oo Ar n Oc Ns W
+.Xc
+Move forward
+.Ar n
+big-words.
+.It %
+Find match.
+The editor looks forward for the nearest parenthesis, bracket, or
+brace and then moves the cursor to the matching parenthesis, bracket, or brace.
+.It Xo
+.Oo Ar n Oc Ns f Ns Ar c
+.Xc
+Move forward to the
+.Ar n Ns th
+occurrence of the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns F Ns Ar c
+.Xc
+Move backward to the
+.Ar n Ns th
+occurrence of the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns t Ns Ar c
+.Xc
+Move forward to just before the
+.Ar n Ns th
+occurrence of the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns T Ns Ar c
+.Xc
+Move backward to just before the
+.Ar n Ns th
+occurrence of the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns \&;
+.Xc
+Repeats the last
+.Ic f , F , t ,
+or
+.Ic T
+command.
+.It Xo
+.Oo Ar n Oc Ns \&,
+.Xc
+Repeats the last
+.Ic f , F , t ,
+or
+.Ic T
+command, but moves in the opposite direction.
+.El
+.Pp
+Inter-line movement commands:
+.Bl -tag -width Ds
+.It Xo
+.Oo Ar n Oc Ns j ,
+.Oo Ar n Oc Ns + ,
+.No and
+.Oo Ar n Oc Ns ^N
+.Xc
+Move to the
+.Ar n Ns th
+next line in the history.
+.It Xo
+.Oo Ar n Oc Ns k ,
+.Oo Ar n Oc Ns - ,
+.No and
+.Oo Ar n Oc Ns ^P
+.Xc
+Move to the
+.Ar n Ns th
+previous line in the history.
+.It Xo
+.Oo Ar n Oc Ns G
+.Xc
+Move to line
+.Ar n
+in the history; if
+.Ar n
+is not specified, the number of the first remembered line is used.
+.It Xo
+.Oo Ar n Oc Ns g
+.Xc
+Like
+.Ic G ,
+except if
+.Ar n
+is not specified, it goes to the most recent remembered line.
+.It Xo
+.Oo Ar n Oc Ns / Ns Ar string
+.Xc
+Search backward through the history for the
+.Ar n Ns th
+line containing
+.Ar string ;
+if
+.Ar string
+starts with
+.Ql ^ ,
+the remainder of the string must appear at the start of the history line for
+it to match.
+.It Xo
+.Oo Ar n Oc Ns \&? Ns Ar string
+.Xc
+Same as
+.Ic / ,
+except it searches forward through the history.
+.It Xo
+.Oo Ar n Oc Ns n
+.Xc
+Search for the
+.Ar n Ns th
+occurrence of the last search string;
+the direction of the search is the same as the last search.
+.It Xo
+.Oo Ar n Oc Ns N
+.Xc
+Search for the
+.Ar n Ns th
+occurrence of the last search string;
+the direction of the search is the opposite of the last search.
+.El
+.Pp
+Edit commands
+.Bl -tag -width Ds
+.It Xo
+.Oo Ar n Oc Ns a
+.Xc
+Append text
+.Ar n
+times; goes into insert mode just after the current position.
+The append is
+only replicated if command mode is re-entered i.e.\&
+.Aq esc
+is used.
+.It Xo
+.Oo Ar n Oc Ns A
+.Xc
+Same as
+.Ic a ,
+except it appends at the end of the line.
+.It Xo
+.Oo Ar n Oc Ns i
+.Xc
+Insert text
+.Ar n
+times; goes into insert mode at the current position.
+The insertion is only
+replicated if command mode is re-entered i.e.\&
+.Aq esc
+is used.
+.It Xo
+.Oo Ar n Oc Ns I
+.Xc
+Same as
+.Ic i ,
+except the insertion is done just before the first non-blank character.
+.It Xo
+.Oo Ar n Oc Ns s
+.Xc
+Substitute the next
+.Ar n
+characters (i.e. delete the characters and go into insert mode).
+.It S
+Substitute whole line.
+All characters from the first non-blank character to the
+end of the line are deleted and insert mode is entered.
+.It Xo
+.Oo Ar n Oc Ns c Ns Ar move-cmd
+.Xc
+Change from the current position to the position resulting from
+.Ar n move-cmd Ns s
+(i.e. delete the indicated region and go into insert mode); if
+.Ar move-cmd
+is
+.Ic c ,
+the line starting from the first non-blank character is changed.
+.It C
+Change from the current position to the end of the line (i.e. delete to the
+end of the line and go into insert mode).
+.It Xo
+.Oo Ar n Oc Ns x
+.Xc
+Delete the next
+.Ar n
+characters.
+.It Xo
+.Oo Ar n Oc Ns X
+.Xc
+Delete the previous
+.Ar n
+characters.
+.It D
+Delete to the end of the line.
+.It Xo
+.Oo Ar n Oc Ns d Ns Ar move-cmd
+.Xc
+Delete from the current position to the position resulting from
+.Ar n move-cmd Ns s ;
+.Ar move-cmd
+is a movement command (see above) or
+.Ic d ,
+in which case the current line is deleted.
+.It Xo
+.Oo Ar n Oc Ns r Ns Ar c
+.Xc
+Replace the next
+.Ar n
+characters with the character
+.Ar c .
+.It Xo
+.Oo Ar n Oc Ns R
+.Xc
+Replace.
+Enter insert mode but overwrite existing characters instead of
+inserting before existing characters.
+The replacement is repeated
+.Ar n
+times.
+.It Xo
+.Oo Ar n Oc Ns ~
+.Xc
+Change the case of the next
+.Ar n
+characters.
+.It Xo
+.Oo Ar n Oc Ns y Ns Ar move-cmd
+.Xc
+Yank from the current position to the position resulting from
+.Ar n move-cmd Ns s
+into the yank buffer; if
+.Ar move-cmd
+is
+.Ic y ,
+the whole line is yanked.
+.It Y
+Yank from the current position to the end of the line.
+.It Xo
+.Oo Ar n Oc Ns p
+.Xc
+Paste the contents of the yank buffer just after the current position,
+.Ar n
+times.
+.It Xo
+.Oo Ar n Oc Ns P
+.Xc
+Same as
+.Ic p ,
+except the buffer is pasted at the current position.
+.El
+.Pp
+Miscellaneous vi commands
+.Bl -tag -width Ds
+.It ^J and ^M
+The current line is read, parsed, and executed by the shell.
+.It ^L and ^R
+Redraw the current line.
+.It Xo
+.Oo Ar n Oc Ns \&.
+.Xc
+Redo the last edit command
+.Ar n
+times.
+.It u
+Undo the last edit command.
+.It U
+Undo all changes that have been made to the current line.
+.It Ar intr No and Ar quit
+The interrupt and quit terminal characters cause the current line to be
+deleted and a new prompt to be printed.
+.El
+.Sh FILES
+.Bl -tag -width "/etc/suid_profileXX" -compact
+.It Pa ~/.profile
+User's login profile.
+.It Pa /etc/ksh.kshrc
+Global configuration file.
+Not sourced by default.
+.It Pa /etc/profile
+System login profile.
+.It Pa /etc/shells
+Shell database.
+.It Pa /etc/suid_profile
+Privileged shell profile.
+.El
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr ed 1 ,
+.Xr mg 1 ,
+.Xr sh 1 ,
+.Xr stty 1 ,
+.Xr vi 1 ,
+.Xr shells 5 ,
+.Xr environ 7 ,
+.Xr script 7
+.Rs
+.%A Morris Bolsky
+.%A David Korn
+.%B The KornShell Command and Programming Language, 2nd Edition
+.%D 1995
+.%I Prentice Hall
+.%O ISBN 0131827006
+.Re
+.Rs
+.%A Stephen G. Kochan
+.%A Patrick H. Wood
+.%B UNIX Shell Programming, 3rd Edition
+.%D 2003
+.%I Sams
+.%O ISBN 0672324903
+.Re
+.Rs
+.%A IEEE Inc.
+.%D 1993
+.%O ISBN 1-55937-266-9
+.%T IEEE Standard for Information Technology \- Portable Operating \
+ System Interface (POSIX) \- Part 2: Shell and Utilities
+.Re
+.Sh VERSION
+This page documents version @(#)PD KSH v5.2.14 99/07/13.2 of the public
+domain Korn shell.
+.Sh AUTHORS
+.An -nosplit
+This shell is based on the public domain 7th edition Bourne shell clone by
+.An Charles Forsyth
+and parts of the BRL shell by
+.An Doug A. Gwyn ,
+.An Doug Kingston ,
+.An Ron Natalie ,
+.An Arnold Robbins ,
+.An Lou Salkind ,
+and others.
+The first release of
+.Nm pdksh
+was created by
+.An Eric Gisin ,
+and it was subsequently maintained by
+.An John R. MacMillan Aq Mt change!john@sq.sq.com ,
+.An Simon J. Gerraty Aq Mt sjg@zen.void.oz.au ,
+and
+.An Michael Rendell Aq Mt michael@cs.mun.ca .
+The
+.Pa CONTRIBUTORS
+file in the source distribution contains a more complete list of people and
+their part in the shell's development.
+.\" .Sh BUGS
+.\" Any bugs in
+.\" .Nm pdksh
+.\" should be reported to pdksh@cs.mun.ca.
+.\" Please include the version of
+.\" .Nm pdksh
+.\" .Po
+.\" .Ic echo $KSH_VERSION
+.\" shows it
+.\" .Pc ,
+.\" the machine, operating system, and compiler you are using and a description of
+.\" how to repeat the bug (a small shell script that demonstrates the bug is best).
+.\" The following, if relevant (if you are not sure, include them), can also be
+.\" helpful: options you are using (both
+.\" .Pa options.h
+.\" and
+.\" .Ic set Fl o Ic options )
+.\" and a copy of your
+.\" .Pa config.h
+.\" (the file generated by the
+.\" .Pa configure
+.\" script).
+.\" New versions of
+.\" .Nm pdksh
+.\" can be obtained from ftp://ftp.cs.mun.ca/pub/pdksh.
+.\" .Pp
+.\" BTW, the most frequently reported bug is:
+.\" .Bd -literal -offset indent
+.\" $ echo hi | read a; echo $a # Does not print hi
+.\" .Ed
+.\" .Pp
+.\" I'm aware of this and there is no need to report it.
diff --git a/ksh_limval.h b/ksh_limval.h
@@ -0,0 +1,13 @@
+/* $OpenBSD: ksh_limval.h,v 1.2 2004/12/18 20:55:52 millert Exp $ */
+
+/* Wrapper around the values.h/limits.h includes/ifdefs */
+
+/* limits.h is included in sh.h */
+
+#ifndef DMAXEXP
+# define DMAXEXP 128 /* should be big enough */
+#endif
+
+#ifndef BITS
+# define BITS(t) (CHAR_BIT * sizeof(t))
+#endif
diff --git a/lex.c b/lex.c
@@ -0,0 +1,1644 @@
+/* $OpenBSD: lex.c,v 1.49 2013/12/17 16:37:06 deraadt Exp $ */
+
+/*
+ * lexical analysis and source input
+ */
+
+#include "sh.h"
+#include <time.h>
+#include <libgen.h>
+#include <ctype.h>
+
+
+/* Structure to keep track of the lexing state and the various pieces of info
+ * needed for each particular state.
+ */
+typedef struct lex_state Lex_state;
+struct lex_state {
+ int ls_state;
+ union {
+ /* $(...) */
+ struct scsparen_info {
+ int nparen; /* count open parenthesis */
+ int csstate; /* XXX remove */
+#define ls_scsparen ls_info.u_scsparen
+ } u_scsparen;
+
+ /* $((...)) */
+ struct sasparen_info {
+ int nparen; /* count open parenthesis */
+ int start; /* marks start of $(( in output str */
+#define ls_sasparen ls_info.u_sasparen
+ } u_sasparen;
+
+ /* ((...)) */
+ struct sletparen_info {
+ int nparen; /* count open parenthesis */
+#define ls_sletparen ls_info.u_sletparen
+ } u_sletparen;
+
+ /* `...` */
+ struct sbquote_info {
+ int indquotes; /* true if in double quotes: "`...`" */
+#define ls_sbquote ls_info.u_sbquote
+ } u_sbquote;
+
+ Lex_state *base; /* used to point to next state block */
+ } ls_info;
+};
+
+typedef struct State_info State_info;
+struct State_info {
+ Lex_state *base;
+ Lex_state *end;
+};
+
+
+static void readhere(struct ioword *);
+static int getsc__(void);
+static void getsc_line(Source *);
+static int getsc_bn(void);
+static char *get_brace_var(XString *, char *);
+static int arraysub(char **);
+static const char *ungetsc(int);
+static void gethere(void);
+static Lex_state *push_state_(State_info *, Lex_state *);
+static Lex_state *pop_state_(State_info *, Lex_state *);
+static char *special_prompt_expand(char *);
+static int dopprompt(const char *, int, const char **, int);
+int promptlen(const char *cp, const char **spp);
+
+static int backslash_skip;
+static int ignore_backslash_newline;
+
+/* optimized getsc_bn() */
+#define getsc() (*source->str != '\0' && *source->str != '\\' \
+ && !backslash_skip ? *source->str++ : getsc_bn())
+/* optimized getsc__() */
+#define getsc_() ((*source->str != '\0') ? *source->str++ : getsc__())
+
+#define STATE_BSIZE 32
+
+#define PUSH_STATE(s) do { \
+ if (++statep == state_info.end) \
+ statep = push_state_(&state_info, statep); \
+ state = statep->ls_state = (s); \
+ } while (0)
+
+#define POP_STATE() do { \
+ if (--statep == state_info.base) \
+ statep = pop_state_(&state_info, statep); \
+ state = statep->ls_state; \
+ } while (0)
+
+
+
+/*
+ * Lexical analyzer
+ *
+ * tokens are not regular expressions, they are LL(1).
+ * for example, "${var:-${PWD}}", and "$(size $(whence ksh))".
+ * hence the state stack.
+ */
+
+int
+yylex(int cf)
+{
+ Lex_state states[STATE_BSIZE], *statep;
+ State_info state_info;
+ int c, state;
+ XString ws; /* expandable output word */
+ char *wp; /* output word pointer */
+ char *sp, *dp;
+ int c2;
+
+
+ Again:
+ states[0].ls_state = -1;
+ states[0].ls_info.base = NULL;
+ statep = &states[1];
+ state_info.base = states;
+ state_info.end = &states[STATE_BSIZE];
+
+ Xinit(ws, wp, 64, ATEMP);
+
+ backslash_skip = 0;
+ ignore_backslash_newline = 0;
+
+ if (cf&ONEWORD)
+ state = SWORD;
+ else if (cf&LETEXPR) {
+ *wp++ = OQUOTE; /* enclose arguments in (double) quotes */
+ state = SLETPAREN;
+ statep->ls_sletparen.nparen = 0;
+ } else { /* normal lexing */
+ state = (cf & HEREDELIM) ? SHEREDELIM : SBASE;
+ while ((c = getsc()) == ' ' || c == '\t')
+ ;
+ if (c == '#') {
+ ignore_backslash_newline++;
+ while ((c = getsc()) != '\0' && c != '\n')
+ ;
+ ignore_backslash_newline--;
+ }
+ ungetsc(c);
+ }
+ if (source->flags & SF_ALIAS) { /* trailing ' ' in alias definition */
+ source->flags &= ~SF_ALIAS;
+ /* In POSIX mode, a trailing space only counts if we are
+ * parsing a simple command
+ */
+ if (!Flag(FPOSIX) || (cf & CMDWORD))
+ cf |= ALIAS;
+ }
+
+ /* Initial state: one of SBASE SHEREDELIM SWORD SASPAREN */
+ statep->ls_state = state;
+
+ /* collect non-special or quoted characters to form word */
+ while (!((c = getsc()) == 0 ||
+ ((state == SBASE || state == SHEREDELIM) && ctype(c, C_LEX1)))) {
+ Xcheck(ws, wp);
+ switch (state) {
+ case SBASE:
+ if (Flag(FCSHHISTORY) && (source->flags & SF_TTY) &&
+ c == '!') {
+ char **replace = NULL;
+
+ c2 = getsc();
+ if (c2 == '\0' || c2 == ' ' || c2 == '\t')
+ ;
+ else if (c2 == '!')
+ replace = hist_get_newest(0);
+ else if (isdigit(c2) || c2 == '-' ||
+ isalpha(c2)) {
+ int get = !isalpha(c2);
+ char match[200], *str = match;
+
+ *str++ = c2;
+ do {
+ if ((c2 = getsc()) == '\0')
+ break;
+ if (c2 == '\t' || c2 == ' ' ||
+ c2 == '\n') {
+ ungetsc(c2);
+ break;
+ }
+ *str++ = c2;
+ } while (str < &match[sizeof(match)-1]);
+ *str = '\0';
+
+ if (get) {
+ int h = findhistrel(match);
+ if (h >= 0)
+ replace = &history[h];
+ } else {
+ int h = findhist(-1, 0, match, true);
+ if (h >= 0)
+ replace = &history[h];
+ }
+ }
+
+ /*
+ * XXX ksh history buffer saves un-expanded
+ * commands. Until the history buffer code is
+ * changed to contain expanded commands, we
+ * ignore the bad commands (spinning sucks)
+ */
+ if (replace && **replace == '!')
+ ungetsc(c2);
+ else if (replace) {
+ Source *s;
+
+ /* do not strdup replacement via alloc */
+ s = pushs(SREREAD, source->areap);
+ s->start = s->str = *replace;
+ s->next = source;
+ s->u.freeme = NULL;
+ source = s;
+ continue;
+ } else
+ ungetsc(c2);
+ }
+ if (c == '[' && (cf & (VARASN|ARRAYVAR))) {
+ *wp = EOS; /* temporary */
+ if (is_wdvarname(Xstring(ws, wp), false)) {
+ char *p, *tmp;
+
+ if (arraysub(&tmp)) {
+ *wp++ = CHAR;
+ *wp++ = c;
+ for (p = tmp; *p; ) {
+ Xcheck(ws, wp);
+ *wp++ = CHAR;
+ *wp++ = *p++;
+ }
+ afree(tmp, ATEMP);
+ break;
+ } else {
+ Source *s;
+
+ s = pushs(SREREAD,
+ source->areap);
+ s->start = s->str
+ = s->u.freeme = tmp;
+ s->next = source;
+ source = s;
+ }
+ }
+ *wp++ = CHAR;
+ *wp++ = c;
+ break;
+ }
+ /* FALLTHROUGH */
+ Sbase1: /* includes *(...|...) pattern (*+?@!) */
+ if (c == '*' || c == '@' || c == '+' || c == '?' ||
+ c == '!') {
+ c2 = getsc();
+ if (c2 == '(' /*)*/ ) {
+ *wp++ = OPAT;
+ *wp++ = c;
+ PUSH_STATE(SPATTERN);
+ break;
+ }
+ ungetsc(c2);
+ }
+ /* FALLTHROUGH */
+ Sbase2: /* doesn't include *(...|...) pattern (*+?@!) */
+ switch (c) {
+ case '\\':
+ c = getsc();
+ if (c) /* trailing \ is lost */
+ *wp++ = QCHAR, *wp++ = c;
+ break;
+ case '\'':
+ if ((cf & HEREDOC) || state == SBRACEQ) {
+ *wp++ = CHAR, *wp++ = c;
+ break;
+ }
+ *wp++ = OQUOTE;
+ ignore_backslash_newline++;
+ PUSH_STATE(SSQUOTE);
+ break;
+ case '"':
+ *wp++ = OQUOTE;
+ PUSH_STATE(SDQUOTE);
+ break;
+ default:
+ goto Subst;
+ }
+ break;
+
+ Subst:
+ switch (c) {
+ case '\\':
+ c = getsc();
+ switch (c) {
+ case '\\':
+ case '$': case '`':
+ *wp++ = QCHAR, *wp++ = c;
+ break;
+ case '"':
+ if ((cf & HEREDOC) == 0) {
+ *wp++ = QCHAR, *wp++ = c;
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ if (cf & UNESCAPE) {
+ *wp++ = QCHAR, *wp++ = c;
+ break;
+ }
+ Xcheck(ws, wp);
+ if (c) { /* trailing \ is lost */
+ *wp++ = CHAR, *wp++ = '\\';
+ *wp++ = CHAR, *wp++ = c;
+ }
+ break;
+ }
+ break;
+ case '$':
+ c = getsc();
+ if (c == '(') /*)*/ {
+ c = getsc();
+ if (c == '(') /*)*/ {
+ PUSH_STATE(SASPAREN);
+ statep->ls_sasparen.nparen = 2;
+ statep->ls_sasparen.start =
+ Xsavepos(ws, wp);
+ *wp++ = EXPRSUB;
+ } else {
+ ungetsc(c);
+ PUSH_STATE(SCSPAREN);
+ statep->ls_scsparen.nparen = 1;
+ statep->ls_scsparen.csstate = 0;
+ *wp++ = COMSUB;
+ }
+ } else if (c == '{') /*}*/ {
+ *wp++ = OSUBST;
+ *wp++ = '{'; /*}*/
+ wp = get_brace_var(&ws, wp);
+ c = getsc();
+ /* allow :# and :% (ksh88 compat) */
+ if (c == ':') {
+ *wp++ = CHAR, *wp++ = c;
+ c = getsc();
+ }
+ /* If this is a trim operation,
+ * treat (,|,) specially in STBRACE.
+ */
+ if (c == '#' || c == '%') {
+ ungetsc(c);
+ PUSH_STATE(STBRACE);
+ } else {
+ ungetsc(c);
+ if (state == SDQUOTE ||
+ state == SBRACEQ)
+ PUSH_STATE(SBRACEQ);
+ else
+ PUSH_STATE(SBRACE);
+ }
+ } else if (ctype(c, C_ALPHA)) {
+ *wp++ = OSUBST;
+ *wp++ = 'X';
+ do {
+ Xcheck(ws, wp);
+ *wp++ = c;
+ c = getsc();
+ } while (ctype(c, C_ALPHA|C_DIGIT));
+ *wp++ = '\0';
+ *wp++ = CSUBST;
+ *wp++ = 'X';
+ ungetsc(c);
+ } else if (ctype(c, C_DIGIT|C_VAR1)) {
+ Xcheck(ws, wp);
+ *wp++ = OSUBST;
+ *wp++ = 'X';
+ *wp++ = c;
+ *wp++ = '\0';
+ *wp++ = CSUBST;
+ *wp++ = 'X';
+ } else {
+ *wp++ = CHAR, *wp++ = '$';
+ ungetsc(c);
+ }
+ break;
+ case '`':
+ PUSH_STATE(SBQUOTE);
+ *wp++ = COMSUB;
+ /* Need to know if we are inside double quotes
+ * since sh/at&t-ksh translate the \" to " in
+ * "`..\"..`".
+ * This is not done in posix mode (section
+ * 3.2.3, Double Quotes: "The backquote shall
+ * retain its special meaning introducing the
+ * other form of command substitution (see
+ * 3.6.3). The portion of the quoted string
+ * from the initial backquote and the
+ * characters up to the next backquote that
+ * is not preceded by a backslash (having
+ * escape characters removed) defines that
+ * command whose output replaces `...` when
+ * the word is expanded."
+ * Section 3.6.3, Command Substitution:
+ * "Within the backquoted style of command
+ * substitution, backslash shall retain its
+ * literal meaning, except when followed by
+ * $ ` \.").
+ */
+ statep->ls_sbquote.indquotes = 0;
+ if (!Flag(FPOSIX)) {
+ Lex_state *s = statep;
+ Lex_state *base = state_info.base;
+ while (1) {
+ for (; s != base; s--) {
+ if (s->ls_state == SDQUOTE) {
+ statep->ls_sbquote.indquotes = 1;
+ break;
+ }
+ }
+ if (s != base)
+ break;
+ if (!(s = s->ls_info.base))
+ break;
+ base = s-- - STATE_BSIZE;
+ }
+ }
+ break;
+ default:
+ *wp++ = CHAR, *wp++ = c;
+ }
+ break;
+
+ case SSQUOTE:
+ if (c == '\'') {
+ POP_STATE();
+ if (state == SBRACEQ) {
+ *wp++ = CHAR, *wp++ = c;
+ break;
+ }
+ *wp++ = CQUOTE;
+ ignore_backslash_newline--;
+ } else
+ *wp++ = QCHAR, *wp++ = c;
+ break;
+
+ case SDQUOTE:
+ if (c == '"') {
+ POP_STATE();
+ *wp++ = CQUOTE;
+ } else
+ goto Subst;
+ break;
+
+ case SCSPAREN: /* $( .. ) */
+ /* todo: deal with $(...) quoting properly
+ * kludge to partly fake quoting inside $(..): doesn't
+ * really work because nested $(..) or ${..} inside
+ * double quotes aren't dealt with.
+ */
+ switch (statep->ls_scsparen.csstate) {
+ case 0: /* normal */
+ switch (c) {
+ case '(':
+ statep->ls_scsparen.nparen++;
+ break;
+ case ')':
+ statep->ls_scsparen.nparen--;
+ break;
+ case '\\':
+ statep->ls_scsparen.csstate = 1;
+ break;
+ case '"':
+ statep->ls_scsparen.csstate = 2;
+ break;
+ case '\'':
+ statep->ls_scsparen.csstate = 4;
+ ignore_backslash_newline++;
+ break;
+ }
+ break;
+
+ case 1: /* backslash in normal mode */
+ case 3: /* backslash in double quotes */
+ --statep->ls_scsparen.csstate;
+ break;
+
+ case 2: /* double quotes */
+ if (c == '"')
+ statep->ls_scsparen.csstate = 0;
+ else if (c == '\\')
+ statep->ls_scsparen.csstate = 3;
+ break;
+
+ case 4: /* single quotes */
+ if (c == '\'') {
+ statep->ls_scsparen.csstate = 0;
+ ignore_backslash_newline--;
+ }
+ break;
+ }
+ if (statep->ls_scsparen.nparen == 0) {
+ POP_STATE();
+ *wp++ = 0; /* end of COMSUB */
+ } else
+ *wp++ = c;
+ break;
+
+ case SASPAREN: /* $(( .. )) */
+ /* todo: deal with $((...); (...)) properly */
+ /* XXX should nest using existing state machine
+ * (embed "..", $(...), etc.) */
+ if (c == '(')
+ statep->ls_sasparen.nparen++;
+ else if (c == ')') {
+ statep->ls_sasparen.nparen--;
+ if (statep->ls_sasparen.nparen == 1) {
+ /*(*/
+ if ((c2 = getsc()) == ')') {
+ POP_STATE();
+ *wp++ = 0; /* end of EXPRSUB */
+ break;
+ } else {
+ char *s;
+
+ ungetsc(c2);
+ /* mismatched parenthesis -
+ * assume we were really
+ * parsing a $(..) expression
+ */
+ s = Xrestpos(ws, wp,
+ statep->ls_sasparen.start);
+ memmove(s + 1, s, wp - s);
+ *s++ = COMSUB;
+ *s = '('; /*)*/
+ wp++;
+ statep->ls_scsparen.nparen = 1;
+ statep->ls_scsparen.csstate = 0;
+ state = statep->ls_state =
+ SCSPAREN;
+ }
+ }
+ }
+ *wp++ = c;
+ break;
+
+ case SBRACEQ:
+ case SBRACE:
+ /*{*/
+ if (c == '}') {
+ POP_STATE();
+ *wp++ = CSUBST;
+ *wp++ = /*{*/ '}';
+ } else
+ goto Sbase1;
+ break;
+
+ case STBRACE:
+ /* Same as SBRACE, except (,|,) treated specially */
+ /*{*/
+ if (c == '}') {
+ POP_STATE();
+ *wp++ = CSUBST;
+ *wp++ = /*{*/ '}';
+ } else if (c == '|') {
+ *wp++ = SPAT;
+ } else if (c == '(') {
+ *wp++ = OPAT;
+ *wp++ = ' '; /* simile for @ */
+ PUSH_STATE(SPATTERN);
+ } else
+ goto Sbase1;
+ break;
+
+ case SBQUOTE:
+ if (c == '`') {
+ *wp++ = 0;
+ POP_STATE();
+ } else if (c == '\\') {
+ switch (c = getsc()) {
+ case '\\':
+ case '$': case '`':
+ *wp++ = c;
+ break;
+ case '"':
+ if (statep->ls_sbquote.indquotes) {
+ *wp++ = c;
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ if (c) { /* trailing \ is lost */
+ *wp++ = '\\';
+ *wp++ = c;
+ }
+ break;
+ }
+ } else
+ *wp++ = c;
+ break;
+
+ case SWORD: /* ONEWORD */
+ goto Subst;
+
+ case SLETPAREN: /* LETEXPR: (( ... )) */
+ /*(*/
+ if (c == ')') {
+ if (statep->ls_sletparen.nparen > 0)
+ --statep->ls_sletparen.nparen;
+ /*(*/
+ else if ((c2 = getsc()) == ')') {
+ c = 0;
+ *wp++ = CQUOTE;
+ goto Done;
+ } else
+ ungetsc(c2);
+ } else if (c == '(')
+ /* parenthesis inside quotes and backslashes
+ * are lost, but at&t ksh doesn't count them
+ * either
+ */
+ ++statep->ls_sletparen.nparen;
+ goto Sbase2;
+
+ case SHEREDELIM: /* <<,<<- delimiter */
+ /* XXX chuck this state (and the next) - use
+ * the existing states ($ and \`..` should be
+ * stripped of their specialness after the
+ * fact).
+ */
+ /* here delimiters need a special case since
+ * $ and `..` are not to be treated specially
+ */
+ if (c == '\\') {
+ c = getsc();
+ if (c) { /* trailing \ is lost */
+ *wp++ = QCHAR;
+ *wp++ = c;
+ }
+ } else if (c == '\'') {
+ PUSH_STATE(SSQUOTE);
+ *wp++ = OQUOTE;
+ ignore_backslash_newline++;
+ } else if (c == '"') {
+ state = statep->ls_state = SHEREDQUOTE;
+ *wp++ = OQUOTE;
+ } else {
+ *wp++ = CHAR;
+ *wp++ = c;
+ }
+ break;
+
+ case SHEREDQUOTE: /* " in <<,<<- delimiter */
+ if (c == '"') {
+ *wp++ = CQUOTE;
+ state = statep->ls_state = SHEREDELIM;
+ } else {
+ if (c == '\\') {
+ switch (c = getsc()) {
+ case '\\': case '"':
+ case '$': case '`':
+ break;
+ default:
+ if (c) { /* trailing \ lost */
+ *wp++ = CHAR;
+ *wp++ = '\\';
+ }
+ break;
+ }
+ }
+ *wp++ = CHAR;
+ *wp++ = c;
+ }
+ break;
+
+ case SPATTERN: /* in *(...|...) pattern (*+?@!) */
+ if ( /*(*/ c == ')') {
+ *wp++ = CPAT;
+ POP_STATE();
+ } else if (c == '|') {
+ *wp++ = SPAT;
+ } else if (c == '(') {
+ *wp++ = OPAT;
+ *wp++ = ' '; /* simile for @ */
+ PUSH_STATE(SPATTERN);
+ } else
+ goto Sbase1;
+ break;
+ }
+ }
+Done:
+ Xcheck(ws, wp);
+ if (statep != &states[1])
+ /* XXX figure out what is missing */
+ yyerror("no closing quote\n");
+
+ /* This done to avoid tests for SHEREDELIM wherever SBASE tested */
+ if (state == SHEREDELIM)
+ state = SBASE;
+
+ dp = Xstring(ws, wp);
+ if ((c == '<' || c == '>') && state == SBASE &&
+ ((c2 = Xlength(ws, wp)) == 0 ||
+ (c2 == 2 && dp[0] == CHAR && digit(dp[1])))) {
+ struct ioword *iop = (struct ioword *) alloc(sizeof(*iop), ATEMP);
+
+ if (c2 == 2)
+ iop->unit = dp[1] - '0';
+ else
+ iop->unit = c == '>'; /* 0 for <, 1 for > */
+
+ c2 = getsc();
+ /* <<, >>, <> are ok, >< is not */
+ if (c == c2 || (c == '<' && c2 == '>')) {
+ iop->flag = c == c2 ?
+ (c == '>' ? IOCAT : IOHERE) : IORDWR;
+ if (iop->flag == IOHERE) {
+ if ((c2 = getsc()) == '-')
+ iop->flag |= IOSKIP;
+ else
+ ungetsc(c2);
+ }
+ } else if (c2 == '&')
+ iop->flag = IODUP | (c == '<' ? IORDUP : 0);
+ else {
+ iop->flag = c == '>' ? IOWRITE : IOREAD;
+ if (c == '>' && c2 == '|')
+ iop->flag |= IOCLOB;
+ else
+ ungetsc(c2);
+ }
+
+ iop->name = (char *) 0;
+ iop->delim = (char *) 0;
+ iop->heredoc = (char *) 0;
+ Xfree(ws, wp); /* free word */
+ yylval.iop = iop;
+ return REDIR;
+ }
+
+ if (wp == dp && state == SBASE) {
+ Xfree(ws, wp); /* free word */
+ /* no word, process LEX1 character */
+ switch (c) {
+ default:
+ return c;
+
+ case '|':
+ case '&':
+ case ';':
+ if ((c2 = getsc()) == c)
+ c = (c == ';') ? BREAK :
+ (c == '|') ? LOGOR :
+ (c == '&') ? LOGAND :
+ YYERRCODE;
+ else if (c == '|' && c2 == '&')
+ c = COPROC;
+ else
+ ungetsc(c2);
+ return c;
+
+ case '\n':
+ gethere();
+ if (cf & CONTIN)
+ goto Again;
+ return c;
+
+ case '(': /*)*/
+ if (!Flag(FSH)) {
+ if ((c2 = getsc()) == '(') /*)*/
+ /* XXX need to handle ((...); (...)) */
+ c = MDPAREN;
+ else
+ ungetsc(c2);
+ }
+ return c;
+ /*(*/
+ case ')':
+ return c;
+ }
+ }
+
+ *wp++ = EOS; /* terminate word */
+ yylval.cp = Xclose(ws, wp);
+ if (state == SWORD || state == SLETPAREN) /* ONEWORD? */
+ return LWORD;
+ ungetsc(c); /* unget terminator */
+
+ /* copy word to unprefixed string ident */
+ for (sp = yylval.cp, dp = ident; dp < ident+IDENT && (c = *sp++) == CHAR; )
+ *dp++ = *sp++;
+ /* Make sure the ident array stays '\0' padded */
+ memset(dp, 0, (ident+IDENT) - dp + 1);
+ if (c != EOS)
+ *ident = '\0'; /* word is not unquoted */
+
+ if (*ident != '\0' && (cf&(KEYWORD|ALIAS))) {
+ struct tbl *p;
+ int h = hash(ident);
+
+ /* { */
+ if ((cf & KEYWORD) && (p = ktsearch(&keywords, ident, h)) &&
+ (!(cf & ESACONLY) || p->val.i == ESAC || p->val.i == '}')) {
+ afree(yylval.cp, ATEMP);
+ return p->val.i;
+ }
+ if ((cf & ALIAS) && (p = ktsearch(&aliases, ident, h)) &&
+ (p->flag & ISSET)) {
+ Source *s;
+
+ for (s = source; s->type == SALIAS; s = s->next)
+ if (s->u.tblp == p)
+ return LWORD;
+ /* push alias expansion */
+ s = pushs(SALIAS, source->areap);
+ s->start = s->str = p->val.s;
+ s->u.tblp = p;
+ s->next = source;
+ source = s;
+ afree(yylval.cp, ATEMP);
+ goto Again;
+ }
+ }
+
+ return LWORD;
+}
+
+static void
+gethere(void)
+{
+ struct ioword **p;
+
+ for (p = heres; p < herep; p++)
+ readhere(*p);
+ herep = heres;
+}
+
+/*
+ * read "<<word" text into temp file
+ */
+
+static void
+readhere(struct ioword *iop)
+{
+ int c;
+ char *volatile eof;
+ char *eofp;
+ int skiptabs;
+ XString xs;
+ char *xp;
+ int xpos;
+
+ eof = evalstr(iop->delim, 0);
+
+ if (!(iop->flag & IOEVAL))
+ ignore_backslash_newline++;
+
+ Xinit(xs, xp, 256, ATEMP);
+
+ for (;;) {
+ eofp = eof;
+ skiptabs = iop->flag & IOSKIP;
+ xpos = Xsavepos(xs, xp);
+ while ((c = getsc()) != 0) {
+ if (skiptabs) {
+ if (c == '\t')
+ continue;
+ skiptabs = 0;
+ }
+ if (c != *eofp)
+ break;
+ Xcheck(xs, xp);
+ Xput(xs, xp, c);
+ eofp++;
+ }
+ /* Allow EOF here so commands with out trailing newlines
+ * will work (eg, ksh -c '...', $(...), etc).
+ */
+ if (*eofp == '\0' && (c == 0 || c == '\n')) {
+ xp = Xrestpos(xs, xp, xpos);
+ break;
+ }
+ ungetsc(c);
+ while ((c = getsc()) != '\n') {
+ if (c == 0)
+ yyerror("here document `%s' unclosed\n", eof);
+ Xcheck(xs, xp);
+ Xput(xs, xp, c);
+ }
+ Xcheck(xs, xp);
+ Xput(xs, xp, c);
+ }
+ Xput(xs, xp, '\0');
+ iop->heredoc = Xclose(xs, xp);
+
+ if (!(iop->flag & IOEVAL))
+ ignore_backslash_newline--;
+}
+
+void
+yyerror(const char *fmt, ...)
+{
+ va_list va;
+
+ /* pop aliases and re-reads */
+ while (source->type == SALIAS || source->type == SREREAD)
+ source = source->next;
+ source->str = null; /* zap pending input */
+
+ error_prefix(true);
+ va_start(va, fmt);
+ shf_vfprintf(shl_out, fmt, va);
+ va_end(va);
+ errorf(null);
+}
+
+/*
+ * input for yylex with alias expansion
+ */
+
+Source *
+pushs(int type, Area *areap)
+{
+ Source *s;
+
+ s = (Source *) alloc(sizeof(Source), areap);
+ s->type = type;
+ s->str = null;
+ s->start = NULL;
+ s->line = 0;
+ s->cmd_offset = 0;
+ s->errline = 0;
+ s->file = NULL;
+ s->flags = 0;
+ s->next = NULL;
+ s->areap = areap;
+ if (type == SFILE || type == SSTDIN) {
+ char *dummy;
+ Xinit(s->xs, dummy, 256, s->areap);
+ } else
+ memset(&s->xs, 0, sizeof(s->xs));
+ return s;
+}
+
+static int
+getsc__(void)
+{
+ Source *s = source;
+ int c;
+
+ while ((c = *s->str++) == 0) {
+ s->str = NULL; /* return 0 for EOF by default */
+ switch (s->type) {
+ case SEOF:
+ s->str = null;
+ return 0;
+
+ case SSTDIN:
+ case SFILE:
+ getsc_line(s);
+ break;
+
+ case SWSTR:
+ break;
+
+ case SSTRING:
+ break;
+
+ case SWORDS:
+ s->start = s->str = *s->u.strv++;
+ s->type = SWORDSEP;
+ break;
+
+ case SWORDSEP:
+ if (*s->u.strv == NULL) {
+ s->start = s->str = newline;
+ s->type = SEOF;
+ } else {
+ s->start = s->str = space;
+ s->type = SWORDS;
+ }
+ break;
+
+ case SALIAS:
+ if (s->flags & SF_ALIASEND) {
+ /* pass on an unused SF_ALIAS flag */
+ source = s->next;
+ source->flags |= s->flags & SF_ALIAS;
+ s = source;
+ } else if (*s->u.tblp->val.s &&
+ isspace((unsigned char)strchr(s->u.tblp->val.s, 0)[-1])) {
+ source = s = s->next; /* pop source stack */
+ /* Note that this alias ended with a space,
+ * enabling alias expansion on the following
+ * word.
+ */
+ s->flags |= SF_ALIAS;
+ } else {
+ /* At this point, we need to keep the current
+ * alias in the source list so recursive
+ * aliases can be detected and we also need
+ * to return the next character. Do this
+ * by temporarily popping the alias to get
+ * the next character and then put it back
+ * in the source list with the SF_ALIASEND
+ * flag set.
+ */
+ source = s->next; /* pop source stack */
+ source->flags |= s->flags & SF_ALIAS;
+ c = getsc__();
+ if (c) {
+ s->flags |= SF_ALIASEND;
+ s->ugbuf[0] = c; s->ugbuf[1] = '\0';
+ s->start = s->str = s->ugbuf;
+ s->next = source;
+ source = s;
+ } else {
+ s = source;
+ /* avoid reading eof twice */
+ s->str = NULL;
+ break;
+ }
+ }
+ continue;
+
+ case SREREAD:
+ if (s->start != s->ugbuf) /* yuck */
+ afree(s->u.freeme, ATEMP);
+ source = s = s->next;
+ continue;
+ }
+ if (s->str == NULL) {
+ s->type = SEOF;
+ s->start = s->str = null;
+ return '\0';
+ }
+ if (s->flags & SF_ECHO) {
+ shf_puts(s->str, shl_out);
+ shf_flush(shl_out);
+ }
+ }
+ return c;
+}
+
+static void
+getsc_line(Source *s)
+{
+ char *xp = Xstring(s->xs, xp);
+ int interactive = Flag(FTALKING) && s->type == SSTDIN;
+ int have_tty = interactive && (s->flags & SF_TTY);
+
+ /* Done here to ensure nothing odd happens when a timeout occurs */
+ XcheckN(s->xs, xp, LINE);
+ *xp = '\0';
+ s->start = s->str = xp;
+
+ if (have_tty && ksh_tmout) {
+ ksh_tmout_state = TMOUT_READING;
+ alarm(ksh_tmout);
+ }
+#ifdef EDIT
+ if (have_tty && (0
+# ifdef VI
+ || Flag(FVI)
+# endif /* VI */
+# ifdef EMACS
+ || Flag(FEMACS) || Flag(FGMACS)
+# endif /* EMACS */
+ )) {
+ int nread;
+
+ nread = x_read(xp, LINE);
+ if (nread < 0) /* read error */
+ nread = 0;
+ xp[nread] = '\0';
+ xp += nread;
+ }
+ else
+#endif /* EDIT */
+ {
+ if (interactive) {
+ pprompt(prompt, 0);
+ } else
+ s->line++;
+
+ while (1) {
+ char *p = shf_getse(xp, Xnleft(s->xs, xp), s->u.shf);
+
+ if (!p && shf_error(s->u.shf) &&
+ shf_errno(s->u.shf) == EINTR) {
+ shf_clearerr(s->u.shf);
+ if (trap)
+ runtraps(0);
+ continue;
+ }
+ if (!p || (xp = p, xp[-1] == '\n'))
+ break;
+ /* double buffer size */
+ xp++; /* move past null so doubling works... */
+ XcheckN(s->xs, xp, Xlength(s->xs, xp));
+ xp--; /* ...and move back again */
+ }
+ /* flush any unwanted input so other programs/builtins
+ * can read it. Not very optimal, but less error prone
+ * than flushing else where, dealing with redirections,
+ * etc..
+ * todo: reduce size of shf buffer (~128?) if SSTDIN
+ */
+ if (s->type == SSTDIN)
+ shf_flush(s->u.shf);
+ }
+ /* XXX: temporary kludge to restore source after a
+ * trap may have been executed.
+ */
+ source = s;
+ if (have_tty && ksh_tmout) {
+ ksh_tmout_state = TMOUT_EXECUTING;
+ alarm(0);
+ }
+ s->start = s->str = Xstring(s->xs, xp);
+ strip_nuls(Xstring(s->xs, xp), Xlength(s->xs, xp));
+ /* Note: if input is all nulls, this is not eof */
+ if (Xlength(s->xs, xp) == 0) { /* EOF */
+ if (s->type == SFILE)
+ shf_fdclose(s->u.shf);
+ s->str = NULL;
+ } else if (interactive) {
+#ifdef HISTORY
+ char *p = Xstring(s->xs, xp);
+ if (cur_prompt == PS1)
+ while (*p && ctype(*p, C_IFS) && ctype(*p, C_IFSWS))
+ p++;
+ if (*p) {
+ s->line++;
+ histsave(s->line, s->str, 1);
+ }
+#endif /* HISTORY */
+ }
+ if (interactive)
+ set_prompt(PS2, (Source *) 0);
+}
+
+static char *
+special_prompt_expand(char *str)
+{
+ char *p = str;
+
+ while ((p = strstr(p, "\\$")) != NULL) {
+ *(p+1) = 'p';
+ }
+ return str;
+}
+
+void
+set_prompt(int to, Source *s)
+{
+ char *ps1;
+ Area *saved_atemp;
+
+ cur_prompt = to;
+
+ switch (to) {
+ case PS1: /* command */
+ ps1 = str_save(str_val(global("PS1")), ATEMP);
+ saved_atemp = ATEMP; /* ps1 is freed by substitute() */
+ newenv(E_ERRH);
+ if (sigsetjmp(e->jbuf, 0)) {
+ prompt = safe_prompt;
+ /* Don't print an error - assume it has already
+ * been printed. Reason is we may have forked
+ * to run a command and the child may be
+ * unwinding its stack through this code as it
+ * exits.
+ */
+ } else {
+ /* expand \$ before other substitutions are done */
+ char *tmp = special_prompt_expand(ps1);
+ prompt = str_save(substitute(tmp, 0), saved_atemp);
+ }
+ quitenv(NULL);
+ break;
+ case PS2: /* command continuation */
+ prompt = str_val(global("PS2"));
+ break;
+ }
+}
+
+static int
+dopprompt(const char *sp, int ntruncate, const char **spp, int doprint)
+{
+ char strbuf[1024], tmpbuf[1024], *p, *str, nbuf[32], delimiter = '\0';
+ int len, c, n, totlen = 0, indelimit = 0, counting = 1, delimitthis;
+ const char *cp = sp;
+ struct tm *tm;
+ time_t t;
+
+ if (*cp && cp[1] == '\r') {
+ delimiter = *cp;
+ cp += 2;
+ }
+
+ while (*cp != 0) {
+ delimitthis = 0;
+ if (indelimit && *cp != delimiter)
+ ;
+ else if (*cp == '\n' || *cp == '\r') {
+ totlen = 0;
+ sp = cp + 1;
+ } else if (*cp == '\t') {
+ if (counting)
+ totlen = (totlen | 7) + 1;
+ } else if (*cp == delimiter) {
+ indelimit = !indelimit;
+ delimitthis = 1;
+ }
+
+ if (*cp == '\\') {
+ cp++;
+ if (!*cp)
+ break;
+ if (Flag(FSH))
+ snprintf(strbuf, sizeof strbuf, "\\%c", *cp);
+ else switch (*cp) {
+ case 'a': /* '\' 'a' bell */
+ strbuf[0] = '\007';
+ strbuf[1] = '\0';
+ break;
+ case 'd': /* '\' 'd' Dow Mon DD */
+ time(&t);
+ tm = localtime(&t);
+ strftime(strbuf, sizeof strbuf, "%a %b %d", tm);
+ break;
+ case 'D': /* '\' 'D' '{' strftime format '}' */
+ p = strchr(cp + 2, '}');
+ if (cp[1] != '{' || p == NULL) {
+ snprintf(strbuf, sizeof strbuf,
+ "\\%c", *cp);
+ break;
+ }
+ strlcpy(tmpbuf, cp + 2, sizeof tmpbuf);
+ p = strchr(tmpbuf, '}');
+ if (p)
+ *p = '\0';
+ time(&t);
+ tm = localtime(&t);
+ strftime(strbuf, sizeof strbuf, tmpbuf, tm);
+ cp = strchr(cp + 2, '}');
+ break;
+ case 'e': /* '\' 'e' escape */
+ strbuf[0] = '\033';
+ strbuf[1] = '\0';
+ break;
+ case 'h': /* '\' 'h' shortened hostname */
+ gethostname(strbuf, sizeof strbuf);
+ p = strchr(strbuf, '.');
+ if (p)
+ *p = '\0';
+ break;
+ case 'H': /* '\' 'H' full hostname */
+ gethostname(strbuf, sizeof strbuf);
+ break;
+ case 'j': /* '\' 'j' number of jobs */
+ snprintf(strbuf, sizeof strbuf, "%d",
+ j_njobs());
+ break;
+ case 'l': /* '\' 'l' basename of tty */
+ p = ttyname(0);
+ if (p)
+ p = basename(p);
+ if (p)
+ strlcpy(strbuf, p, sizeof strbuf);
+ break;
+ case 'n': /* '\' 'n' newline */
+ strbuf[0] = '\n';
+ strbuf[1] = '\0';
+ totlen = 0; /* reset for prompt re-print */
+ sp = cp + 1;
+ break;
+ case 'p': /* '\' '$' $ or # */
+ strbuf[0] = ksheuid ? '$' : '#';
+ strbuf[1] = '\0';
+ break;
+ case 'r': /* '\' 'r' return */
+ strbuf[0] = '\r';
+ strbuf[1] = '\0';
+ totlen = 0; /* reset for prompt re-print */
+ sp = cp + 1;
+ break;
+ case 's': /* '\' 's' basename $0 */
+ strlcpy(strbuf, kshname, sizeof strbuf);
+ break;
+ case 't': /* '\' 't' 24 hour HH:MM:SS */
+ time(&t);
+ tm = localtime(&t);
+ strftime(strbuf, sizeof strbuf, "%T", tm);
+ break;
+ case 'T': /* '\' 'T' 12 hour HH:MM:SS */
+ time(&t);
+ tm = localtime(&t);
+ strftime(strbuf, sizeof strbuf, "%l:%M:%S", tm);
+ break;
+ case '@': /* '\' '@' 12 hour am/pm format */
+ time(&t);
+ tm = localtime(&t);
+ strftime(strbuf, sizeof strbuf, "%r", tm);
+ break;
+ case 'A': /* '\' 'A' 24 hour HH:MM */
+ time(&t);
+ tm = localtime(&t);
+ strftime(strbuf, sizeof strbuf, "%R", tm);
+ break;
+ case 'u': /* '\' 'u' username */
+ strlcpy(strbuf, username, sizeof strbuf);
+ break;
+ case 'v': /* '\' 'v' version (short) */
+ p = strchr(ksh_version, ' ');
+ if (p)
+ p = strchr(p + 1, ' ');
+ if (p) {
+ p++;
+ strlcpy(strbuf, p, sizeof strbuf);
+ p = strchr(strbuf, ' ');
+ if (p)
+ *p = '\0';
+ }
+ break;
+ case 'V': /* '\' 'V' version (long) */
+ strlcpy(strbuf, ksh_version, sizeof strbuf);
+ break;
+ case 'w': /* '\' 'w' cwd */
+ p = str_val(global("PWD"));
+ n = strlen(str_val(global("HOME")));
+ if (strcmp(p, "/") == 0) {
+ strlcpy(strbuf, p, sizeof strbuf);
+ } else if (strcmp(p, str_val(global("HOME"))) == 0) {
+ strbuf[0] = '~';
+ strbuf[1] = '\0';
+ } else if (strncmp(p, str_val(global("HOME")), n)
+ == 0 && p[n] == '/') {
+ snprintf(strbuf, sizeof strbuf, "~/%s",
+ str_val(global("PWD")) + n + 1);
+ } else
+ strlcpy(strbuf, p, sizeof strbuf);
+ break;
+ case 'W': /* '\' 'W' basename(cwd) */
+ p = str_val(global("PWD"));
+ if (strcmp(p, str_val(global("HOME"))) == 0) {
+ strbuf[0] = '~';
+ strbuf[1] = '\0';
+ } else
+ strlcpy(strbuf, basename(p), sizeof strbuf);
+ break;
+ case '!': /* '\' '!' history line number */
+ snprintf(strbuf, sizeof strbuf, "%d",
+ source->line + 1);
+ break;
+ case '#': /* '\' '#' command line number */
+ snprintf(strbuf, sizeof strbuf, "%d",
+ source->line - source->cmd_offset + 1);
+ break;
+ case '0': /* '\' '#' '#' ' #' octal numeric handling */
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ if ((cp[1] > '7' || cp[1] < '0') ||
+ (cp[2] > '7' || cp[2] < '0')) {
+ snprintf(strbuf, sizeof strbuf,
+ "\\%c", *cp);
+ break;
+ }
+ n = cp[0] * 8 * 8 + cp[1] * 8 + cp[2];
+ snprintf(strbuf, sizeof strbuf, "%c", n);
+ cp += 2;
+ break;
+ case '\\': /* '\' '\' */
+ strbuf[0] = '\\';
+ strbuf[1] = '\0';
+ break;
+ case '[': /* '\' '[' .... stop counting */
+ strbuf[0] = '\0';
+ counting = 0;
+ break;
+ case ']': /* '\' ']' restart counting */
+ strbuf[0] = '\0';
+ counting = 1;
+ break;
+
+ default:
+ snprintf(strbuf, sizeof strbuf, "\\%c", *cp);
+ break;
+ }
+ cp++;
+
+ str = strbuf;
+ len = strlen(str);
+ if (ntruncate) {
+ if (ntruncate >= len) {
+ ntruncate -= len;
+ continue;
+ }
+ str += ntruncate;
+ len -= ntruncate;
+ ntruncate = 0;
+ }
+ if (doprint)
+ shf_write(str, len, shl_out);
+ if (counting && !indelimit && !delimitthis)
+ totlen += len;
+ continue;
+ } else if (*cp != '!')
+ c = *cp++;
+ else if (*++cp == '!')
+ c = *cp++;
+ else {
+ char *p;
+
+ shf_snprintf(p = nbuf, sizeof(nbuf), "%d",
+ source->line + 1);
+ len = strlen(nbuf);
+ if (ntruncate) {
+ if (ntruncate >= len) {
+ ntruncate -= len;
+ continue;
+ }
+ p += ntruncate;
+ len -= ntruncate;
+ ntruncate = 0;
+ }
+ if (doprint)
+ shf_write(p, len, shl_out);
+ if (counting && !indelimit && !delimitthis)
+ totlen += len;
+ continue;
+ }
+ if (counting && ntruncate)
+ --ntruncate;
+ else if (doprint) {
+ shf_putc(c, shl_out);
+ }
+ if (counting && !indelimit && !delimitthis)
+ totlen++;
+ }
+ if (doprint)
+ shf_flush(shl_out);
+ if (spp)
+ *spp = sp;
+ return (totlen);
+}
+
+void
+pprompt(const char *cp, int ntruncate)
+{
+ dopprompt(cp, ntruncate, NULL, 1);
+}
+
+int
+promptlen(const char *cp, const char **spp)
+{
+ return dopprompt(cp, 0, spp, 0);
+}
+
+/* Read the variable part of a ${...} expression (ie, up to but not including
+ * the :[-+?=#%] or close-brace.
+ */
+static char *
+get_brace_var(XString *wsp, char *wp)
+{
+ enum parse_state {
+ PS_INITIAL, PS_SAW_HASH, PS_IDENT,
+ PS_NUMBER, PS_VAR1, PS_END
+ }
+ state;
+ char c;
+
+ state = PS_INITIAL;
+ while (1) {
+ c = getsc();
+ /* State machine to figure out where the variable part ends. */
+ switch (state) {
+ case PS_INITIAL:
+ if (c == '#') {
+ state = PS_SAW_HASH;
+ break;
+ }
+ /* FALLTHROUGH */
+ case PS_SAW_HASH:
+ if (letter(c))
+ state = PS_IDENT;
+ else if (digit(c))
+ state = PS_NUMBER;
+ else if (ctype(c, C_VAR1))
+ state = PS_VAR1;
+ else
+ state = PS_END;
+ break;
+ case PS_IDENT:
+ if (!letnum(c)) {
+ state = PS_END;
+ if (c == '[') {
+ char *tmp, *p;
+
+ if (!arraysub(&tmp))
+ yyerror("missing ]\n");
+ *wp++ = c;
+ for (p = tmp; *p; ) {
+ Xcheck(*wsp, wp);
+ *wp++ = *p++;
+ }
+ afree(tmp, ATEMP);
+ c = getsc(); /* the ] */
+ }
+ }
+ break;
+ case PS_NUMBER:
+ if (!digit(c))
+ state = PS_END;
+ break;
+ case PS_VAR1:
+ state = PS_END;
+ break;
+ case PS_END: /* keep gcc happy */
+ break;
+ }
+ if (state == PS_END) {
+ *wp++ = '\0'; /* end of variable part */
+ ungetsc(c);
+ break;
+ }
+ Xcheck(*wsp, wp);
+ *wp++ = c;
+ }
+ return wp;
+}
+
+/*
+ * Save an array subscript - returns true if matching bracket found, false
+ * if eof or newline was found.
+ * (Returned string double null terminated)
+ */
+static int
+arraysub(char **strp)
+{
+ XString ws;
+ char *wp;
+ char c;
+ int depth = 1; /* we are just past the initial [ */
+
+ Xinit(ws, wp, 32, ATEMP);
+
+ do {
+ c = getsc();
+ Xcheck(ws, wp);
+ *wp++ = c;
+ if (c == '[')
+ depth++;
+ else if (c == ']')
+ depth--;
+ } while (depth > 0 && c && c != '\n');
+
+ *wp++ = '\0';
+ *strp = Xclose(ws, wp);
+
+ return depth == 0 ? 1 : 0;
+}
+
+/* Unget a char: handles case when we are already at the start of the buffer */
+static const char *
+ungetsc(int c)
+{
+ if (backslash_skip)
+ backslash_skip--;
+ /* Don't unget eof... */
+ if (source->str == null && c == '\0')
+ return source->str;
+ if (source->str > source->start)
+ source->str--;
+ else {
+ Source *s;
+
+ s = pushs(SREREAD, source->areap);
+ s->ugbuf[0] = c; s->ugbuf[1] = '\0';
+ s->start = s->str = s->ugbuf;
+ s->next = source;
+ source = s;
+ }
+ return source->str;
+}
+
+
+/* Called to get a char that isn't a \newline sequence. */
+static int
+getsc_bn(void)
+{
+ int c, c2;
+
+ if (ignore_backslash_newline)
+ return getsc_();
+
+ if (backslash_skip == 1) {
+ backslash_skip = 2;
+ return getsc_();
+ }
+
+ backslash_skip = 0;
+
+ while (1) {
+ c = getsc_();
+ if (c == '\\') {
+ if ((c2 = getsc_()) == '\n')
+ /* ignore the \newline; get the next char... */
+ continue;
+ ungetsc(c2);
+ backslash_skip = 1;
+ }
+ return c;
+ }
+}
+
+static Lex_state *
+push_state_(State_info *si, Lex_state *old_end)
+{
+ Lex_state *new = alloc(sizeof(Lex_state) * STATE_BSIZE, ATEMP);
+
+ new[0].ls_info.base = old_end;
+ si->base = &new[0];
+ si->end = &new[STATE_BSIZE];
+ return &new[1];
+}
+
+static Lex_state *
+pop_state_(State_info *si, Lex_state *old_end)
+{
+ Lex_state *old_base = si->base;
+
+ si->base = old_end->ls_info.base - STATE_BSIZE;
+ si->end = old_end->ls_info.base;
+
+ afree(old_base, ATEMP);
+
+ return si->base + STATE_BSIZE - 1;
+}
diff --git a/lex.h b/lex.h
@@ -0,0 +1,132 @@
+/* $OpenBSD: lex.h,v 1.13 2013/03/03 19:11:34 guenther Exp $ */
+
+/*
+ * Source input, lexer and parser
+ */
+
+/* $From: lex.h,v 1.4 1994/05/31 13:34:34 michael Exp $ */
+
+#define IDENT 64
+
+typedef struct source Source;
+struct source {
+ const char *str; /* input pointer */
+ int type; /* input type */
+ const char *start; /* start of current buffer */
+ union {
+ char **strv; /* string [] */
+ struct shf *shf; /* shell file */
+ struct tbl *tblp; /* alias (SALIAS) */
+ char *freeme; /* also for SREREAD */
+ } u;
+ char ugbuf[2]; /* buffer for ungetsc() (SREREAD) and
+ * alias (SALIAS) */
+ int line; /* line number */
+ int cmd_offset; /* line number - command number */
+ int errline; /* line the error occurred on (0 if not set) */
+ const char *file; /* input file name */
+ int flags; /* SF_* */
+ Area *areap;
+ XString xs; /* input buffer */
+ Source *next; /* stacked source */
+};
+
+/* Source.type values */
+#define SEOF 0 /* input EOF */
+#define SFILE 1 /* file input */
+#define SSTDIN 2 /* read stdin */
+#define SSTRING 3 /* string */
+#define SWSTR 4 /* string without \n */
+#define SWORDS 5 /* string[] */
+#define SWORDSEP 6 /* string[] separator */
+#define SALIAS 7 /* alias expansion */
+#define SREREAD 8 /* read ahead to be re-scanned */
+
+/* Source.flags values */
+#define SF_ECHO BIT(0) /* echo input to shlout */
+#define SF_ALIAS BIT(1) /* faking space at end of alias */
+#define SF_ALIASEND BIT(2) /* faking space at end of alias */
+#define SF_TTY BIT(3) /* type == SSTDIN & it is a tty */
+
+/*
+ * states while lexing word
+ */
+#define SBASE 0 /* outside any lexical constructs */
+#define SWORD 1 /* implicit quoting for substitute() */
+#define SLETPAREN 2 /* inside (( )), implicit quoting */
+#define SSQUOTE 3 /* inside '' */
+#define SDQUOTE 4 /* inside "" */
+#define SBRACE 5 /* inside ${} */
+#define SCSPAREN 6 /* inside $() */
+#define SBQUOTE 7 /* inside `` */
+#define SASPAREN 8 /* inside $(( )) */
+#define SHEREDELIM 9 /* parsing <<,<<- delimiter */
+#define SHEREDQUOTE 10 /* parsing " in <<,<<- delimiter */
+#define SPATTERN 11 /* parsing *(...|...) pattern (*+?@!) */
+#define STBRACE 12 /* parsing ${..[#%]..} */
+#define SBRACEQ 13 /* inside "${}" */
+
+typedef union {
+ int i;
+ char *cp;
+ char **wp;
+ struct op *o;
+ struct ioword *iop;
+} YYSTYPE;
+
+/* If something is added here, add it to tokentab[] in syn.c as well */
+#define LWORD 256
+#define LOGAND 257 /* && */
+#define LOGOR 258 /* || */
+#define BREAK 259 /* ;; */
+#define IF 260
+#define THEN 261
+#define ELSE 262
+#define ELIF 263
+#define FI 264
+#define CASE 265
+#define ESAC 266
+#define FOR 267
+#define SELECT 268
+#define WHILE 269
+#define UNTIL 270
+#define DO 271
+#define DONE 272
+#define IN 273
+#define FUNCTION 274
+#define TIME 275
+#define REDIR 276
+#define MDPAREN 277 /* (( )) */
+#define BANG 278 /* ! */
+#define DBRACKET 279 /* [[ .. ]] */
+#define COPROC 280 /* |& */
+#define YYERRCODE 300
+
+/* flags to yylex */
+#define CONTIN BIT(0) /* skip new lines to complete command */
+#define ONEWORD BIT(1) /* single word for substitute() */
+#define ALIAS BIT(2) /* recognize alias */
+#define KEYWORD BIT(3) /* recognize keywords */
+#define LETEXPR BIT(4) /* get expression inside (( )) */
+#define VARASN BIT(5) /* check for var=word */
+#define ARRAYVAR BIT(6) /* parse x[1 & 2] as one word */
+#define ESACONLY BIT(7) /* only accept esac keyword */
+#define CMDWORD BIT(8) /* parsing simple command (alias related) */
+#define HEREDELIM BIT(9) /* parsing <<,<<- delimiter */
+#define HEREDOC BIT(10) /* parsing heredoc */
+#define UNESCAPE BIT(11) /* remove backslashes */
+
+#define HERES 10 /* max << in line */
+
+EXTERN Source *source; /* yyparse/yylex source */
+EXTERN YYSTYPE yylval; /* result from yylex */
+EXTERN struct ioword *heres [HERES], **herep;
+EXTERN char ident [IDENT+1];
+
+#ifdef HISTORY
+# define HISTORYSIZE 500 /* size of saved history */
+
+EXTERN char **history; /* saved commands */
+EXTERN char **histptr; /* last history item */
+EXTERN int histsize; /* history size */
+#endif /* HISTORY */
diff --git a/mail.c b/mail.c
@@ -0,0 +1,196 @@
+/* $OpenBSD: mail.c,v 1.17 2013/11/28 10:33:37 sobrado Exp $ */
+
+/*
+ * Mailbox checking code by Robert J. Gibson, adapted for PD ksh by
+ * John R. MacMillan
+ */
+
+#include "config.h"
+
+#include "sh.h"
+#include <sys/stat.h>
+#include <time.h>
+
+#define MBMESSAGE "you have mail in $_"
+
+typedef struct mbox {
+ struct mbox *mb_next; /* next mbox in list */
+ char *mb_path; /* path to mail file */
+ char *mb_msg; /* to announce arrival of new mail */
+ time_t mb_mtime; /* mtime of mail file */
+} mbox_t;
+
+/*
+ * $MAILPATH is a linked list of mboxes. $MAIL is a treated as a
+ * special case of $MAILPATH, where the list has only one node. The
+ * same list is used for both since they are exclusive.
+ */
+
+static mbox_t *mplist;
+static mbox_t mbox;
+static time_t mlastchkd; /* when mail was last checked */
+static time_t mailcheck_interval;
+
+static void munset(mbox_t *); /* free mlist and mval */
+static mbox_t * mballoc(char *, char *); /* allocate a new mbox */
+static void mprintit(mbox_t *);
+
+void
+mcheck(void)
+{
+ mbox_t *mbp;
+ time_t now;
+ struct tbl *vp;
+ struct stat stbuf;
+
+ now = time(NULL);
+ if (mlastchkd == 0)
+ mlastchkd = now;
+ if (now - mlastchkd >= mailcheck_interval) {
+ mlastchkd = now;
+
+ if (mplist)
+ mbp = mplist;
+ else if ((vp = global("MAIL")) && (vp->flag & ISSET))
+ mbp = &mbox;
+ else
+ mbp = NULL;
+
+ while (mbp) {
+ if (mbp->mb_path && stat(mbp->mb_path, &stbuf) == 0 &&
+ S_ISREG(stbuf.st_mode)) {
+ if (stbuf.st_size &&
+ mbp->mb_mtime != stbuf.st_mtime &&
+ stbuf.st_atime <= stbuf.st_mtime)
+ mprintit(mbp);
+ mbp->mb_mtime = stbuf.st_mtime;
+ } else {
+ /*
+ * Some mail readers remove the mail
+ * file if all mail is read. If file
+ * does not exist, assume this is the
+ * case and set mtime to zero.
+ */
+ mbp->mb_mtime = 0;
+ }
+ mbp = mbp->mb_next;
+ }
+ }
+}
+
+void
+mcset(long int interval)
+{
+ mailcheck_interval = interval;
+}
+
+void
+mbset(char *p)
+{
+ struct stat stbuf;
+
+ if (mbox.mb_msg)
+ afree((void *)mbox.mb_msg, APERM);
+ if (mbox.mb_path)
+ afree((void *)mbox.mb_path, APERM);
+ /* Save a copy to protect from export (which munges the string) */
+ mbox.mb_path = str_save(p, APERM);
+ mbox.mb_msg = NULL;
+ if (p && stat(p, &stbuf) == 0 && S_ISREG(stbuf.st_mode))
+ mbox.mb_mtime = stbuf.st_mtime;
+ else
+ mbox.mb_mtime = 0;
+}
+
+void
+mpset(char *mptoparse)
+{
+ mbox_t *mbp;
+ char *mpath, *mmsg, *mval;
+ char *p;
+
+ munset( mplist );
+ mplist = NULL;
+ mval = str_save(mptoparse, APERM);
+ while (mval) {
+ mpath = mval;
+ if ((mval = strchr(mval, ':')) != NULL) {
+ *mval = '\0';
+ mval++;
+ }
+ /* POSIX/bourne-shell say file%message */
+ for (p = mpath; (mmsg = strchr(p, '%')); ) {
+ /* a literal percent? (POSIXism) */
+ if (mmsg[-1] == '\\') {
+ /* use memmove() to avoid overlap problems */
+ memmove(mmsg - 1, mmsg, strlen(mmsg) + 1);
+ p = mmsg + 1;
+ continue;
+ }
+ break;
+ }
+ /* at&t ksh says file?message */
+ if (!mmsg && !Flag(FPOSIX))
+ mmsg = strchr(mpath, '?');
+ if (mmsg) {
+ *mmsg = '\0';
+ mmsg++;
+ }
+ mbp = mballoc(mpath, mmsg);
+ mbp->mb_next = mplist;
+ mplist = mbp;
+ }
+}
+
+static void
+munset(mbox_t *mlist)
+{
+ mbox_t *mbp;
+
+ while (mlist != NULL) {
+ mbp = mlist;
+ mlist = mbp->mb_next;
+ if (!mlist)
+ afree((void *)mbp->mb_path, APERM);
+ afree((void *)mbp, APERM);
+ }
+}
+
+static mbox_t *
+mballoc(char *p, char *m)
+{
+ struct stat stbuf;
+ mbox_t *mbp;
+
+ mbp = (mbox_t *)alloc(sizeof(mbox_t), APERM);
+ mbp->mb_next = NULL;
+ mbp->mb_path = p;
+ mbp->mb_msg = m;
+ if (stat(mbp->mb_path, &stbuf) == 0 && S_ISREG(stbuf.st_mode))
+ mbp->mb_mtime = stbuf.st_mtime;
+ else
+ mbp->mb_mtime = 0;
+ return(mbp);
+}
+
+static void
+mprintit(mbox_t *mbp)
+{
+ struct tbl *vp;
+
+#if 0
+ /*
+ * I doubt this $_ overloading is bad in /bin/sh mode. Anyhow, we
+ * crash as the code looks now if we do not set vp. Now, this is
+ * easy to fix too, but I'd like to see what POSIX says before doing
+ * a change like that.
+ */
+ if (!Flag(FSH))
+#endif
+ /* Ignore setstr errors here (arbitrary) */
+ setstr((vp = local("_", false)), mbp->mb_path, KSH_RETURN_ERROR);
+
+ shellf("%s\n", substitute(mbp->mb_msg ? mbp->mb_msg : MBMESSAGE, 0));
+
+ unset(vp, 0);
+}
diff --git a/main.c b/main.c
@@ -0,0 +1,787 @@
+/* $OpenBSD: main.c,v 1.54 2013/11/28 10:33:37 sobrado Exp $ */
+
+/*
+ * startup, main loop, environments and error handling
+ */
+
+#define EXTERN /* define EXTERNs in sh.h */
+
+#include "sh.h"
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <pwd.h>
+
+extern char **environ;
+
+/*
+ * global data
+ */
+
+static void reclaim(void);
+static void remove_temps(struct temp *tp);
+static int is_restricted(char *name);
+static void init_username(void);
+
+/*
+ * shell initialization
+ */
+
+static const char initifs[] = "IFS= \t\n";
+
+static const char initsubs[] = "${PS2=> } ${PS3=#? } ${PS4=+ }";
+
+static const char *initcoms [] = {
+ "typeset", "-r", "KSH_VERSION", NULL,
+ "typeset", "-x", "SHELL", "PATH", "HOME", NULL,
+ "typeset", "-i", "PPID", NULL,
+ "typeset", "-i", "OPTIND=1", NULL,
+ "eval", "typeset -i RANDOM MAILCHECK=\"${MAILCHECK-600}\" SECONDS=\"${SECONDS-0}\" TMOUT=\"${TMOUT-0}\"", NULL,
+ "alias",
+ /* Standard ksh aliases */
+ "hash=alias -t", /* not "alias -t --": hash -r needs to work */
+ "type=whence -v",
+#ifdef JOBS
+ "stop=kill -STOP",
+#endif
+ "autoload=typeset -fu",
+ "functions=typeset -f",
+#ifdef HISTORY
+ "history=fc -l",
+#endif /* HISTORY */
+ "integer=typeset -i",
+ "nohup=nohup ",
+ "local=typeset",
+ "r=fc -e -",
+ /* Aliases that are builtin commands in at&t */
+ "login=exec login",
+ NULL,
+ /* this is what at&t ksh seems to track, with the addition of emacs */
+ "alias", "-tU",
+ "cat", "cc", "chmod", "cp", "date", "ed", "emacs", "grep", "ls",
+ "mail", "make", "mv", "pr", "rm", "sed", "sh", "vi", "who",
+ NULL,
+ NULL
+};
+
+char username[MAXLOGNAME];
+
+#define version_param (initcoms[2])
+
+/* The shell uses its own variation on argv, to build variables like
+ * $0 and $@.
+ * If we need to alter argv, allocate a new array first since
+ * modifying the original argv will modify ps output.
+ */
+static char **
+make_argv(int argc, char *argv[])
+{
+ int i;
+ char **nargv = argv;
+
+ if (strcmp(argv[0], kshname) != 0) {
+ nargv = alloc(sizeof(char *) * (argc + 1), &aperm);
+ nargv[0] = (char *) kshname;
+ for (i = 1; i < argc; i++)
+ nargv[i] = argv[i];
+ nargv[i] = NULL;
+ }
+
+ return nargv;
+}
+
+int
+main(int argc, char *argv[])
+{
+ int i;
+ int argi;
+ Source *s;
+ struct block *l;
+ int restricted, errexit;
+ char **wp;
+ struct env env;
+ pid_t ppid;
+
+ /* make sure argv[] is sane */
+ if (!*argv) {
+ static const char *empty_argv[] = {
+ "ksh", (char *) 0
+ };
+
+ argv = (char **) empty_argv;
+ argc = 1;
+ }
+ kshname = *argv;
+
+ ainit(&aperm); /* initialize permanent Area */
+
+ /* set up base environment */
+ memset(&env, 0, sizeof(env));
+ env.type = E_NONE;
+ ainit(&env.area);
+ e = &env;
+ newblock(); /* set up global l->vars and l->funs */
+
+ /* Do this first so output routines (eg, errorf, shellf) can work */
+ initio();
+
+ initvar();
+
+ initctypes();
+
+ inittraps();
+
+ coproc_init();
+
+ /* set up variable and command dictionaries */
+ ktinit(&taliases, APERM, 0);
+ ktinit(&aliases, APERM, 0);
+ ktinit(&homedirs, APERM, 0);
+
+ /* define shell keywords */
+ initkeywords();
+
+ /* define built-in commands */
+ ktinit(&builtins, APERM, 64); /* must be 2^n (currently 40 builtins) */
+ for (i = 0; shbuiltins[i].name != NULL; i++)
+ builtin(shbuiltins[i].name, shbuiltins[i].func);
+ for (i = 0; kshbuiltins[i].name != NULL; i++)
+ builtin(kshbuiltins[i].name, kshbuiltins[i].func);
+
+ init_histvec();
+
+ def_path = _PATH_DEFPATH;
+ {
+ size_t len = confstr(_CS_PATH, (char *) 0, 0);
+ char *new;
+
+ if (len > 0) {
+ confstr(_CS_PATH, new = alloc(len + 1, APERM), len + 1);
+ def_path = new;
+ }
+ }
+
+ /* Set PATH to def_path (will set the path global variable).
+ * (import of environment below will probably change this setting).
+ */
+ {
+ struct tbl *vp = global("PATH");
+ /* setstr can't fail here */
+ setstr(vp, def_path, KSH_RETURN_ERROR);
+ }
+
+
+ /* Turn on nohup by default for now - will change to off
+ * by default once people are aware of its existence
+ * (at&t ksh does not have a nohup option - it always sends
+ * the hup).
+ */
+ Flag(FNOHUP) = 1;
+
+ /* Turn on brace expansion by default. At&t ksh's that have
+ * alternation always have it on. BUT, posix doesn't have
+ * brace expansion, so set this before setting up FPOSIX
+ * (change_flag() clears FBRACEEXPAND when FPOSIX is set).
+ */
+#ifdef BRACE_EXPAND
+ Flag(FBRACEEXPAND) = 1;
+#endif /* BRACE_EXPAND */
+
+ /* set posix flag just before environment so that it will have
+ * exactly the same effect as the POSIXLY_CORRECT environment
+ * variable. If this needs to be done sooner to ensure correct posix
+ * operation, an initial scan of the environment will also have
+ * done sooner.
+ */
+#ifdef POSIXLY_CORRECT
+ change_flag(FPOSIX, OF_SPECIAL, 1);
+#endif /* POSIXLY_CORRECT */
+
+ /* Check to see if we're /bin/sh. */
+ if (!strcmp(kshname, "sh") || !strcmp(kshname, "-sh") ||
+ (strlen(kshname) >= 3 &&
+ !strcmp(&kshname[strlen(kshname) - 3], "/sh"))) {
+ Flag(FSH) = 1;
+ version_param = "SH_VERSION";
+ }
+
+ /* Set edit mode to emacs by default, may be overridden
+ * by the environment or the user. Also, we want tab completion
+ * on in vi by default. */
+#if defined(EDIT) && defined(EMACS)
+ change_flag(FEMACS, OF_SPECIAL, 1);
+#endif /* EDIT && EMACS */
+#if defined(EDIT) && defined(VI)
+ Flag(FVITABCOMPLETE) = 1;
+#endif /* EDIT && VI */
+
+ /* import environment */
+ if (environ != NULL)
+ for (wp = environ; *wp != NULL; wp++)
+ typeset(*wp, IMPORT|EXPORT, 0, 0, 0);
+
+ kshpid = procpid = getpid();
+ typeset(initifs, 0, 0, 0, 0); /* for security */
+
+ /* assign default shell variable values */
+ substitute(initsubs, 0);
+
+ /* Figure out the current working directory and set $PWD */
+ {
+ struct stat s_pwd, s_dot;
+ struct tbl *pwd_v = global("PWD");
+ char *pwd = str_val(pwd_v);
+ char *pwdx = pwd;
+
+ /* Try to use existing $PWD if it is valid */
+ if (pwd[0] != '/' ||
+ stat(pwd, &s_pwd) < 0 || stat(".", &s_dot) < 0 ||
+ s_pwd.st_dev != s_dot.st_dev ||
+ s_pwd.st_ino != s_dot.st_ino)
+ pwdx = (char *) 0;
+ set_current_wd(pwdx);
+ if (current_wd[0])
+ simplify_path(current_wd);
+ /* Only set pwd if we know where we are or if it had a
+ * bogus value
+ */
+ if (current_wd[0] || pwd != null)
+ /* setstr can't fail here */
+ setstr(pwd_v, current_wd, KSH_RETURN_ERROR);
+ }
+ ppid = getppid();
+ setint(global("PPID"), (long) ppid);
+ /* setstr can't fail here */
+ setstr(global(version_param), ksh_version, KSH_RETURN_ERROR);
+
+ /* execute initialization statements */
+ for (wp = (char**) initcoms; *wp != NULL; wp++) {
+ shcomexec(wp);
+ for (; *wp != NULL; wp++)
+ ;
+ }
+
+
+ ksheuid = geteuid();
+ init_username();
+ safe_prompt = ksheuid ? "$ " : "# ";
+ {
+ struct tbl *vp = global("PS1");
+
+ /* Set PS1 if it isn't set, or we are root and prompt doesn't
+ * contain a # or \$ (only in ksh mode).
+ */
+ if (!(vp->flag & ISSET) ||
+ (!ksheuid && !strchr(str_val(vp), '#') &&
+ (Flag(FSH) || !strstr(str_val(vp), "\\$"))))
+ /* setstr can't fail here */
+ setstr(vp, safe_prompt, KSH_RETURN_ERROR);
+ }
+
+ /* Set this before parsing arguments */
+ Flag(FPRIVILEGED) = getuid() != ksheuid || getgid() != getegid();
+
+ /* this to note if monitor is set on command line (see below) */
+ Flag(FMONITOR) = 127;
+ argi = parse_args(argv, OF_CMDLINE, (int *) 0);
+ if (argi < 0)
+ exit(1);
+
+ if (Flag(FCOMMAND)) {
+ s = pushs(SSTRING, ATEMP);
+ if (!(s->start = s->str = argv[argi++]))
+ errorf("-c requires an argument");
+ if (argv[argi])
+ kshname = argv[argi++];
+ } else if (argi < argc && !Flag(FSTDIN)) {
+ s = pushs(SFILE, ATEMP);
+ s->file = argv[argi++];
+ s->u.shf = shf_open(s->file, O_RDONLY, 0, SHF_MAPHI|SHF_CLEXEC);
+ if (s->u.shf == NULL) {
+ exstat = 127; /* POSIX */
+ errorf("%s: %s", s->file, strerror(errno));
+ }
+ kshname = s->file;
+ } else {
+ Flag(FSTDIN) = 1;
+ s = pushs(SSTDIN, ATEMP);
+ s->file = "<stdin>";
+ s->u.shf = shf_fdopen(0, SHF_RD | can_seek(0),
+ (struct shf *) 0);
+ if (isatty(0) && isatty(2)) {
+ Flag(FTALKING) = Flag(FTALKING_I) = 1;
+ /* The following only if isatty(0) */
+ s->flags |= SF_TTY;
+ s->u.shf->flags |= SHF_INTERRUPT;
+ s->file = (char *) 0;
+ }
+ }
+
+ /* This bizarreness is mandated by POSIX */
+ {
+ struct stat s_stdin;
+
+ if (fstat(0, &s_stdin) >= 0 && S_ISCHR(s_stdin.st_mode) &&
+ Flag(FTALKING))
+ reset_nonblock(0);
+ }
+
+ /* initialize job control */
+ i = Flag(FMONITOR) != 127;
+ Flag(FMONITOR) = 0;
+ j_init(i);
+#ifdef EDIT
+ /* Do this after j_init(), as tty_fd is not initialized 'til then */
+ if (Flag(FTALKING))
+ x_init();
+#endif
+
+ l = e->loc;
+ l->argv = make_argv(argc - (argi - 1), &argv[argi - 1]);
+ l->argc = argc - argi;
+ getopts_reset(1);
+
+ /* Disable during .profile/ENV reading */
+ restricted = Flag(FRESTRICTED);
+ Flag(FRESTRICTED) = 0;
+ errexit = Flag(FERREXIT);
+ Flag(FERREXIT) = 0;
+
+ /* Do this before profile/$ENV so that if it causes problems in them,
+ * user will know why things broke.
+ */
+ if (!current_wd[0] && Flag(FTALKING))
+ warningf(false, "Cannot determine current working directory");
+
+ if (Flag(FLOGIN)) {
+ include(KSH_SYSTEM_PROFILE, 0, (char **) 0, 1);
+ if (!Flag(FPRIVILEGED))
+ include(substitute("$HOME/.profile", 0), 0,
+ (char **) 0, 1);
+ }
+
+ if (Flag(FPRIVILEGED))
+ include("/etc/suid_profile", 0, (char **) 0, 1);
+ else if (Flag(FTALKING)) {
+ char *env_file;
+
+ /* include $ENV */
+ env_file = str_val(global("ENV"));
+
+#ifdef DEFAULT_ENV
+ /* If env isn't set, include default environment */
+ if (env_file == null)
+ env_file = DEFAULT_ENV;
+#endif /* DEFAULT_ENV */
+ env_file = substitute(env_file, DOTILDE);
+ if (*env_file != '\0')
+ include(env_file, 0, (char **) 0, 1);
+ }
+
+ if (is_restricted(argv[0]) || is_restricted(str_val(global("SHELL"))))
+ restricted = 1;
+ if (restricted) {
+ static const char *const restr_com[] = {
+ "typeset", "-r", "PATH",
+ "ENV", "SHELL",
+ (char *) 0
+ };
+ shcomexec((char **) restr_com);
+ /* After typeset command... */
+ Flag(FRESTRICTED) = 1;
+ }
+ if (errexit)
+ Flag(FERREXIT) = 1;
+
+ if (Flag(FTALKING)) {
+ hist_init(s);
+ alarm_init();
+ } else
+ Flag(FTRACKALL) = 1; /* set after ENV */
+
+ shell(s, true); /* doesn't return */
+ return 0;
+}
+
+static void
+init_username(void)
+{
+ char *p;
+ struct tbl *vp = global("USER");
+
+ if (vp->flag & ISSET)
+ p = ksheuid == 0 ? "root" : str_val(vp);
+ else
+ p = getlogin();
+
+ strlcpy(username, p != NULL ? p : "?", sizeof username);
+}
+
+int
+include(const char *name, int argc, char **argv, int intr_ok)
+{
+ Source *volatile s = NULL;
+ struct shf *shf;
+ char **volatile old_argv;
+ volatile int old_argc;
+ int i;
+
+ shf = shf_open(name, O_RDONLY, 0, SHF_MAPHI|SHF_CLEXEC);
+ if (shf == NULL)
+ return -1;
+
+ if (argv) {
+ old_argv = e->loc->argv;
+ old_argc = e->loc->argc;
+ } else {
+ old_argv = (char **) 0;
+ old_argc = 0;
+ }
+ newenv(E_INCL);
+ i = sigsetjmp(e->jbuf, 0);
+ if (i) {
+ quitenv(s ? s->u.shf : NULL);
+ if (old_argv) {
+ e->loc->argv = old_argv;
+ e->loc->argc = old_argc;
+ }
+ switch (i) {
+ case LRETURN:
+ case LERROR:
+ return exstat & 0xff; /* see below */
+ case LINTR:
+ /* intr_ok is set if we are including .profile or $ENV.
+ * If user ^C's out, we don't want to kill the shell...
+ */
+ if (intr_ok && (exstat - 128) != SIGTERM)
+ return 1;
+ /* FALLTHROUGH */
+ case LEXIT:
+ case LLEAVE:
+ case LSHELL:
+ unwind(i);
+ /* NOTREACHED */
+ default:
+ internal_errorf(1, "include: %d", i);
+ /* NOTREACHED */
+ }
+ }
+ if (argv) {
+ e->loc->argv = argv;
+ e->loc->argc = argc;
+ }
+ s = pushs(SFILE, ATEMP);
+ s->u.shf = shf;
+ s->file = str_save(name, ATEMP);
+ i = shell(s, false);
+ quitenv(s->u.shf);
+ if (old_argv) {
+ e->loc->argv = old_argv;
+ e->loc->argc = old_argc;
+ }
+ return i & 0xff; /* & 0xff to ensure value not -1 */
+}
+
+/*
+ * spawn a command into a shell optionally keeping track of line
+ * number.
+ */
+int
+command(const char *comm, int line)
+{
+ Source *s;
+
+ s = pushs(SSTRING, ATEMP);
+ s->start = s->str = comm;
+ s->line = line;
+ return shell(s, false);
+}
+
+/*
+ * run the commands from the input source, returning status.
+ */
+int
+shell(Source *volatile s, volatile int toplevel)
+{
+ struct op *t;
+ volatile int wastty = s->flags & SF_TTY;
+ volatile int attempts = 13;
+ volatile int interactive = Flag(FTALKING) && toplevel;
+ Source *volatile old_source = source;
+ int i;
+
+ newenv(E_PARSE);
+ if (interactive)
+ really_exit = 0;
+ i = sigsetjmp(e->jbuf, 0);
+ if (i) {
+ switch (i) {
+ case LINTR: /* we get here if SIGINT not caught or ignored */
+ case LERROR:
+ case LSHELL:
+ if (interactive) {
+ if (i == LINTR)
+ shellf(newline);
+ /* Reset any eof that was read as part of a
+ * multiline command.
+ */
+ if (Flag(FIGNOREEOF) && s->type == SEOF &&
+ wastty)
+ s->type = SSTDIN;
+ /* Used by exit command to get back to
+ * top level shell. Kind of strange since
+ * interactive is set if we are reading from
+ * a tty, but to have stopped jobs, one only
+ * needs FMONITOR set (not FTALKING/SF_TTY)...
+ */
+ /* toss any input we have so far */
+ s->start = s->str = null;
+ break;
+ }
+ /* FALLTHROUGH */
+ case LEXIT:
+ case LLEAVE:
+ case LRETURN:
+ source = old_source;
+ quitenv(NULL);
+ unwind(i); /* keep on going */
+ /* NOTREACHED */
+ default:
+ source = old_source;
+ quitenv(NULL);
+ internal_errorf(1, "shell: %d", i);
+ /* NOTREACHED */
+ }
+ }
+
+ while (1) {
+ if (trap)
+ runtraps(0);
+
+ if (s->next == NULL) {
+ if (Flag(FVERBOSE))
+ s->flags |= SF_ECHO;
+ else
+ s->flags &= ~SF_ECHO;
+ }
+
+ if (interactive) {
+ got_sigwinch = 1;
+ j_notify();
+ mcheck();
+ set_prompt(PS1, s);
+ }
+
+ t = compile(s);
+ if (t != NULL && t->type == TEOF) {
+ if (wastty && Flag(FIGNOREEOF) && --attempts > 0) {
+ shellf("Use `exit' to leave ksh\n");
+ s->type = SSTDIN;
+ } else if (wastty && !really_exit &&
+ j_stopped_running()) {
+ really_exit = 1;
+ s->type = SSTDIN;
+ } else {
+ /* this for POSIX, which says EXIT traps
+ * shall be taken in the environment
+ * immediately after the last command
+ * executed.
+ */
+ if (toplevel)
+ unwind(LEXIT);
+ break;
+ }
+ }
+
+ if (t && (!Flag(FNOEXEC) || (s->flags & SF_TTY)))
+ exstat = execute(t, 0, NULL);
+
+ if (t != NULL && t->type != TEOF && interactive && really_exit)
+ really_exit = 0;
+
+ reclaim();
+ }
+ quitenv(NULL);
+ source = old_source;
+ return exstat;
+}
+
+/* return to closest error handler or shell(), exit if none found */
+void
+unwind(int i)
+{
+ /* ordering for EXIT vs ERR is a bit odd (this is what at&t ksh does) */
+ if (i == LEXIT || (Flag(FERREXIT) && (i == LERROR || i == LINTR) &&
+ sigtraps[SIGEXIT_].trap)) {
+ if (trap)
+ runtraps(0);
+ runtrap(&sigtraps[SIGEXIT_]);
+ i = LLEAVE;
+ } else if (Flag(FERREXIT) && (i == LERROR || i == LINTR)) {
+ if (trap)
+ runtraps(0);
+ runtrap(&sigtraps[SIGERR_]);
+ i = LLEAVE;
+ }
+ while (1) {
+ switch (e->type) {
+ case E_PARSE:
+ case E_FUNC:
+ case E_INCL:
+ case E_LOOP:
+ case E_ERRH:
+ siglongjmp(e->jbuf, i);
+ /* NOTREACHED */
+
+ case E_NONE:
+ if (i == LINTR)
+ e->flags |= EF_FAKE_SIGDIE;
+ /* FALLTHROUGH */
+
+ default:
+ quitenv(NULL);
+ }
+ }
+}
+
+void
+newenv(int type)
+{
+ struct env *ep;
+
+ ep = (struct env *) alloc(sizeof(*ep), ATEMP);
+ ep->type = type;
+ ep->flags = 0;
+ ainit(&ep->area);
+ ep->loc = e->loc;
+ ep->savefd = NULL;
+ ep->oenv = e;
+ ep->temps = NULL;
+ e = ep;
+}
+
+void
+quitenv(struct shf *shf)
+{
+ struct env *ep = e;
+ int fd;
+
+ if (ep->oenv && ep->oenv->loc != ep->loc)
+ popblock();
+ if (ep->savefd != NULL) {
+ for (fd = 0; fd < NUFILE; fd++)
+ /* if ep->savefd[fd] < 0, means fd was closed */
+ if (ep->savefd[fd])
+ restfd(fd, ep->savefd[fd]);
+ if (ep->savefd[2]) /* Clear any write errors */
+ shf_reopen(2, SHF_WR, shl_out);
+ }
+
+ /* Bottom of the stack.
+ * Either main shell is exiting or cleanup_parents_env() was called.
+ */
+ if (ep->oenv == NULL) {
+ if (ep->type == E_NONE) { /* Main shell exiting? */
+ if (Flag(FTALKING))
+ hist_finish();
+ j_exit();
+ if (ep->flags & EF_FAKE_SIGDIE) {
+ int sig = exstat - 128;
+
+ /* ham up our death a bit (at&t ksh
+ * only seems to do this for SIGTERM)
+ * Don't do it for SIGQUIT, since we'd
+ * dump a core..
+ */
+ if ((sig == SIGINT || sig == SIGTERM) &&
+ getpgrp() == kshpid) {
+ setsig(&sigtraps[sig], SIG_DFL,
+ SS_RESTORE_CURR|SS_FORCE);
+ kill(0, sig);
+ }
+ }
+ }
+ if (shf)
+ shf_close(shf);
+ reclaim();
+ exit(exstat);
+ }
+ if (shf)
+ shf_close(shf);
+ reclaim();
+
+ e = e->oenv;
+ afree(ep, ATEMP);
+}
+
+/* Called after a fork to cleanup stuff left over from parents environment */
+void
+cleanup_parents_env(void)
+{
+ struct env *ep;
+ int fd;
+
+ /* Don't clean up temporary files - parent will probably need them.
+ * Also, can't easily reclaim memory since variables, etc. could be
+ * anywhere.
+ */
+
+ /* close all file descriptors hiding in savefd */
+ for (ep = e; ep; ep = ep->oenv) {
+ if (ep->savefd) {
+ for (fd = 0; fd < NUFILE; fd++)
+ if (ep->savefd[fd] > 0)
+ close(ep->savefd[fd]);
+ afree(ep->savefd, &ep->area);
+ ep->savefd = (short *) 0;
+ }
+ }
+ e->oenv = (struct env *) 0;
+}
+
+/* Called just before an execve cleanup stuff temporary files */
+void
+cleanup_proc_env(void)
+{
+ struct env *ep;
+
+ for (ep = e; ep; ep = ep->oenv)
+ remove_temps(ep->temps);
+}
+
+/* remove temp files and free ATEMP Area */
+static void
+reclaim(void)
+{
+ remove_temps(e->temps);
+ e->temps = NULL;
+ afreeall(&e->area);
+}
+
+static void
+remove_temps(struct temp *tp)
+{
+
+ for (; tp != NULL; tp = tp->next)
+ if (tp->pid == procpid) {
+ unlink(tp->name);
+ }
+}
+
+/* Returns true if name refers to a restricted shell */
+static int
+is_restricted(char *name)
+{
+ char *p;
+
+ if ((p = strrchr(name, '/')))
+ name = p + 1;
+ /* accepts rsh, rksh, rpdksh, pdrksh */
+ if (strcmp(name, "rsh") && \
+ strcmp(name, "rksh") && \
+ strcmp(name, "rpdksh") && \
+ strcmp(name, "pdrksh"))
+ return(0);
+ else
+ return(1);
+
+}
diff --git a/misc.c b/misc.c
@@ -0,0 +1,1149 @@
+/* $OpenBSD: misc.c,v 1.38 2013/11/28 10:33:37 sobrado Exp $ */
+
+/*
+ * Miscellaneous functions
+ */
+
+#include "sh.h"
+#include <ctype.h>
+#include <sys/param.h> /* for MAXPATHLEN */
+#include "charclass.h"
+
+short ctypes [UCHAR_MAX+1]; /* type bits for unsigned char */
+
+static int do_gmatch(const unsigned char *, const unsigned char *,
+ const unsigned char *, const unsigned char *);
+static const unsigned char *cclass(const unsigned char *, int);
+
+/*
+ * Fast character classes
+ */
+void
+setctypes(const char *s, int t)
+{
+ int i;
+
+ if (t & C_IFS) {
+ for (i = 0; i < UCHAR_MAX+1; i++)
+ ctypes[i] &= ~C_IFS;
+ ctypes[0] |= C_IFS; /* include \0 in C_IFS */
+ }
+ while (*s != 0)
+ ctypes[(unsigned char) *s++] |= t;
+}
+
+void
+initctypes(void)
+{
+ int c;
+
+ for (c = 'a'; c <= 'z'; c++)
+ ctypes[c] |= C_ALPHA;
+ for (c = 'A'; c <= 'Z'; c++)
+ ctypes[c] |= C_ALPHA;
+ ctypes['_'] |= C_ALPHA;
+ setctypes("0123456789", C_DIGIT);
+ setctypes(" \t\n|&;<>()", C_LEX1); /* \0 added automatically */
+ setctypes("*@#!$-?", C_VAR1);
+ setctypes(" \t\n", C_IFSWS);
+ setctypes("=-+?", C_SUBOP1);
+ setctypes("#%", C_SUBOP2);
+ setctypes(" \n\t\"#$&'()*;<>?[\\`|", C_QUOTE);
+}
+
+/* convert unsigned long to base N string */
+
+char *
+ulton(long unsigned int n, int base)
+{
+ char *p;
+ static char buf [20];
+
+ p = &buf[sizeof(buf)];
+ *--p = '\0';
+ do {
+ *--p = "0123456789ABCDEF"[n%base];
+ n /= base;
+ } while (n != 0);
+ return p;
+}
+
+char *
+str_save(const char *s, Area *ap)
+{
+ size_t len;
+ char *p;
+
+ if (!s)
+ return NULL;
+ len = strlen(s)+1;
+ p = alloc(len, ap);
+ strlcpy(p, s, len);
+ return (p);
+}
+
+/* Allocate a string of size n+1 and copy upto n characters from the possibly
+ * null terminated string s into it. Always returns a null terminated string
+ * (unless n < 0).
+ */
+char *
+str_nsave(const char *s, int n, Area *ap)
+{
+ char *ns;
+
+ if (n < 0)
+ return 0;
+ ns = alloc(n + 1, ap);
+ ns[0] = '\0';
+ return strncat(ns, s, n);
+}
+
+/* called from expand.h:XcheckN() to grow buffer */
+char *
+Xcheck_grow_(XString *xsp, char *xp, int more)
+{
+ char *old_beg = xsp->beg;
+
+ xsp->len += more > xsp->len ? more : xsp->len;
+ xsp->beg = aresize(xsp->beg, xsp->len + 8, xsp->areap);
+ xsp->end = xsp->beg + xsp->len;
+ return xsp->beg + (xp - old_beg);
+}
+
+const struct option options[] = {
+ /* Special cases (see parse_args()): -A, -o, -s.
+ * Options are sorted by their longnames - the order of these
+ * entries MUST match the order of sh_flag F* enumerations in sh.h.
+ */
+ { "allexport", 'a', OF_ANY },
+#ifdef BRACE_EXPAND
+ { "braceexpand", 0, OF_ANY }, /* non-standard */
+#endif
+ { "bgnice", 0, OF_ANY },
+ { (char *) 0, 'c', OF_CMDLINE },
+ { "csh-history", 0, OF_ANY }, /* non-standard */
+#ifdef EMACS
+ { "emacs", 0, OF_ANY },
+ { "emacs-usemeta", 0, OF_ANY }, /* non-standard */
+#endif
+ { "errexit", 'e', OF_ANY },
+#ifdef EMACS
+ { "gmacs", 0, OF_ANY },
+#endif
+ { "ignoreeof", 0, OF_ANY },
+ { "interactive",'i', OF_CMDLINE },
+ { "keyword", 'k', OF_ANY },
+ { "login", 'l', OF_CMDLINE },
+ { "markdirs", 'X', OF_ANY },
+#ifdef JOBS
+ { "monitor", 'm', OF_ANY },
+#else /* JOBS */
+ { (char *) 0, 'm', 0 }, /* so FMONITOR not ifdef'd */
+#endif /* JOBS */
+ { "noclobber", 'C', OF_ANY },
+ { "noexec", 'n', OF_ANY },
+ { "noglob", 'f', OF_ANY },
+ { "nohup", 0, OF_ANY },
+ { "nolog", 0, OF_ANY }, /* no effect */
+#ifdef JOBS
+ { "notify", 'b', OF_ANY },
+#endif /* JOBS */
+ { "nounset", 'u', OF_ANY },
+ { "physical", 0, OF_ANY }, /* non-standard */
+ { "posix", 0, OF_ANY }, /* non-standard */
+ { "privileged", 'p', OF_ANY },
+ { "restricted", 'r', OF_CMDLINE },
+ { "sh", 0, OF_ANY }, /* non-standard */
+ { "stdin", 's', OF_CMDLINE }, /* pseudo non-standard */
+ { "trackall", 'h', OF_ANY },
+ { "verbose", 'v', OF_ANY },
+#ifdef VI
+ { "vi", 0, OF_ANY },
+ { "viraw", 0, OF_ANY }, /* no effect */
+ { "vi-show8", 0, OF_ANY }, /* non-standard */
+ { "vi-tabcomplete", 0, OF_ANY }, /* non-standard */
+ { "vi-esccomplete", 0, OF_ANY }, /* non-standard */
+#endif
+ { "xtrace", 'x', OF_ANY },
+ /* Anonymous flags: used internally by shell only
+ * (not visible to user)
+ */
+ { (char *) 0, 0, OF_INTERNAL }, /* FTALKING_I */
+};
+
+/*
+ * translate -o option into F* constant (also used for test -o option)
+ */
+int
+option(const char *n)
+{
+ int i;
+
+ for (i = 0; i < NELEM(options); i++)
+ if (options[i].name && strcmp(options[i].name, n) == 0)
+ return i;
+
+ return -1;
+}
+
+struct options_info {
+ int opt_width;
+ struct {
+ const char *name;
+ int flag;
+ } opts[NELEM(options)];
+};
+
+static char *options_fmt_entry(void *arg, int i, char *buf, int buflen);
+static void printoptions(int verbose);
+
+/* format a single select menu item */
+static char *
+options_fmt_entry(void *arg, int i, char *buf, int buflen)
+{
+ struct options_info *oi = (struct options_info *) arg;
+
+ shf_snprintf(buf, buflen, "%-*s %s",
+ oi->opt_width, oi->opts[i].name,
+ Flag(oi->opts[i].flag) ? "on" : "off");
+ return buf;
+}
+
+static void
+printoptions(int verbose)
+{
+ int i;
+
+ if (verbose) {
+ struct options_info oi;
+ int n, len;
+
+ /* verbose version */
+ shprintf("Current option settings\n");
+
+ for (i = n = oi.opt_width = 0; i < NELEM(options); i++)
+ if (options[i].name) {
+ len = strlen(options[i].name);
+ oi.opts[n].name = options[i].name;
+ oi.opts[n++].flag = i;
+ if (len > oi.opt_width)
+ oi.opt_width = len;
+ }
+ print_columns(shl_stdout, n, options_fmt_entry, &oi,
+ oi.opt_width + 5, 1);
+ } else {
+ /* short version ala ksh93 */
+ shprintf("set");
+ for (i = 0; i < NELEM(options); i++)
+ if (Flag(i) && options[i].name)
+ shprintf(" -o %s", options[i].name);
+ shprintf(newline);
+ }
+}
+
+char *
+getoptions(void)
+{
+ int i;
+ char m[(int) FNFLAGS + 1];
+ char *cp = m;
+
+ for (i = 0; i < NELEM(options); i++)
+ if (options[i].c && Flag(i))
+ *cp++ = options[i].c;
+ *cp = 0;
+ return str_save(m, ATEMP);
+}
+
+/* change a Flag(*) value; takes care of special actions */
+void
+change_flag(enum sh_flag f,
+ int what, /* flag to change */
+ int newval) /* what is changing the flag (command line vs set) */
+{
+ int oldval;
+
+ oldval = Flag(f);
+ Flag(f) = newval;
+#ifdef JOBS
+ if (f == FMONITOR) {
+ if (what != OF_CMDLINE && newval != oldval)
+ j_change();
+ } else
+#endif /* JOBS */
+#ifdef EDIT
+ if (0
+# ifdef VI
+ || f == FVI
+# endif /* VI */
+# ifdef EMACS
+ || f == FEMACS || f == FGMACS
+# endif /* EMACS */
+ )
+ {
+ if (newval) {
+# ifdef VI
+ Flag(FVI) = 0;
+# endif /* VI */
+# ifdef EMACS
+ Flag(FEMACS) = Flag(FGMACS) = 0;
+# endif /* EMACS */
+ Flag(f) = newval;
+ }
+ } else
+#endif /* EDIT */
+ /* Turning off -p? */
+ if (f == FPRIVILEGED && oldval && !newval) {
+ gid_t gid = getgid();
+
+ setresgid(gid, gid, gid);
+ setgroups(1, &gid);
+ setresuid(ksheuid, ksheuid, ksheuid);
+ } else if (f == FPOSIX && newval) {
+#ifdef BRACE_EXPAND
+ Flag(FBRACEEXPAND) = 0
+#endif /* BRACE_EXPAND */
+ ;
+ }
+ /* Changing interactive flag? */
+ if (f == FTALKING) {
+ if ((what == OF_CMDLINE || what == OF_SET) && procpid == kshpid)
+ Flag(FTALKING_I) = newval;
+ }
+}
+
+/* parse command line & set command arguments. returns the index of
+ * non-option arguments, -1 if there is an error.
+ */
+int
+parse_args(char **argv,
+ int what, /* OF_CMDLINE or OF_SET */
+ int *setargsp)
+{
+ static char cmd_opts[NELEM(options) + 3]; /* o:\0 */
+ static char set_opts[NELEM(options) + 5]; /* Ao;s\0 */
+ char *opts;
+ char *array = (char *) 0;
+ Getopt go;
+ int i, optc, set, sortargs = 0, arrayset = 0;
+
+ /* First call? Build option strings... */
+ if (cmd_opts[0] == '\0') {
+ char *p, *q;
+
+ /* see cmd_opts[] declaration */
+ strlcpy(cmd_opts, "o:", sizeof cmd_opts);
+ p = cmd_opts + strlen(cmd_opts);
+ /* see set_opts[] declaration */
+ strlcpy(set_opts, "A:o;s", sizeof set_opts);
+ q = set_opts + strlen(set_opts);
+ for (i = 0; i < NELEM(options); i++) {
+ if (options[i].c) {
+ if (options[i].flags & OF_CMDLINE)
+ *p++ = options[i].c;
+ if (options[i].flags & OF_SET)
+ *q++ = options[i].c;
+ }
+ }
+ *p = '\0';
+ *q = '\0';
+ }
+
+ if (what == OF_CMDLINE) {
+ char *p;
+ /* Set FLOGIN before parsing options so user can clear
+ * flag using +l.
+ */
+ Flag(FLOGIN) = (argv[0][0] == '-' ||
+ ((p = strrchr(argv[0], '/')) && *++p == '-'));
+ opts = cmd_opts;
+ } else
+ opts = set_opts;
+ ksh_getopt_reset(&go, GF_ERROR|GF_PLUSOPT);
+ while ((optc = ksh_getopt(argv, &go, opts)) != -1) {
+ set = (go.info & GI_PLUS) ? 0 : 1;
+ switch (optc) {
+ case 'A':
+ arrayset = set ? 1 : -1;
+ array = go.optarg;
+ break;
+
+ case 'o':
+ if (go.optarg == (char *) 0) {
+ /* lone -o: print options
+ *
+ * Note that on the command line, -o requires
+ * an option (ie, can't get here if what is
+ * OF_CMDLINE).
+ */
+ printoptions(set);
+ break;
+ }
+ i = option(go.optarg);
+ if (i >= 0 && set == Flag(i))
+ /* Don't check the context if the flag
+ * isn't changing - makes "set -o interactive"
+ * work if you're already interactive. Needed
+ * if the output of "set +o" is to be used.
+ */
+ ;
+ else if (i >= 0 && (options[i].flags & what))
+ change_flag((enum sh_flag) i, what, set);
+ else {
+ bi_errorf("%s: bad option", go.optarg);
+ return -1;
+ }
+ break;
+
+ case '?':
+ return -1;
+
+ default:
+ /* -s: sort positional params (at&t ksh stupidity) */
+ if (what == OF_SET && optc == 's') {
+ sortargs = 1;
+ break;
+ }
+ for (i = 0; i < NELEM(options); i++)
+ if (optc == options[i].c &&
+ (what & options[i].flags)) {
+ change_flag((enum sh_flag) i, what,
+ set);
+ break;
+ }
+ if (i == NELEM(options)) {
+ internal_errorf(1, "parse_args: `%c'", optc);
+ return -1; /* not reached */
+ }
+ }
+ }
+ if (!(go.info & GI_MINUSMINUS) && argv[go.optind] &&
+ (argv[go.optind][0] == '-' || argv[go.optind][0] == '+') &&
+ argv[go.optind][1] == '\0') {
+ /* lone - clears -v and -x flags */
+ if (argv[go.optind][0] == '-' && !Flag(FPOSIX))
+ Flag(FVERBOSE) = Flag(FXTRACE) = 0;
+ /* set skips lone - or + option */
+ go.optind++;
+ }
+ if (setargsp)
+ /* -- means set $#/$* even if there are no arguments */
+ *setargsp = !arrayset && ((go.info & GI_MINUSMINUS) ||
+ argv[go.optind]);
+
+ if (arrayset && (!*array || *skip_varname(array, false))) {
+ bi_errorf("%s: is not an identifier", array);
+ return -1;
+ }
+ if (sortargs) {
+ for (i = go.optind; argv[i]; i++)
+ ;
+ qsortp((void **) &argv[go.optind], (size_t) (i - go.optind),
+ xstrcmp);
+ }
+ if (arrayset) {
+ set_array(array, arrayset, argv + go.optind);
+ for (; argv[go.optind]; go.optind++)
+ ;
+ }
+
+ return go.optind;
+}
+
+/* parse a decimal number: returns 0 if string isn't a number, 1 otherwise */
+int
+getn(const char *as, int *ai)
+{
+ char *p;
+ long n;
+
+ n = strtol(as, &p, 10);
+
+ if (!*as || *p || INT_MIN >= n || n >= INT_MAX)
+ return 0;
+
+ *ai = (int)n;
+ return 1;
+}
+
+/* getn() that prints error */
+int
+bi_getn(const char *as, int *ai)
+{
+ int rv = getn(as, ai);
+
+ if (!rv)
+ bi_errorf("%s: bad number", as);
+ return rv;
+}
+
+/* -------- gmatch.c -------- */
+
+/*
+ * int gmatch(string, pattern)
+ * char *string, *pattern;
+ *
+ * Match a pattern as in sh(1).
+ * pattern character are prefixed with MAGIC by expand.
+ */
+
+int
+gmatch(const char *s, const char *p, int isfile)
+{
+ const char *se, *pe;
+
+ if (s == NULL || p == NULL)
+ return 0;
+ se = s + strlen(s);
+ pe = p + strlen(p);
+ /* isfile is false iff no syntax check has been done on
+ * the pattern. If check fails, just to a strcmp().
+ */
+ if (!isfile && !has_globbing(p, pe)) {
+ int len = pe - p + 1;
+ char tbuf[64];
+ char *t = len <= sizeof(tbuf) ? tbuf :
+ (char *) alloc(len, ATEMP);
+ debunk(t, p, len);
+ return !strcmp(t, s);
+ }
+ return do_gmatch((const unsigned char *) s, (const unsigned char *) se,
+ (const unsigned char *) p, (const unsigned char *) pe);
+}
+
+/* Returns if p is a syntacticly correct globbing pattern, false
+ * if it contains no pattern characters or if there is a syntax error.
+ * Syntax errors are:
+ * - [ with no closing ]
+ * - imbalanced $(...) expression
+ * - [...] and *(...) not nested (eg, [a$(b|]c), *(a[b|c]d))
+ */
+/*XXX
+- if no magic,
+ if dest given, copy to dst
+ return ?
+- if magic && (no globbing || syntax error)
+ debunk to dst
+ return ?
+- return ?
+*/
+int
+has_globbing(const char *xp, const char *xpe)
+{
+ const unsigned char *p = (const unsigned char *) xp;
+ const unsigned char *pe = (const unsigned char *) xpe;
+ int c;
+ int nest = 0, bnest = 0;
+ int saw_glob = 0;
+ int in_bracket = 0; /* inside [...] */
+
+ for (; p < pe; p++) {
+ if (!ISMAGIC(*p))
+ continue;
+ if ((c = *++p) == '*' || c == '?')
+ saw_glob = 1;
+ else if (c == '[') {
+ if (!in_bracket) {
+ saw_glob = 1;
+ in_bracket = 1;
+ if (ISMAGIC(p[1]) && p[2] == NOT)
+ p += 2;
+ if (ISMAGIC(p[1]) && p[2] == ']')
+ p += 2;
+ }
+ /* XXX Do we need to check ranges here? POSIX Q */
+ } else if (c == ']') {
+ if (in_bracket) {
+ if (bnest) /* [a*(b]) */
+ return 0;
+ in_bracket = 0;
+ }
+ } else if ((c & 0x80) && strchr("*+?@! ", c & 0x7f)) {
+ saw_glob = 1;
+ if (in_bracket)
+ bnest++;
+ else
+ nest++;
+ } else if (c == '|') {
+ if (in_bracket && !bnest) /* *(a[foo|bar]) */
+ return 0;
+ } else if (c == /*(*/ ')') {
+ if (in_bracket) {
+ if (!bnest--) /* *(a[b)c] */
+ return 0;
+ } else if (nest)
+ nest--;
+ }
+ /* else must be a MAGIC-MAGIC, or MAGIC-!, MAGIC--, MAGIC-]
+ MAGIC-{, MAGIC-,, MAGIC-} */
+ }
+ return saw_glob && !in_bracket && !nest;
+}
+
+/* Function must return either 0 or 1 (assumed by code for 0x80|'!') */
+static int
+do_gmatch(const unsigned char *s, const unsigned char *se,
+ const unsigned char *p, const unsigned char *pe)
+{
+ int sc, pc;
+ const unsigned char *prest, *psub, *pnext;
+ const unsigned char *srest;
+
+ if (s == NULL || p == NULL)
+ return 0;
+ while (p < pe) {
+ pc = *p++;
+ sc = s < se ? *s : '\0';
+ s++;
+ if (!ISMAGIC(pc)) {
+ if (sc != pc)
+ return 0;
+ continue;
+ }
+ switch (*p++) {
+ case '[':
+ if (sc == 0 || (p = cclass(p, sc)) == NULL)
+ return 0;
+ break;
+
+ case '?':
+ if (sc == 0)
+ return 0;
+ break;
+
+ case '*':
+ if (p == pe)
+ return 1;
+ s--;
+ do {
+ if (do_gmatch(s, se, p, pe))
+ return 1;
+ } while (s++ < se);
+ return 0;
+
+ /*
+ * [*+?@!](pattern|pattern|..)
+ *
+ * Not ifdef'd KSH as this is needed for ${..%..}, etc.
+ */
+ case 0x80|'+': /* matches one or more times */
+ case 0x80|'*': /* matches zero or more times */
+ if (!(prest = pat_scan(p, pe, 0)))
+ return 0;
+ s--;
+ /* take care of zero matches */
+ if (p[-1] == (0x80 | '*') &&
+ do_gmatch(s, se, prest, pe))
+ return 1;
+ for (psub = p; ; psub = pnext) {
+ pnext = pat_scan(psub, pe, 1);
+ for (srest = s; srest <= se; srest++) {
+ if (do_gmatch(s, srest, psub, pnext - 2) &&
+ (do_gmatch(srest, se, prest, pe) ||
+ (s != srest && do_gmatch(srest,
+ se, p - 2, pe))))
+ return 1;
+ }
+ if (pnext == prest)
+ break;
+ }
+ return 0;
+
+ case 0x80|'?': /* matches zero or once */
+ case 0x80|'@': /* matches one of the patterns */
+ case 0x80|' ': /* simile for @ */
+ if (!(prest = pat_scan(p, pe, 0)))
+ return 0;
+ s--;
+ /* Take care of zero matches */
+ if (p[-1] == (0x80 | '?') &&
+ do_gmatch(s, se, prest, pe))
+ return 1;
+ for (psub = p; ; psub = pnext) {
+ pnext = pat_scan(psub, pe, 1);
+ srest = prest == pe ? se : s;
+ for (; srest <= se; srest++) {
+ if (do_gmatch(s, srest, psub, pnext - 2) &&
+ do_gmatch(srest, se, prest, pe))
+ return 1;
+ }
+ if (pnext == prest)
+ break;
+ }
+ return 0;
+
+ case 0x80|'!': /* matches none of the patterns */
+ if (!(prest = pat_scan(p, pe, 0)))
+ return 0;
+ s--;
+ for (srest = s; srest <= se; srest++) {
+ int matched = 0;
+
+ for (psub = p; ; psub = pnext) {
+ pnext = pat_scan(psub, pe, 1);
+ if (do_gmatch(s, srest, psub,
+ pnext - 2)) {
+ matched = 1;
+ break;
+ }
+ if (pnext == prest)
+ break;
+ }
+ if (!matched &&
+ do_gmatch(srest, se, prest, pe))
+ return 1;
+ }
+ return 0;
+
+ default:
+ if (sc != p[-1])
+ return 0;
+ break;
+ }
+ }
+ return s == se;
+}
+
+static int
+posix_cclass(const unsigned char *pattern, int test, const unsigned char **ep)
+{
+ struct cclass *cc;
+ const unsigned char *colon;
+ size_t len;
+ int rval = 0;
+
+ if ((colon = strchr(pattern, ':')) == NULL || colon[1] != MAGIC) {
+ *ep = pattern - 2;
+ return -1;
+ }
+ *ep = colon + 3; /* skip MAGIC */
+ len = (size_t)(colon - pattern);
+
+ for (cc = cclasses; cc->name != NULL; cc++) {
+ if (!strncmp(pattern, cc->name, len) && cc->name[len] == '\0') {
+ if (cc->isctype(test))
+ rval = 1;
+ break;
+ }
+ }
+ if (cc->name == NULL) {
+ rval = -2; /* invalid character class */
+ }
+ return rval;
+}
+
+static const unsigned char *
+cclass(const unsigned char *p, int sub)
+{
+ int c, d, rv, not, found = 0;
+ const unsigned char *orig_p = p;
+
+ if ((not = (ISMAGIC(*p) && *++p == NOT)))
+ p++;
+ do {
+ /* check for POSIX character class (e.g. [[:alpha:]]) */
+ if ((p[0] == MAGIC && p[1] == '[' && p[2] == ':') ||
+ (p[0] == '[' && p[1] == ':')) {
+ do {
+ const char *pp = p + (*p == MAGIC) + 2;
+ rv = posix_cclass(pp, sub, &p);
+ switch (rv) {
+ case 1:
+ found = 1;
+ break;
+ case -2:
+ return NULL;
+ }
+ } while (rv != -1 && p[0] == MAGIC && p[1] == '[' && p[2] == ':');
+ if (p[0] == MAGIC && p[1] == ']')
+ break;
+ }
+
+ c = *p++;
+ if (ISMAGIC(c)) {
+ c = *p++;
+ if ((c & 0x80) && !ISMAGIC(c)) {
+ c &= 0x7f;/* extended pattern matching: *+?@! */
+ /* XXX the ( char isn't handled as part of [] */
+ if (c == ' ') /* simile for @: plain (..) */
+ c = '(' /*)*/;
+ }
+ }
+ if (c == '\0')
+ /* No closing ] - act as if the opening [ was quoted */
+ return sub == '[' ? orig_p : NULL;
+ if (ISMAGIC(p[0]) && p[1] == '-' &&
+ (!ISMAGIC(p[2]) || p[3] != ']')) {
+ p += 2; /* MAGIC- */
+ d = *p++;
+ if (ISMAGIC(d)) {
+ d = *p++;
+ if ((d & 0x80) && !ISMAGIC(d))
+ d &= 0x7f;
+ }
+ /* POSIX says this is an invalid expression */
+ if (c > d)
+ return NULL;
+ } else
+ d = c;
+ if (c == sub || (c <= sub && sub <= d))
+ found = 1;
+ } while (!(ISMAGIC(p[0]) && p[1] == ']'));
+
+ return (found != not) ? p+2 : NULL;
+}
+
+/* Look for next ) or | (if match_sep) in *(foo|bar) pattern */
+const unsigned char *
+pat_scan(const unsigned char *p, const unsigned char *pe, int match_sep)
+{
+ int nest = 0;
+
+ for (; p < pe; p++) {
+ if (!ISMAGIC(*p))
+ continue;
+ if ((*++p == /*(*/ ')' && nest-- == 0) ||
+ (*p == '|' && match_sep && nest == 0))
+ return ++p;
+ if ((*p & 0x80) && strchr("*+?@! ", *p & 0x7f))
+ nest++;
+ }
+ return (const unsigned char *) 0;
+}
+
+/*
+ * quick sort of array of generic pointers to objects.
+ */
+void
+qsortp(void **base, /* base address */
+ size_t n, /* elements */
+ int (*f) (const void *, const void *)) /* compare function */
+{
+ qsort(base, n, sizeof(char *), f);
+}
+
+int
+xstrcmp(const void *p1, const void *p2)
+{
+ return (strcmp(*(char **)p1, *(char **)p2));
+}
+
+/* Initialize a Getopt structure */
+void
+ksh_getopt_reset(Getopt *go, int flags)
+{
+ go->optind = 1;
+ go->optarg = (char *) 0;
+ go->p = 0;
+ go->flags = flags;
+ go->info = 0;
+ go->buf[1] = '\0';
+}
+
+
+/* getopt() used for shell built-in commands, the getopts command, and
+ * command line options.
+ * A leading ':' in options means don't print errors, instead return '?'
+ * or ':' and set go->optarg to the offending option character.
+ * If GF_ERROR is set (and option doesn't start with :), errors result in
+ * a call to bi_errorf().
+ *
+ * Non-standard features:
+ * - ';' is like ':' in options, except the argument is optional
+ * (if it isn't present, optarg is set to 0).
+ * Used for 'set -o'.
+ * - ',' is like ':' in options, except the argument always immediately
+ * follows the option character (optarg is set to the null string if
+ * the option is missing).
+ * Used for 'read -u2', 'print -u2' and fc -40.
+ * - '#' is like ':' in options, expect that the argument is optional
+ * and must start with a digit or be the string "unlimited". If the
+ * argument doesn't match, it is assumed to be missing and normal option
+ * processing continues (optarg is set to 0 if the option is missing).
+ * Used for 'typeset -LZ4' and 'ulimit -adunlimited'.
+ * - accepts +c as well as -c IF the GF_PLUSOPT flag is present. If an
+ * option starting with + is accepted, the GI_PLUS flag will be set
+ * in go->info.
+ */
+int
+ksh_getopt(char **argv, Getopt *go, const char *options)
+{
+ char c;
+ char *o;
+
+ if (go->p == 0 || (c = argv[go->optind - 1][go->p]) == '\0') {
+ char *arg = argv[go->optind], flag = arg ? *arg : '\0';
+
+ go->p = 1;
+ if (flag == '-' && arg[1] == '-' && arg[2] == '\0') {
+ go->optind++;
+ go->p = 0;
+ go->info |= GI_MINUSMINUS;
+ return -1;
+ }
+ if (arg == (char *) 0 ||
+ ((flag != '-' ) && /* neither a - nor a + (if + allowed) */
+ (!(go->flags & GF_PLUSOPT) || flag != '+')) ||
+ (c = arg[1]) == '\0') {
+ go->p = 0;
+ return -1;
+ }
+ go->optind++;
+ go->info &= ~(GI_MINUS|GI_PLUS);
+ go->info |= flag == '-' ? GI_MINUS : GI_PLUS;
+ }
+ go->p++;
+ if (c == '?' || c == ':' || c == ';' || c == ',' || c == '#' ||
+ !(o = strchr(options, c))) {
+ if (options[0] == ':') {
+ go->buf[0] = c;
+ go->optarg = go->buf;
+ } else {
+ warningf(true, "%s%s-%c: unknown option",
+ (go->flags & GF_NONAME) ? "" : argv[0],
+ (go->flags & GF_NONAME) ? "" : ": ", c);
+ if (go->flags & GF_ERROR)
+ bi_errorf(null);
+ }
+ return '?';
+ }
+ /* : means argument must be present, may be part of option argument
+ * or the next argument
+ * ; same as : but argument may be missing
+ * , means argument is part of option argument, and may be null.
+ */
+ if (*++o == ':' || *o == ';') {
+ if (argv[go->optind - 1][go->p])
+ go->optarg = argv[go->optind - 1] + go->p;
+ else if (argv[go->optind])
+ go->optarg = argv[go->optind++];
+ else if (*o == ';')
+ go->optarg = (char *) 0;
+ else {
+ if (options[0] == ':') {
+ go->buf[0] = c;
+ go->optarg = go->buf;
+ return ':';
+ }
+ warningf(true, "%s%s-`%c' requires argument",
+ (go->flags & GF_NONAME) ? "" : argv[0],
+ (go->flags & GF_NONAME) ? "" : ": ", c);
+ if (go->flags & GF_ERROR)
+ bi_errorf(null);
+ return '?';
+ }
+ go->p = 0;
+ } else if (*o == ',') {
+ /* argument is attached to option character, even if null */
+ go->optarg = argv[go->optind - 1] + go->p;
+ go->p = 0;
+ } else if (*o == '#') {
+ /* argument is optional and may be attached or unattached
+ * but must start with a digit. optarg is set to 0 if the
+ * argument is missing.
+ */
+ if (argv[go->optind - 1][go->p]) {
+ if (digit(argv[go->optind - 1][go->p]) ||
+ !strcmp(&argv[go->optind - 1][go->p], "unlimited")) {
+ go->optarg = argv[go->optind - 1] + go->p;
+ go->p = 0;
+ } else
+ go->optarg = (char *) 0;
+ } else {
+ if (argv[go->optind] && (digit(argv[go->optind][0]) ||
+ !strcmp(argv[go->optind], "unlimited"))) {
+ go->optarg = argv[go->optind++];
+ go->p = 0;
+ } else
+ go->optarg = (char *) 0;
+ }
+ }
+ return c;
+}
+
+/* print variable/alias value using necessary quotes
+ * (POSIX says they should be suitable for re-entry...)
+ * No trailing newline is printed.
+ */
+void
+print_value_quoted(const char *s)
+{
+ const char *p;
+ int inquote = 0;
+
+ /* Test if any quotes are needed */
+ for (p = s; *p; p++)
+ if (ctype(*p, C_QUOTE))
+ break;
+ if (!*p) {
+ shprintf("%s", s);
+ return;
+ }
+ for (p = s; *p; p++) {
+ if (*p == '\'') {
+ shprintf("'\\'" + 1 - inquote);
+ inquote = 0;
+ } else {
+ if (!inquote) {
+ shprintf("'");
+ inquote = 1;
+ }
+ shf_putc(*p, shl_stdout);
+ }
+ }
+ if (inquote)
+ shprintf("'");
+}
+
+/* Print things in columns and rows - func() is called to format the ith
+ * element
+ */
+void
+print_columns(struct shf *shf, int n, char *(*func) (void *, int, char *, int),
+ void *arg, int max_width, int prefcol)
+{
+ char *str = (char *) alloc(max_width + 1, ATEMP);
+ int i;
+ int r, c;
+ int rows, cols;
+ int nspace;
+ int col_width;
+
+ /* max_width + 1 for the space. Note that no space
+ * is printed after the last column to avoid problems
+ * with terminals that have auto-wrap.
+ */
+ cols = x_cols / (max_width + 1);
+ if (!cols)
+ cols = 1;
+ rows = (n + cols - 1) / cols;
+ if (prefcol && n && cols > rows) {
+ int tmp = rows;
+
+ rows = cols;
+ cols = tmp;
+ if (rows > n)
+ rows = n;
+ }
+
+ col_width = max_width;
+ if (cols == 1)
+ col_width = 0; /* Don't pad entries in single column output. */
+ nspace = (x_cols - max_width * cols) / cols;
+ if (nspace <= 0)
+ nspace = 1;
+ for (r = 0; r < rows; r++) {
+ for (c = 0; c < cols; c++) {
+ i = c * rows + r;
+ if (i < n) {
+ shf_fprintf(shf, "%-*s",
+ col_width,
+ (*func)(arg, i, str, max_width + 1));
+ if (c + 1 < cols)
+ shf_fprintf(shf, "%*s", nspace, null);
+ }
+ }
+ shf_putchar('\n', shf);
+ }
+ afree(str, ATEMP);
+}
+
+/* Strip any nul bytes from buf - returns new length (nbytes - # of nuls) */
+int
+strip_nuls(char *buf, int nbytes)
+{
+ char *dst;
+
+ /* nbytes check because some systems (older freebsd's) have a buggy
+ * memchr()
+ */
+ if (nbytes && (dst = memchr(buf, '\0', nbytes))) {
+ char *end = buf + nbytes;
+ char *p, *q;
+
+ for (p = dst; p < end; p = q) {
+ /* skip a block of nulls */
+ while (++p < end && *p == '\0')
+ ;
+ /* find end of non-null block */
+ if (!(q = memchr(p, '\0', end - p)))
+ q = end;
+ memmove(dst, p, q - p);
+ dst += q - p;
+ }
+ *dst = '\0';
+ return dst - buf;
+ }
+ return nbytes;
+}
+
+/* Like read(2), but if read fails due to non-blocking flag, resets flag
+ * and restarts read.
+ */
+int
+blocking_read(int fd, char *buf, int nbytes)
+{
+ int ret;
+ int tried_reset = 0;
+
+ while ((ret = read(fd, buf, nbytes)) < 0) {
+ if (!tried_reset && errno == EAGAIN) {
+ int oerrno = errno;
+ if (reset_nonblock(fd) > 0) {
+ tried_reset = 1;
+ continue;
+ }
+ errno = oerrno;
+ }
+ break;
+ }
+ return ret;
+}
+
+/* Reset the non-blocking flag on the specified file descriptor.
+ * Returns -1 if there was an error, 0 if non-blocking wasn't set,
+ * 1 if it was.
+ */
+int
+reset_nonblock(int fd)
+{
+ int flags;
+
+ if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
+ return -1;
+ if (!(flags & O_NONBLOCK))
+ return 0;
+ flags &= ~O_NONBLOCK;
+ if (fcntl(fd, F_SETFL, flags) < 0)
+ return -1;
+ return 1;
+}
+
+
+/* Like getcwd(), except bsize is ignored if buf is 0 (MAXPATHLEN is used) */
+char *
+ksh_get_wd(char *buf, int bsize)
+{
+ char *b;
+ char *ret;
+
+ /* Note: we could just use plain getcwd(), but then we'd had to
+ * inject possibly allocated space into the ATEMP area. */
+ /* Assume getcwd() available */
+ if (!buf) {
+ bsize = MAXPATHLEN;
+ b = alloc(MAXPATHLEN + 1, ATEMP);
+ } else
+ b = buf;
+
+ ret = getcwd(b, bsize);
+
+ if (!buf) {
+ if (ret)
+ ret = aresize(b, strlen(b) + 1, ATEMP);
+ else
+ afree(b, ATEMP);
+ }
+
+ return ret;
+}
diff --git a/mknod.c b/mknod.c
@@ -0,0 +1,91 @@
+/* $OpenBSD: mknod.c,v 1.2 2009/10/27 23:59:21 deraadt Exp $ */
+/* $NetBSD: mknod.c,v 1.8 1995/08/11 00:08:18 jtc Exp $ */
+
+/*
+ * Copyright (c) 1989, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kevin Fall.
+ *
+ * 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 <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#include "sh.h"
+
+int
+domknod(int argc, char **argv, mode_t mode)
+{
+ dev_t dev;
+ char *endp;
+ u_int major, minor;
+
+ if (argv[1][0] == 'c')
+ mode |= S_IFCHR;
+ else if (argv[1][0] == 'b')
+ mode |= S_IFBLK;
+ else {
+ bi_errorf("node must be type 'b' or 'c'.");
+ return 1;
+ }
+
+ major = (long)strtoul(argv[2], &endp, 0);
+ if (endp == argv[2] || *endp != '\0') {
+ bi_errorf("non-numeric major number.");
+ return 1;
+ }
+ minor = (long)strtoul(argv[3], &endp, 0);
+ if (endp == argv[3] || *endp != '\0') {
+ bi_errorf("non-numeric minor number.");
+ return 1;
+ }
+ dev = makedev(major, minor);
+ if (major(dev) != major || minor(dev) != minor) {
+ bi_errorf("major or minor number too large");
+ return 1;
+ }
+ if (mknod(argv[0], mode, dev) < 0) {
+ bi_errorf("%s: %s", argv[0], strerror(errno));
+ return 1;
+ }
+ return 0;
+}
+
+int
+domkfifo(int argc, char **argv, mode_t mode)
+{
+ int rv = 0;
+
+ if (mkfifo(argv[0], mode) < 0) {
+ bi_errorf("%s: %s", argv[0], strerror(errno));
+ rv = 1;
+ }
+ return(rv);
+}
+
diff --git a/path.c b/path.c
@@ -0,0 +1,285 @@
+/* $OpenBSD: path.c,v 1.12 2005/03/30 17:16:37 deraadt Exp $ */
+
+#include "sh.h"
+#include <sys/stat.h>
+
+/*
+ * Contains a routine to search a : separated list of
+ * paths (a la CDPATH) and make appropriate file names.
+ * Also contains a routine to simplify .'s and ..'s out of
+ * a path name.
+ *
+ * Larry Bouzane (larry@cs.mun.ca)
+ */
+
+static char *do_phys_path(XString *, char *, const char *);
+
+/*
+ * Makes a filename into result using the following algorithm.
+ * - make result NULL
+ * - if file starts with '/', append file to result & set cdpathp to NULL
+ * - if file starts with ./ or ../ append cwd and file to result
+ * and set cdpathp to NULL
+ * - if the first element of cdpathp doesnt start with a '/' xx or '.' xx
+ * then cwd is appended to result.
+ * - the first element of cdpathp is appended to result
+ * - file is appended to result
+ * - cdpathp is set to the start of the next element in cdpathp (or NULL
+ * if there are no more elements.
+ * The return value indicates whether a non-null element from cdpathp
+ * was appended to result.
+ */
+int
+make_path(const char *cwd, const char *file,
+ char **cdpathp, /* & of : separated list */
+ XString *xsp,
+ int *phys_pathp)
+{
+ int rval = 0;
+ int use_cdpath = 1;
+ char *plist;
+ int len;
+ int plen = 0;
+ char *xp = Xstring(*xsp, xp);
+
+ if (!file)
+ file = null;
+
+ if (file[0] == '/') {
+ *phys_pathp = 0;
+ use_cdpath = 0;
+ } else {
+ if (file[0] == '.') {
+ char c = file[1];
+
+ if (c == '.')
+ c = file[2];
+ if (c == '/' || c == '\0')
+ use_cdpath = 0;
+ }
+
+ plist = *cdpathp;
+ if (!plist)
+ use_cdpath = 0;
+ else if (use_cdpath) {
+ char *pend;
+
+ for (pend = plist; *pend && *pend != ':'; pend++)
+ ;
+ plen = pend - plist;
+ *cdpathp = *pend ? ++pend : (char *) 0;
+ }
+
+ if ((use_cdpath == 0 || !plen || plist[0] != '/') &&
+ (cwd && *cwd)) {
+ len = strlen(cwd);
+ XcheckN(*xsp, xp, len);
+ memcpy(xp, cwd, len);
+ xp += len;
+ if (cwd[len - 1] != '/')
+ Xput(*xsp, xp, '/');
+ }
+ *phys_pathp = Xlength(*xsp, xp);
+ if (use_cdpath && plen) {
+ XcheckN(*xsp, xp, plen);
+ memcpy(xp, plist, plen);
+ xp += plen;
+ if (plist[plen - 1] != '/')
+ Xput(*xsp, xp, '/');
+ rval = 1;
+ }
+ }
+
+ len = strlen(file) + 1;
+ XcheckN(*xsp, xp, len);
+ memcpy(xp, file, len);
+
+ if (!use_cdpath)
+ *cdpathp = (char *) 0;
+
+ return rval;
+}
+
+/*
+ * Simplify pathnames containing "." and ".." entries.
+ * ie, simplify_path("/a/b/c/./../d/..") returns "/a/b"
+ */
+void
+simplify_path(char *path)
+{
+ char *cur;
+ char *t;
+ int isrooted;
+ char *very_start = path;
+ char *start;
+
+ if (!*path)
+ return;
+
+ if ((isrooted = path[0] == '/'))
+ very_start++;
+
+ /* Before After
+ * /foo/ /foo
+ * /foo/../../bar /bar
+ * /foo/./blah/.. /foo
+ * . .
+ * .. ..
+ * ./foo foo
+ * foo/../../../bar ../../bar
+ */
+
+ for (cur = t = start = very_start; ; ) {
+ /* treat multiple '/'s as one '/' */
+ while (*t == '/')
+ t++;
+
+ if (*t == '\0') {
+ if (cur == path)
+ /* convert empty path to dot */
+ *cur++ = '.';
+ *cur = '\0';
+ break;
+ }
+
+ if (t[0] == '.') {
+ if (!t[1] || t[1] == '/') {
+ t += 1;
+ continue;
+ } else if (t[1] == '.' && (!t[2] || t[2] == '/')) {
+ if (!isrooted && cur == start) {
+ if (cur != very_start)
+ *cur++ = '/';
+ *cur++ = '.';
+ *cur++ = '.';
+ start = cur;
+ } else if (cur != start)
+ while (--cur > start && *cur != '/')
+ ;
+ t += 2;
+ continue;
+ }
+ }
+
+ if (cur != very_start)
+ *cur++ = '/';
+
+ /* find/copy next component of pathname */
+ while (*t && *t != '/')
+ *cur++ = *t++;
+ }
+}
+
+
+void
+set_current_wd(char *path)
+{
+ int len;
+ char *p = path;
+
+ if (!p && !(p = ksh_get_wd((char *) 0, 0)))
+ p = null;
+
+ len = strlen(p) + 1;
+
+ if (len > current_wd_size)
+ current_wd = aresize(current_wd, current_wd_size = len, APERM);
+ memcpy(current_wd, p, len);
+ if (p != path && p != null)
+ afree(p, ATEMP);
+}
+
+char *
+get_phys_path(const char *path)
+{
+ XString xs;
+ char *xp;
+
+ Xinit(xs, xp, strlen(path) + 1, ATEMP);
+
+ xp = do_phys_path(&xs, xp, path);
+
+ if (!xp)
+ return (char *) 0;
+
+ if (Xlength(xs, xp) == 0)
+ Xput(xs, xp, '/');
+ Xput(xs, xp, '\0');
+
+ return Xclose(xs, xp);
+}
+
+static char *
+do_phys_path(XString *xsp, char *xp, const char *path)
+{
+ const char *p, *q;
+ int len, llen;
+ int savepos;
+ char lbuf[PATH];
+
+ Xcheck(*xsp, xp);
+ for (p = path; p; p = q) {
+ while (*p == '/')
+ p++;
+ if (!*p)
+ break;
+ len = (q = strchr(p, '/')) ? q - p : strlen(p);
+ if (len == 1 && p[0] == '.')
+ continue;
+ if (len == 2 && p[0] == '.' && p[1] == '.') {
+ while (xp > Xstring(*xsp, xp)) {
+ xp--;
+ if (*xp == '/')
+ break;
+ }
+ continue;
+ }
+
+ savepos = Xsavepos(*xsp, xp);
+ Xput(*xsp, xp, '/');
+ XcheckN(*xsp, xp, len + 1);
+ memcpy(xp, p, len);
+ xp += len;
+ *xp = '\0';
+
+ llen = readlink(Xstring(*xsp, xp), lbuf, sizeof(lbuf) - 1);
+ if (llen < 0) {
+ /* EINVAL means it wasn't a symlink... */
+ if (errno != EINVAL)
+ return (char *) 0;
+ continue;
+ }
+ lbuf[llen] = '\0';
+
+ /* If absolute path, start from scratch.. */
+ xp = lbuf[0] == '/' ? Xstring(*xsp, xp) :
+ Xrestpos(*xsp, xp, savepos);
+ if (!(xp = do_phys_path(xsp, xp, lbuf)))
+ return (char *) 0;
+ }
+ return xp;
+}
+
+#ifdef TEST
+
+int
+main(void)
+{
+ int rv;
+ char *cp, cdpath[256], pwd[256], file[256], result[256];
+
+ printf("enter CDPATH: "); gets(cdpath);
+ printf("enter PWD: "); gets(pwd);
+ while (1) {
+ if (printf("Enter file: "), gets(file) == 0)
+ return 0;
+ cp = cdpath;
+ do {
+ rv = make_path(pwd, file, &cp, result, sizeof(result));
+ printf("make_path returns (%d), \"%s\" ", rv, result);
+ simplify_path(result);
+ printf("(simpifies to \"%s\")\n", result);
+ } while (cp);
+ }
+}
+#endif /* TEST */
diff --git a/proto.h b/proto.h
@@ -0,0 +1,267 @@
+/* $OpenBSD: proto.h,v 1.35 2013/09/04 15:49:19 millert Exp $ */
+
+/*
+ * prototypes for PD-KSH
+ * originally generated using "cproto.c 3.5 92/04/11 19:28:01 cthuang "
+ * $From: proto.h,v 1.3 1994/05/19 18:32:40 michael Exp michael $
+ */
+
+/* alloc.c */
+Area * ainit(Area *);
+void afreeall(Area *);
+void * alloc(size_t, Area *);
+void * aresize(void *, size_t, Area *);
+void afree(void *, Area *);
+/* c_ksh.c */
+int c_hash(char **);
+int c_cd(char **);
+int c_pwd(char **);
+int c_print(char **);
+int c_whence(char **);
+int c_command(char **);
+int c_typeset(char **);
+int c_alias(char **);
+int c_unalias(char **);
+int c_let(char **);
+int c_jobs(char **);
+int c_fgbg(char **);
+int c_kill(char **);
+void getopts_reset(int);
+int c_getopts(char **);
+int c_bind(char **);
+/* c_sh.c */
+int c_label(char **);
+int c_shift(char **);
+int c_umask(char **);
+int c_dot(char **);
+int c_wait(char **);
+int c_read(char **);
+int c_eval(char **);
+int c_trap(char **);
+int c_brkcont(char **);
+int c_exitreturn(char **);
+int c_set(char **);
+int c_unset(char **);
+int c_ulimit(char **);
+int c_times(char **);
+int timex(struct op *, int, volatile int *);
+void timex_hook(struct op *, char ** volatile *);
+int c_exec(char **);
+int c_builtin(char **);
+/* c_test.c */
+int c_test(char **);
+/* edit.c: most prototypes in edit.h */
+void x_init(void);
+int x_read(char *, size_t);
+void set_editmode(const char *);
+/* emacs.c: most prototypes in edit.h */
+int x_bind(const char *, const char *, int, int);
+/* eval.c */
+char * substitute(const char *, int);
+char ** eval(char **, int);
+char * evalstr(char *cp, int);
+char * evalonestr(char *cp, int);
+char *debunk(char *, const char *, size_t);
+void expand(char *, XPtrV *, int);
+int glob_str(char *, XPtrV *, int);
+/* exec.c */
+int execute(struct op * volatile, volatile int, volatile int *);
+int shcomexec(char **);
+struct tbl * findfunc(const char *, unsigned int, int);
+int define(const char *, struct op *);
+void builtin(const char *, int (*)(char **));
+struct tbl * findcom(const char *, int);
+void flushcom(int);
+char * search(const char *, const char *, int, int *);
+int search_access(const char *, int, int *);
+int pr_menu(char *const *);
+int pr_list(char *const *);
+/* expr.c */
+int evaluate(const char *, long *, int, bool);
+int v_evaluate(struct tbl *, const char *, volatile int, bool);
+/* history.c */
+void init_histvec(void);
+void hist_init(Source *);
+void hist_finish(void);
+void histsave(int, const char *, int);
+#ifdef HISTORY
+int c_fc(char **);
+void sethistsize(int);
+void sethistfile(const char *);
+char ** histpos(void);
+int histnum(int);
+int findhist(int, int, const char *, int);
+int findhistrel(const char *);
+char **hist_get_newest(int);
+
+#endif /* HISTORY */
+/* io.c */
+void errorf(const char *, ...)
+ __attribute__((__noreturn__, __format__ (printf, 1, 2)));
+void warningf(int, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+void bi_errorf(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void internal_errorf(int, const char *, ...)
+ __attribute__((__format__ (printf, 2, 3)));
+void error_prefix(int);
+void shellf(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void shprintf(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+#ifdef KSH_DEBUG
+void kshdebug_init_(void);
+void kshdebug_printf_(const char *, ...)
+ __attribute__((__format__ (printf, 1, 2)));
+void kshdebug_dump_(const char *, const void *, int);
+#endif /* KSH_DEBUG */
+int can_seek(int);
+void initio(void);
+int ksh_dup2(int, int, int);
+int savefd(int);
+void restfd(int, int);
+void openpipe(int *);
+void closepipe(int *);
+int check_fd(char *, int, const char **);
+void coproc_init(void);
+void coproc_read_close(int);
+void coproc_readw_close(int);
+void coproc_write_close(int);
+int coproc_getfd(int, const char **);
+void coproc_cleanup(int);
+struct temp *maketemp(Area *, Temp_type, struct temp **);
+/* jobs.c */
+void j_init(int);
+void j_suspend(void);
+void j_exit(void);
+void j_change(void);
+int exchild(struct op *, int, volatile int *, int);
+void startlast(void);
+int waitlast(void);
+int waitfor(const char *, int *);
+int j_kill(const char *, int);
+int j_resume(const char *, int);
+int j_jobs(const char *, int, int);
+int j_njobs(void);
+void j_notify(void);
+pid_t j_async(void);
+int j_stopped_running(void);
+/* lex.c */
+int yylex(int);
+void yyerror(const char *, ...)
+ __attribute__((__noreturn__, __format__ (printf, 1, 2)));
+Source * pushs(int, Area *);
+void set_prompt(int, Source *);
+void pprompt(const char *, int);
+/* mail.c */
+void mcheck(void);
+void mcset(long);
+void mbset(char *);
+void mpset(char *);
+/* main.c */
+int include(const char *, int, char **, int);
+int command(const char *, int);
+int shell(Source *volatile, int volatile);
+void unwind(int) __attribute__((__noreturn__));
+void newenv(int);
+void quitenv(struct shf *);
+void cleanup_parents_env(void);
+void cleanup_proc_env(void);
+/* misc.c */
+void setctypes(const char *, int);
+void initctypes(void);
+char * ulton(unsigned long, int);
+char * str_save(const char *, Area *);
+char * str_nsave(const char *, int, Area *);
+int option(const char *);
+char * getoptions(void);
+void change_flag(enum sh_flag, int, int);
+int parse_args(char **, int, int *);
+int getn(const char *, int *);
+int bi_getn(const char *, int *);
+int gmatch(const char *, const char *, int);
+int has_globbing(const char *, const char *);
+const unsigned char *pat_scan(const unsigned char *, const unsigned char *,
+ int);
+void qsortp(void **, size_t, int (*)(const void *, const void *));
+int xstrcmp(const void *, const void *);
+void ksh_getopt_reset(Getopt *, int);
+int ksh_getopt(char **, Getopt *, const char *);
+void print_value_quoted(const char *);
+void print_columns(struct shf *, int, char *(*)(void *, int, char *, int),
+ void *, int, int prefcol);
+int strip_nuls(char *, int);
+int blocking_read(int, char *, int);
+int reset_nonblock(int);
+char *ksh_get_wd(char *, int);
+/* mknod.c */
+int domknod(int, char **, mode_t);
+int domkfifo(int, char **, mode_t);
+/* path.c */
+int make_path(const char *, const char *, char **, XString *, int *);
+void simplify_path(char *);
+char *get_phys_path(const char *);
+void set_current_wd(char *);
+/* syn.c */
+void initkeywords(void);
+struct op * compile(Source *);
+/* table.c */
+unsigned int hash(const char *);
+void ktinit(struct table *, Area *, int);
+struct tbl * ktsearch(struct table *, const char *, unsigned int);
+struct tbl * ktenter(struct table *, const char *, unsigned int);
+void ktdelete(struct tbl *);
+void ktwalk(struct tstate *, struct table *);
+struct tbl * ktnext(struct tstate *);
+struct tbl ** ktsort(struct table *);
+/* trace.c */
+/* trap.c */
+void inittraps(void);
+void alarm_init(void);
+Trap * gettrap(const char *, int);
+void trapsig(int);
+void intrcheck(void);
+int fatal_trap_check(void);
+int trap_pending(void);
+void runtraps(int intr);
+void runtrap(Trap *);
+void cleartraps(void);
+void restoresigs(void);
+void settrap(Trap *, char *);
+int block_pipe(void);
+void restore_pipe(int);
+int setsig(Trap *, sig_t, int);
+void setexecsig(Trap *, int);
+/* tree.c */
+void fptreef(struct shf *, int, const char *, ...);
+char * snptreef(char *, int, const char *, ...);
+struct op * tcopy(struct op *, Area *);
+char * wdcopy(const char *, Area *);
+char * wdscan(const char *, int);
+char * wdstrip(const char *);
+void tfree(struct op *, Area *);
+/* var.c */
+void newblock(void);
+void popblock(void);
+void initvar(void);
+struct tbl * global(const char *);
+struct tbl * local(const char *, bool);
+char * str_val(struct tbl *);
+long intval(struct tbl *);
+int setstr(struct tbl *, const char *, int);
+struct tbl *setint_v(struct tbl *, struct tbl *, bool);
+void setint(struct tbl *, long);
+int getint(struct tbl *, long *, bool);
+struct tbl * typeset(const char *, Tflag, Tflag, int, int);
+void unset(struct tbl *, int);
+char * skip_varname(const char *, int);
+char *skip_wdvarname(const char *, int);
+int is_wdvarname(const char *, int);
+int is_wdvarassign(const char *);
+char ** makenv(void);
+void change_random(void);
+int array_ref_len(const char *);
+char * arrayname(const char *);
+void set_array(const char *, int, char **);
+/* version.c */
+/* vi.c: see edit.h */
diff --git a/sh.1 b/sh.1
@@ -0,0 +1,3976 @@
+.\" $OpenBSD: sh.1,v 1.99 2014/12/09 15:37:13 schwarze Exp $
+.\"
+.\" Public Domain
+.\"
+.Dd $Mdocdate: December 9 2014 $
+.Dt SH 1
+.Os
+.Sh NAME
+.Nm sh
+.Nd public domain Bourne shell
+.Sh SYNOPSIS
+.Nm sh
+.Bk -words
+.Op Fl +abCefhiklmnpruvXx
+.Op Fl +o Ar option
+.Op Fl c Ar string \*(Ba Fl s \*(Ba Ar file Op Ar argument ...
+.Ek
+.Sh DESCRIPTION
+.Nm
+is a reimplementation of the Bourne shell, a command interpreter for both
+interactive and script use.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl c Ar string
+.Nm
+will execute the command(s) contained in
+.Ar string .
+.It Fl i
+Interactive shell.
+A shell is
+.Dq interactive
+if this
+option is used or if both standard input and standard error are attached
+to a
+.Xr tty 4 .
+An interactive shell has job control enabled, ignores the
+.Dv SIGINT ,
+.Dv SIGQUIT ,
+and
+.Dv SIGTERM
+signals, and prints prompts before reading input (see the
+.Ev PS1
+and
+.Ev PS2
+parameters).
+For non-interactive shells, the
+.Ic trackall
+option is on by default (see the
+.Ic set
+command below).
+.It Fl l
+Login shell.
+If the basename the shell is called with (i.e. argv[0])
+starts with
+.Ql -
+or if this option is used,
+the shell is assumed to be a login shell and the shell reads and executes
+the contents of
+.Pa /etc/profile
+and
+.Pa $HOME/.profile
+if they exist and are readable.
+.It Fl p
+Privileged shell.
+A shell is
+.Dq privileged
+if this option is used
+or if the real user ID or group ID does not match the
+effective user ID or group ID (see
+.Xr getuid 2
+and
+.Xr getgid 2 ) .
+A privileged shell does not process
+.Pa $HOME/.profile
+nor the
+.Ev ENV
+parameter (see below).
+Instead, the file
+.Pa /etc/suid_profile
+is processed.
+Clearing the privileged option causes the shell to set
+its effective user ID (group ID) to its real user ID (group ID).
+.It Fl r
+Restricted shell.
+A shell is
+.Dq restricted
+if this
+option is used;
+if the basename the shell was invoked with was
+.Dq rsh ;
+or if the
+.Ev SHELL
+parameter is set to
+.Dq rsh .
+The following restrictions come into effect after the shell processes any
+profile and
+.Ev ENV
+files:
+.Pp
+.Bl -bullet -compact
+.It
+The
+.Ic cd
+command is disabled.
+.It
+The
+.Ev SHELL ,
+.Ev ENV ,
+and
+.Ev PATH
+parameters cannot be changed.
+.It
+Command names can't be specified with absolute or relative paths.
+.It
+The
+.Fl p
+option of the built-in command
+.Ic command
+can't be used.
+.It
+Redirections that create files can't be used (i.e.\&
+.Ql \*(Gt ,
+.Ql \*(Gt\*(Ba ,
+.Ql \*(Gt\*(Gt ,
+.Ql \*(Lt\*(Gt ) .
+.El
+.It Fl s
+The shell reads commands from standard input; all non-option arguments
+are positional parameters.
+.El
+.Pp
+In addition to the above, the options described in the
+.Ic set
+built-in command can also be used on the command line:
+both
+.Op Fl +abCefhkmnuvXx
+and
+.Op Fl +o Ar option
+can be used for single letter or long options, respectively.
+.Pp
+If neither the
+.Fl c
+nor the
+.Fl s
+option is specified, the first non-option argument specifies the name
+of a file the shell reads commands from.
+If there are no non-option
+arguments, the shell reads commands from the standard input.
+The name of the shell (i.e. the contents of $0)
+is determined as follows: if the
+.Fl c
+option is used and there is a non-option argument, it is used as the name;
+if commands are being read from a file, the file is used as the name;
+otherwise, the basename the shell was called with (i.e. argv[0]) is used.
+.Pp
+If the
+.Ev ENV
+parameter is set when an interactive shell starts (or,
+in the case of login shells,
+after any profiles are processed), its value is subjected to parameter,
+command, arithmetic, and tilde
+.Pq Sq ~
+substitution and the resulting file
+(if any) is read and executed.
+.Pp
+The exit status of the shell is 127 if the command file specified on the
+command line could not be opened, or non-zero if a fatal syntax error
+occurred during the execution of a script.
+In the absence of fatal errors,
+the exit status is that of the last command executed, or zero, if no
+command is executed.
+.Ss Command syntax
+The shell begins parsing its input by breaking it into
+.Em words .
+Words, which are sequences of characters, are delimited by unquoted whitespace
+characters (space, tab, and newline) or meta-characters
+.Po
+.Ql \*(Lt ,
+.Ql \*(Gt ,
+.Ql \*(Ba ,
+.Ql \&; ,
+.Ql \&( ,
+.Ql \&) ,
+and
+.Ql &
+.Pc .
+Aside from delimiting words, spaces and tabs are ignored, while newlines
+usually delimit commands.
+The meta-characters are used in building the following
+.Em tokens :
+.Ql \*(Lt ,
+.Ql \*(Lt& ,
+.Ql \*(Lt\*(Lt ,
+.Ql \*(Gt ,
+.Ql \*(Gt& ,
+.Ql \*(Gt\*(Gt ,
+etc. are used to specify redirections (see
+.Sx Input/output redirection
+below);
+.Ql \*(Ba
+is used to create pipelines;
+.Ql \&;
+is used to separate commands;
+.Ql &
+is used to create asynchronous pipelines;
+.Ql &&
+and
+.Ql ||
+are used to specify conditional execution;
+.Ql ;;
+is used in
+.Ic case
+statements;
+and lastly,
+.Ql \&( .. )\&
+is used to create subshells.
+.Pp
+Whitespace and meta-characters can be quoted individually using a backslash
+.Pq Sq \e ,
+or in groups using double
+.Pq Sq \&"
+or single
+.Pq Sq '
+quotes.
+Note that the following characters are also treated specially by the
+shell and must be quoted if they are to represent themselves:
+.Ql \e ,
+.Ql \&" ,
+.Ql ' ,
+.Ql # ,
+.Ql $ ,
+.Ql ` ,
+.Ql ~ ,
+.Ql { ,
+.Ql } ,
+.Ql * ,
+.Ql \&? ,
+and
+.Ql \&[ .
+The first three of these are the above mentioned quoting characters (see
+.Sx Quoting
+below);
+.Ql # ,
+if used at the beginning of a word, introduces a comment \(em everything after
+the
+.Ql #
+up to the nearest newline is ignored;
+.Ql $
+is used to introduce parameter, command, and arithmetic substitutions (see
+.Sx Substitution
+below);
+.Ql `
+introduces an old-style command substitution (see
+.Sx Substitution
+below);
+.Ql ~
+begins a directory expansion (see
+.Sx Tilde expansion
+below);
+and finally,
+.Ql * ,
+.Ql \&? ,
+and
+.Ql \&[
+are used in file name generation (see
+.Sx File name patterns
+below).
+.Pp
+As words and tokens are parsed, the shell builds commands, of which there
+are two basic types:
+.Em simple-commands ,
+typically programs that are executed, and
+.Em compound-commands ,
+such as
+.Ic for
+and
+.Ic if
+statements, grouping constructs, and function definitions.
+.Pp
+A simple-command consists of some combination of parameter assignments
+(see
+.Sx Parameters
+below),
+input/output redirections (see
+.Sx Input/output redirections
+below),
+and command words; the only restriction is that parameter assignments come
+before any command words.
+The command words, if any, define the command
+that is to be executed and its arguments.
+The command may be a shell built-in command, a function,
+or an external command
+(i.e. a separate executable file that is located using the
+.Ev PATH
+parameter; see
+.Sx Command execution
+below).
+Note that all command constructs have an exit status: for external commands,
+this is related to the status returned by
+.Xr wait 2
+(if the command could not be found, the exit status is 127; if it could not
+be executed, the exit status is 126); the exit status of other command
+constructs (built-in commands, functions, compound-commands, pipelines, lists,
+etc.) are all well-defined and are described where the construct is
+described.
+The exit status of a command consisting only of parameter
+assignments is that of the last command substitution performed during the
+parameter assignment or 0 if there were no command substitutions.
+.Pp
+Commands can be chained together using the
+.Ql |
+token to form pipelines, in which the standard output of each command but the
+last is piped (see
+.Xr pipe 2 )
+to the standard input of the following command.
+The exit status of a pipeline is that of its last command.
+A pipeline may be prefixed by the
+.Ql \&!
+reserved word, which causes the exit status of the pipeline to be logically
+complemented: if the original status was 0, the complemented status will be 1;
+if the original status was not 0, the complemented status will be 0.
+.Pp
+.Em Lists
+of commands can be created by separating pipelines by any of the following
+tokens:
+.Ql && ,
+.Ql || ,
+.Ql & ,
+.Ql |& ,
+and
+.Ql \&; .
+The first two are for conditional execution:
+.Dq Ar cmd1 No && Ar cmd2
+executes
+.Ar cmd2
+only if the exit status of
+.Ar cmd1
+is zero;
+.Ql ||
+is the opposite \(em
+.Ar cmd2
+is executed only if the exit status of
+.Ar cmd1
+is non-zero.
+.Ql &&
+and
+.Ql ||
+have equal precedence which is higher than that of
+.Ql & ,
+.Ql |& ,
+and
+.Ql \&; ,
+which also have equal precedence.
+Note that the
+.Ql &&
+and
+.Ql ||
+operators are
+.Qq left-associative .
+For example, both of these commands will print only
+.Qq bar :
+.Bd -literal -offset indent
+$ false && echo foo || echo bar
+$ true || echo foo && echo bar
+.Ed
+.Pp
+The
+.Ql &
+token causes the preceding command to be executed asynchronously; that is,
+the shell starts the command but does not wait for it to complete (the shell
+does keep track of the status of asynchronous commands; see
+.Sx Job control
+below).
+When an asynchronous command is started when job control is disabled
+(i.e. in most scripts), the command is started with signals
+.Dv SIGINT
+and
+.Dv SIGQUIT
+ignored and with input redirected from
+.Pa /dev/null
+(however, redirections specified in the asynchronous command have precedence).
+Note that a command must follow the
+.Ql &&
+and
+.Ql ||
+operators, while it need not follow
+.Ql & ,
+.Ql |& ,
+or
+.Ql \&; .
+The exit status of a list is that of the last command executed, with the
+exception of asynchronous lists, for which the exit status is 0.
+.Pp
+Compound commands are created using the following reserved words.
+These words
+are only recognized if they are unquoted and if they are used as the first
+word of a command (i.e. they can't be preceded by parameter assignments or
+redirections):
+.Bd -literal -offset indent
+case esac in while ]]
+do fi name ! {
+done for select ( }
+elif function then )
+else if until [[
+.Ed
+.Pp
+.Sy Note :
+Some shells (but not this one) execute control structure commands in a
+subshell when one or more of their file descriptors are redirected, so any
+environment changes inside them may fail.
+To be portable, the
+.Ic exec
+statement should be used instead to redirect file descriptors before the
+control structure.
+.Pp
+In the following compound command descriptions, command lists (denoted as
+.Em list )
+that are followed by reserved words must end with a semicolon, a newline, or
+a (syntactically correct) reserved word.
+For example, the following are all valid:
+.Bd -literal -offset indent
+$ { echo foo; echo bar; }
+$ { echo foo; echo bar\*(Ltnewline\*(Gt }
+$ { { echo foo; echo bar; } }
+.Ed
+.Pp
+This is not valid:
+.Pp
+.Dl $ { echo foo; echo bar }
+.Bl -tag -width Ds
+.It Pq Ar list
+Execute
+.Ar list
+in a subshell.
+There is no implicit way to pass environment changes from a
+subshell back to its parent.
+.It { Ar list ; No }
+Compound construct;
+.Ar list
+is executed, but not in a subshell.
+Note that
+.Ql {
+and
+.Ql }
+are reserved words, not meta-characters.
+.It Xo case Ar word No in
+.Oo Op \&(
+.Ar \ pattern
+.Op \*(Ba Ar pattern
+.No ... No )
+.Ar list No ;;\ \& Oc ... esac
+.Xc
+The
+.Ic case
+statement attempts to match
+.Ar word
+against a specified
+.Ar pattern ;
+the
+.Ar list
+associated with the first successfully matched pattern is executed.
+Patterns used in
+.Ic case
+statements are the same as those used for file name patterns except that the
+restrictions regarding
+.Ql \&.
+and
+.Ql /
+are dropped.
+Note that any unquoted space before and after a pattern is
+stripped; any space within a pattern must be quoted.
+Both the word and the
+patterns are subject to parameter, command, and arithmetic substitution, as
+well as tilde substitution.
+For historical reasons, open and close braces may be used instead of
+.Ic in
+and
+.Ic esac
+e.g.\&
+.Ic case $foo { *) echo bar; } .
+The exit status of a
+.Ic case
+statement is that of the executed
+.Ar list ;
+if no
+.Ar list
+is executed, the exit status is zero.
+.It Xo for Ar name
+.Oo in Ar word No ... Oc ;
+.No do Ar list ; No done
+.Xc
+For each
+.Ar word
+in the specified word list, the parameter
+.Ar name
+is set to the word and
+.Ar list
+is executed.
+If
+.Ic in
+is not used to specify a word list, the positional parameters
+($1, $2, etc.)\&
+are used instead.
+For historical reasons, open and close braces may be used instead of
+.Ic do
+and
+.Ic done
+e.g.\&
+.Ic for i; { echo $i; } .
+The exit status of a
+.Ic for
+statement is the last exit status of
+.Ar list ;
+if
+.Ar list
+is never executed, the exit status is zero.
+.It Xo if Ar list ;
+.No then Ar list ;
+.Oo elif Ar list ;
+.No then Ar list ; Oc
+.No ...
+.Oo else Ar list ; Oc
+.No fi
+.Xc
+If the exit status of the first
+.Ar list
+is zero, the second
+.Ar list
+is executed; otherwise, the
+.Ar list
+following the
+.Ic elif ,
+if any, is executed with similar consequences.
+If all the lists following the
+.Ic if
+and
+.Ic elif Ns s
+fail (i.e. exit with non-zero status), the
+.Ar list
+following the
+.Ic else
+is executed.
+The exit status of an
+.Ic if
+statement is that of non-conditional
+.Ar list
+that is executed; if no non-conditional
+.Ar list
+is executed, the exit status is zero.
+.It Xo select Ar name
+.Oo in Ar word No ... Oc ;
+.No do Ar list ; No done
+.Xc
+The
+.Ic select
+statement provides an automatic method of presenting the user with a menu and
+selecting from it.
+An enumerated list of the specified
+.Ar word Ns (s)
+is printed on standard error, followed by a prompt
+.Po
+.Ev PS3: normally
+.Sq #?\ \&
+.Pc .
+A number corresponding to one of the enumerated words is then read from
+standard input,
+.Ar name
+is set to the selected word (or unset if the selection is not valid),
+.Ev REPLY
+is set to what was read (leading/trailing space is stripped), and
+.Ar list
+is executed.
+If a blank line (i.e. zero or more
+.Ev IFS
+characters) is entered, the menu is reprinted without executing
+.Ar list .
+.Pp
+When
+.Ar list
+completes, the enumerated list is printed if
+.Ev REPLY
+is
+.Dv NULL ,
+the prompt is printed, and so on.
+This process continues until an end-of-file
+is read, an interrupt is received, or a
+.Ic break
+statement is executed inside the loop.
+If
+.Dq in word ...
+is omitted, the positional parameters are used
+(i.e. $1, $2, etc.).
+For historical reasons, open and close braces may be used instead of
+.Ic do
+and
+.Ic done
+e.g.\&
+.Ic select i; { echo $i; } .
+The exit status of a
+.Ic select
+statement is zero if a
+.Ic break
+statement is used to exit the loop, non-zero otherwise.
+.It Xo until Ar list ;
+.No do Ar list ;
+.No done
+.Xc
+This works like
+.Ic while ,
+except that the body is executed only while the exit status of the first
+.Ar list
+is non-zero.
+.It Xo while Ar list ;
+.No do Ar list ;
+.No done
+.Xc
+A
+.Ic while
+is a pre-checked loop.
+Its body is executed as often as the exit status of the first
+.Ar list
+is zero.
+The exit status of a
+.Ic while
+statement is the last exit status of the
+.Ar list
+in the body of the loop; if the body is not executed, the exit status is zero.
+.It Xo function Ar name
+.No { Ar list ; No }
+.Xc
+Defines the function
+.Ar name
+(see
+.Sx Functions
+below).
+Note that redirections specified after a function definition are
+performed whenever the function is executed, not when the function definition
+is executed.
+.It Ar name Ns () Ar command
+Mostly the same as
+.Ic function
+(see
+.Sx Functions
+below).
+.It Bq Bq Ar \ \&expression\ \&
+Similar to the
+.Ic test
+and
+.Ic \&[ ... \&]
+commands (described later), with the following exceptions:
+.Bl -bullet -offset indent
+.It
+Field splitting and file name generation are not performed on arguments.
+.It
+The
+.Fl a
+.Pq AND
+and
+.Fl o
+.Pq OR
+operators are replaced with
+.Ql &&
+and
+.Ql || ,
+respectively.
+.It
+Operators (e.g.\&
+.Sq Fl f ,
+.Sq = ,
+.Sq \&! )
+must be unquoted.
+.It
+The second operand of the
+.Sq !=
+and
+.Sq =
+expressions are patterns (e.g. the comparison
+.Ic [[ foobar = f*r ]]
+succeeds).
+.It
+There are two additional binary operators,
+.Ql \*(Lt
+and
+.Ql \*(Gt ,
+which return true if their first string operand is less than, or greater than,
+their second string operand, respectively.
+.It
+The single argument form of
+.Ic test ,
+which tests if the argument has a non-zero length, is not valid; explicit
+operators must always be used e.g. instead of
+.No \&[ Ar str No \&]
+use
+.No \&[[ -n Ar str No \&]] .
+.It
+Parameter, command, and arithmetic substitutions are performed as expressions
+are evaluated and lazy expression evaluation is used for the
+.Ql &&
+and
+.Ql ||
+operators.
+This means that in the following statement,
+.Ic $(\*(Lt foo)
+is evaluated if and only if the file
+.Pa foo
+exists and is readable:
+.Bd -literal -offset indent
+$ [[ -r foo && $(\*(Lt foo) = b*r ]]
+.Ed
+.El
+.El
+.Ss Quoting
+Quoting is used to prevent the shell from treating characters or words
+specially.
+There are three methods of quoting.
+First,
+.Ql \e
+quotes the following character, unless it is at the end of a line, in which
+case both the
+.Ql \e
+and the newline are stripped.
+Second, a single quote
+.Pq Sq '
+quotes everything up to the next single quote (this may span lines).
+Third, a double quote
+.Pq Sq \&"
+quotes all characters, except
+.Ql $ ,
+.Ql `
+and
+.Ql \e ,
+up to the next unquoted double quote.
+.Ql $
+and
+.Ql `
+inside double quotes have their usual meaning (i.e. parameter, command, or
+arithmetic substitution) except no field splitting is carried out on the
+results of double-quoted substitutions.
+If a
+.Ql \e
+inside a double-quoted string is followed by
+.Ql \e ,
+.Ql $ ,
+.Ql ` ,
+or
+.Ql \&" ,
+it is replaced by the second character; if it is followed by a newline, both
+the
+.Ql \e
+and the newline are stripped; otherwise, both the
+.Ql \e
+and the character following are unchanged.
+.Pp
+.Sy Note :
+See
+.Sx POSIX mode
+below for a special rule regarding
+differences in quoting when the shell is in POSIX mode.
+.Ss Aliases
+There are two types of aliases: normal command aliases and tracked aliases.
+Command aliases are normally used as a short hand for a long or often used
+command.
+The shell expands command aliases (i.e. substitutes the alias name
+for its value) when it reads the first word of a command.
+An expanded alias is re-processed to check for more aliases.
+If a command alias ends in a
+space or tab, the following word is also checked for alias expansion.
+The alias expansion process stops when a word that is not an alias is found,
+when a quoted word is found, or when an alias word that is currently being
+expanded is found.
+.Pp
+The following command aliases are defined automatically by the shell:
+.Bd -literal -offset indent
+hash='alias -t'
+type='whence -v'
+.Ed
+.Pp
+Tracked aliases allow the shell to remember where it found a particular
+command.
+The first time the shell does a path search for a command that is
+marked as a tracked alias, it saves the full path of the command.
+The next
+time the command is executed, the shell checks the saved path to see that it
+is still valid, and if so, avoids repeating the path search.
+Tracked aliases can be listed and created using
+.Ic alias -t .
+Note that changing the
+.Ev PATH
+parameter clears the saved paths for all tracked aliases.
+If the
+.Ic trackall
+option is set (i.e.\&
+.Ic set -o Ic trackall
+or
+.Ic set -h ) ,
+the shell tracks all commands.
+This option is set automatically for non-interactive shells.
+For interactive shells, only the following commands are
+automatically tracked:
+.Xr cat 1 ,
+.Xr cc 1 ,
+.Xr chmod 1 ,
+.Xr cp 1 ,
+.Xr date 1 ,
+.Xr ed 1 ,
+.Xr emacs 1 ,
+.Xr grep 1 ,
+.Xr ls 1 ,
+.Xr mail 1 ,
+.Xr make 1 ,
+.Xr mv 1 ,
+.Xr pr 1 ,
+.Xr rm 1 ,
+.Xr sed 1 ,
+.Xr vi 1 ,
+and
+.Xr who 1 .
+.Ss Substitution
+The first step the shell takes in executing a simple-command is to perform
+substitutions on the words of the command.
+There are three kinds of
+substitution: parameter, command, and arithmetic.
+Parameter substitutions,
+which are described in detail in the next section, take the form
+.Pf $ Ns Ar name
+or
+.Pf ${ Ns Ar ... Ns } ;
+command substitutions take the form
+.Pf $( Ns Ar command Ns \&)
+or
+.Pf ` Ns Ar command Ns ` ;
+and arithmetic substitutions take the form
+.Pf $(( Ns Ar expression Ns )) .
+.Pp
+If a substitution appears outside of double quotes, the results of the
+substitution are generally subject to word or field splitting according to
+the current value of the
+.Ev IFS
+parameter.
+The
+.Ev IFS
+parameter specifies a list of characters which are used to break a string up
+into several words; any characters from the set space, tab, and newline that
+appear in the
+.Ev IFS
+characters are called
+.Dq IFS whitespace .
+Sequences of one or more
+.Ev IFS
+whitespace characters, in combination with zero or one
+.Pf non- Ev IFS
+whitespace
+characters, delimit a field.
+As a special case, leading and trailing
+.Ev IFS
+whitespace is stripped (i.e. no leading or trailing empty field is created by
+it); leading
+.Pf non- Ev IFS
+whitespace does create an empty field.
+.Pp
+Example: If
+.Ev IFS
+is set to
+.Dq \*(Ltspace\*(Gt: ,
+and VAR is set to
+.Dq \*(Ltspace\*(GtA\*(Ltspace\*(Gt:\*(Ltspace\*(Gt\*(Ltspace\*(GtB::D ,
+the substitution for $VAR results in four fields:
+.Sq A ,
+.Sq B ,
+.Sq
+(an empty field),
+and
+.Sq D .
+Note that if the
+.Ev IFS
+parameter is set to the
+.Dv NULL
+string, no field splitting is done; if the parameter is unset, the default
+value of space, tab, and newline is used.
+.Pp
+Also, note that the field splitting applies only to the immediate result of
+the substitution.
+Using the previous example, the substitution for $VAR:E
+results in the fields:
+.Sq A ,
+.Sq B ,
+.Sq ,
+and
+.Sq D:E ,
+not
+.Sq A ,
+.Sq B ,
+.Sq ,
+.Sq D ,
+and
+.Sq E .
+This behavior is POSIX compliant, but incompatible with some other shell
+implementations which do field splitting on the word which contained the
+substitution or use
+.Dv IFS
+as a general whitespace delimiter.
+.Pp
+The results of substitution are, unless otherwise specified, also subject to
+file name expansion (see the relevant section below).
+.Pp
+A command substitution is replaced by the output generated by the specified
+command, which is run in a subshell.
+For
+.Pf $( Ns Ar command Ns \&)
+substitutions, normal quoting rules are used when
+.Ar command
+is parsed; however, for the
+.Pf ` Ns Ar command Ns `
+form, a
+.Ql \e
+followed by any of
+.Ql $ ,
+.Ql ` ,
+or
+.Ql \e
+is stripped (a
+.Ql \e
+followed by any other character is unchanged).
+As a special case in command substitutions, a command of the form
+.Pf \*(Lt Ar file
+is interpreted to mean substitute the contents of
+.Ar file .
+Note that
+.Ic $(\*(Lt foo)
+has the same effect as
+.Ic $(cat foo) ,
+but it is carried out more efficiently because no process is started.
+.Pp
+.Sy Note :
+.Pf $( Ns Ar command Ns \&)
+expressions are currently parsed by finding the matching parenthesis,
+regardless of quoting.
+This should be fixed soon.
+.Pp
+Arithmetic substitutions are replaced by the value of the specified expression.
+For example, the command
+.Ic echo $((2+3*4))
+prints 14.
+See
+.Sx Arithmetic expressions
+for a description of an expression.
+.Ss Parameters
+Parameters are shell variables; they can be assigned values and their values
+can be accessed using a parameter substitution.
+A parameter name is either one
+of the special single punctuation or digit character parameters described
+below, or a letter followed by zero or more letters or digits
+.Po
+.Ql _
+counts as a letter
+.Pc .
+Parameter substitutions take the form
+.Pf $ Ns Ar name ,
+.Pf ${ Ns Ar name Ns } ,
+or
+.Sm off
+.Pf ${ Ar name Oo Ar expr Oc }
+.Sm on
+where
+.Ar name
+is a parameter name.
+If substitution is performed on a parameter
+that is not set, a null string is substituted unless the
+.Ic nounset
+option
+.Po
+.Ic set Fl o Ic nounset
+or
+.Ic set Fl u
+.Pc
+is set, in which case an error occurs.
+.Pp
+Parameters can be assigned values in a number of ways.
+First, the shell implicitly sets some parameters like
+.Ql # ,
+.Ql PWD ,
+and
+.Ql $ ;
+this is the only way the special single character parameters are set.
+Second, parameters are imported from the shell's environment at startup.
+Third, parameters can be assigned values on the command line: for example,
+.Ic FOO=bar
+sets the parameter
+.Dq FOO
+to
+.Dq bar ;
+multiple parameter assignments can be given on a single command line and they
+can be followed by a simple-command, in which case the assignments are in
+effect only for the duration of the command (such assignments are also
+exported; see below for the implications of this).
+Note that both the parameter name and the
+.Ql =
+must be unquoted for the shell to recognize a parameter assignment.
+The fourth way of setting a parameter is with the
+.Ic export ,
+.Ic readonly ,
+and
+.Ic typeset
+commands; see their descriptions in the
+.Sx Command execution
+section.
+Fifth,
+.Ic for
+loops set parameters as well as the
+.Ic getopts ,
+.Ic read ,
+and
+.Ic set -A
+commands.
+Lastly, parameters can be assigned values using assignment operators
+inside arithmetic expressions (see
+.Sx Arithmetic expressions
+below) or using the
+.Sm off
+.Pf ${ Ar name No = Ar value No }
+.Sm on
+form of the parameter substitution (see below).
+.Pp
+Parameters with the export attribute (set using the
+.Ic export
+or
+.Ic typeset Fl x
+commands, or by parameter assignments followed by simple commands) are put in
+the environment (see
+.Xr environ 7 )
+of commands run by the shell as
+.Ar name Ns = Ns Ar value
+pairs.
+The order in which parameters appear in the environment of a command is
+unspecified.
+When the shell starts up, it extracts parameters and their values
+from its environment and automatically sets the export attribute for those
+parameters.
+.Pp
+Modifiers can be applied to the
+.Pf ${ Ns Ar name Ns }
+form of parameter substitution:
+.Bl -tag -width Ds
+.Sm off
+.It ${ Ar name No :- Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+it is substituted; otherwise,
+.Ar word
+is substituted.
+.Sm off
+.It ${ Ar name No :+ Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+.Ar word
+is substituted; otherwise, nothing is substituted.
+.Sm off
+.It ${ Ar name No := Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+it is substituted; otherwise, it is assigned
+.Ar word
+and the resulting value of
+.Ar name
+is substituted.
+.Sm off
+.It ${ Ar name No :? Ar word No }
+.Sm on
+If
+.Ar name
+is set and not
+.Dv NULL ,
+it is substituted; otherwise,
+.Ar word
+is printed on standard error (preceded by
+.Ar name : )
+and an error occurs (normally causing termination of a shell script, function,
+or script sourced using the
+.Sq \&.
+built-in).
+If
+.Ar word
+is omitted, the string
+.Dq parameter null or not set
+is used instead.
+.El
+.Pp
+In the above modifiers, the
+.Ql \&:
+can be omitted, in which case the conditions only depend on
+.Ar name
+being set (as opposed to set and not
+.Dv NULL ) .
+If
+.Ar word
+is needed, parameter, command, arithmetic, and tilde substitution are performed
+on it; if
+.Ar word
+is not needed, it is not evaluated.
+.Pp
+The following forms of parameter substitution can also be used:
+.Pp
+.Bl -tag -width Ds -compact
+.It Pf ${# Ns Ar name Ns \&}
+The number of positional parameters if
+.Ar name
+is
+.Ql * ,
+.Ql @ ,
+or not specified; otherwise the length of the string value of parameter
+.Ar name .
+.Pp
+.It Pf ${# Ns Ar name Ns [*]}
+.It Pf ${# Ns Ar name Ns [@]}
+The number of elements in the array
+.Ar name .
+.Pp
+.Sm off
+.It Xo
+.Pf ${ Ar name
+.Pf # Ar pattern No }
+.Xc
+.It Xo
+.Pf ${ Ar name
+.Pf ## Ar pattern No }
+.Xc
+.Sm on
+If
+.Ar pattern
+matches the beginning of the value of parameter
+.Ar name ,
+the matched text is deleted from the result of substitution.
+A single
+.Ql #
+results in the shortest match, and two
+of them result in the longest match.
+.Pp
+.Sm off
+.It Xo
+.Pf ${ Ar name
+.Pf % Ar pattern No }
+.Xc
+.It Xo
+.Pf ${ Ar name
+.Pf %% Ar pattern No }
+.Xc
+.Sm on
+Like ${..#..} substitution, but it deletes from the end of the value.
+.El
+.Pp
+The following special parameters are implicitly set by the shell and cannot be
+set directly using assignments:
+.Bl -tag -width "1 ... 9"
+.It Ev \&!
+Process ID of the last background process started.
+If no background processes have been started, the parameter is not set.
+.It Ev \&#
+The number of positional parameters ($1, $2, etc.).
+.It Ev \&$
+The PID of the shell, or the PID of the original shell if it is a subshell.
+Do
+.Em NOT
+use this mechanism for generating temporary file names; see
+.Xr mktemp 1
+instead.
+.It Ev -
+The concatenation of the current single letter options (see the
+.Ic set
+command below for a list of options).
+.It Ev \&?
+The exit status of the last non-asynchronous command executed.
+If the last command was killed by a signal,
+.Ic $?\&
+is set to 128 plus the signal number.
+.It Ev 0
+The name of the shell, determined as follows:
+the first argument to
+.Nm
+if it was invoked with the
+.Fl c
+option and arguments were given; otherwise the
+.Ar file
+argument, if it was supplied;
+or else the basename the shell was invoked with (i.e.\&
+.Li argv[0] ) .
+.Ev $0
+is also set to the name of the current script or
+the name of the current function, if it was defined with the
+.Ic function
+keyword (i.e. a Korn shell style function).
+.It Ev 1 No ... Ev 9
+The first nine positional parameters that were supplied to the shell, function,
+or script sourced using the
+.Sq \&.
+built-in.
+Further positional parameters may be accessed using
+.Pf ${ Ar number Ns } .
+.It Ev *
+All positional parameters (except parameter 0) i.e. $1, $2, $3, ...
+If used
+outside of double quotes, parameters are separate words (which are subjected
+to word splitting); if used within double quotes, parameters are separated
+by the first character of the
+.Ev IFS
+parameter (or the empty string if
+.Ev IFS
+is
+.Dv NULL ) .
+.It Ev @
+Same as
+.Ic $* ,
+unless it is used inside double quotes, in which case a separate word is
+generated for each positional parameter.
+If there are no positional parameters, no word is generated.
+.Ic $@
+can be used to access arguments, verbatim, without losing
+.Dv NULL
+arguments or splitting arguments with spaces.
+.El
+.Pp
+The following parameters are set and/or used by the shell:
+.Bl -tag -width "EXECSHELL"
+.It Ev CDPATH
+Search path for the
+.Ic cd
+built-in command.
+It works the same way as
+.Ev PATH
+for those directories not beginning with
+.Ql /
+in
+.Ic cd
+commands.
+Note that if
+.Ev CDPATH
+is set and does not contain
+.Sq \&.
+or contains an empty path, the current directory is not searched.
+Also, the
+.Ic cd
+built-in command will display the resulting directory when a match is found
+in any search path other than the empty path.
+.It Ev COLUMNS
+Set to the number of columns on the terminal or window.
+Currently set to the
+.Dq cols
+value as reported by
+.Xr stty 1
+if that value is non-zero.
+This parameter is used by the
+.Ic set -o
+and
+.Ic kill -l
+commands to format information columns.
+.It Ev ENV
+If this parameter is found to be set after any profile files are executed, the
+expanded value is used as a shell startup file.
+It typically contains function and alias definitions.
+.It Ev ERRNO
+Integer value of the shell's
+.Va errno
+variable.
+It indicates the reason the last system call failed.
+Not yet implemented.
+.It Ev EXECSHELL
+If set, this parameter is assumed to contain the shell that is to be used to
+execute commands that
+.Xr execve 2
+fails to execute and which do not start with a
+.Dq #! Ns Ar shell
+sequence.
+.It Ev FCEDIT
+The editor used by the
+.Ic fc
+command (see below).
+.It Ev FPATH
+Like
+.Ev PATH ,
+but used when an undefined function is executed to locate the file defining the
+function.
+It is also searched when a command can't be found using
+.Ev PATH .
+See
+.Sx Functions
+below for more information.
+.It Ev HOME
+The default directory for the
+.Ic cd
+command and the value substituted for an unqualified
+.Ic ~
+(see
+.Sx Tilde expansion
+below).
+.It Ev IFS
+Internal field separator, used during substitution and by the
+.Ic read
+command, to split values into distinct arguments; normally set to space, tab,
+and newline.
+See
+.Sx Substitution
+above for details.
+.Pp
+.Sy Note :
+This parameter is not imported from the environment when the shell is
+started.
+.It Ev SH_VERSION
+The version of shell and the date the version was created (read-only).
+.It Ev LINENO
+The line number of the function or shell script that is currently being
+executed.
+.It Ev LINES
+Set to the number of lines on the terminal or window.
+.It Ev OLDPWD
+The previous working directory.
+Unset if
+.Ic cd
+has not successfully changed directories since the shell started, or if the
+shell doesn't know where it is.
+.It Ev OPTARG
+When using
+.Ic getopts ,
+it contains the argument for a parsed option, if it requires one.
+.It Ev OPTIND
+The index of the next argument to be processed when using
+.Ic getopts .
+Assigning 1 to this parameter causes
+.Ic getopts
+to process arguments from the beginning the next time it is invoked.
+.It Ev PATH
+A colon separated list of directories that are searched when looking for
+commands and files sourced using the
+.Sq \&.
+command (see below).
+An empty string resulting from a leading or trailing
+colon, or two adjacent colons, is treated as a
+.Sq \&.
+(the current directory).
+.It Ev POSIXLY_CORRECT
+If set, this parameter causes the
+.Ic posix
+option to be enabled.
+See
+.Sx POSIX mode
+below.
+.It Ev PPID
+The process ID of the shell's parent (read-only).
+.It Ev PS1
+The primary prompt for interactive shells.
+Parameter, command, and arithmetic
+substitutions are performed.
+.Ql \&!
+is replaced with the current command number (see the
+.Ic fc
+command below).
+A literal
+.Ql \&!
+can be put in the prompt by placing
+.Ql !!
+in
+.Ev PS1 .
+The default is
+.Sq $\ \&
+for non-root users,
+.Sq #\ \&
+for root.
+.It Ev PS2
+Secondary prompt string, by default
+.Sq \*(Gt\ \& ,
+used when more input is needed to complete a command.
+.It Ev PS4
+Used to prefix commands that are printed during execution tracing (see the
+.Ic set Fl x
+command below).
+Parameter, command, and arithmetic substitutions are performed
+before it is printed.
+The default is
+.Sq +\ \& .
+.It Ev PWD
+The current working directory.
+May be unset or
+.Dv NULL
+if the shell doesn't know where it is.
+.It Ev REPLY
+Default parameter for the
+.Ic read
+command if no names are given.
+.It Ev TMPDIR
+The directory temporary shell files are created in.
+If this parameter is not
+set, or does not contain the absolute path of a writable directory, temporary
+files are created in
+.Pa /tmp .
+.El
+.Ss Tilde expansion
+Tilde expansion, which is done in parallel with parameter substitution, is done
+on words starting with an unquoted
+.Ql ~ .
+The characters following the tilde, up to the first
+.Ql / ,
+if any, are assumed to be a login name.
+If the login name is empty,
+.Ql + ,
+or
+.Ql - ,
+the value of the
+.Ev HOME ,
+.Ev PWD ,
+or
+.Ev OLDPWD
+parameter is substituted, respectively.
+Otherwise, the password file is
+searched for the login name, and the tilde expression is substituted with the
+user's home directory.
+If the login name is not found in the password file or
+if any quoting or parameter substitution occurs in the login name, no
+substitution is performed.
+.Pp
+In parameter assignments
+(such as those preceding a simple-command or those occurring
+in the arguments of
+.Ic alias ,
+.Ic export ,
+.Ic readonly ,
+and
+.Ic typeset ) ,
+tilde expansion is done after any assignment
+(i.e. after the equals sign)
+or after an unquoted colon
+.Pq Sq \&: ;
+login names are also delimited by colons.
+.Pp
+The home directory of previously expanded login names are cached and re-used.
+The
+.Ic alias -d
+command may be used to list, change, and add to this cache (e.g.\&
+.Ic alias -d fac=/usr/local/facilities; cd ~fac/bin ) .
+.Ss File name patterns
+A file name pattern is a word containing one or more unquoted
+.Ql \&? ,
+.Ql * ,
+.Ql + ,
+.Ql @ ,
+or
+.Ql \&!
+characters or
+.Dq [..]
+sequences.
+The shell replaces file
+name patterns with the sorted names of all the files that match the pattern
+(if no files match, the word is left unchanged).
+The pattern elements have the following meaning:
+.Bl -tag -width Ds
+.It \&?
+Matches any single character.
+.It \&*
+Matches any sequence of characters.
+.It [..]
+Matches any of the characters inside the brackets.
+Ranges of characters can be
+specified by separating two characters by a
+.Ql -
+(e.g.\&
+.Dq [a0-9]
+matches the letter
+.Sq a
+or any digit).
+In order to represent itself, a
+.Ql -
+must either be quoted or the first or last character in the character list.
+Similarly, a
+.Ql \&]
+must be quoted or the first character in the list if it is to represent itself
+instead of the end of the list.
+Also, a
+.Ql \&!
+appearing at the start of the list has special meaning (see below), so to
+represent itself it must be quoted or appear later in the list.
+.Pp
+Within a bracket expression, the name of a
+.Em character class
+enclosed in
+.Sq [:
+and
+.Sq :]
+stands for the list of all characters belonging to that class.
+Supported character classes:
+.Bd -literal -offset indent
+alnum cntrl lower space
+alpha digit print upper
+blank graph punct xdigit
+.Ed
+.Pp
+These match characters using the macros specified in
+.Xr isalnum 3 ,
+.Xr isalpha 3 ,
+and so on.
+A character class may not be used as an endpoint of a range.
+.It [!..]
+Like [..],
+except it matches any character not inside the brackets.
+.Sm off
+.It *( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches any string of characters that matches zero or more occurrences of the
+specified patterns.
+Example: The pattern
+.Ic *(foo|bar)
+matches the strings
+.Dq ,
+.Dq foo ,
+.Dq bar ,
+.Dq foobarfoo ,
+etc.
+.Sm off
+.It +( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches any string of characters that matches one or more occurrences of the
+specified patterns.
+Example: The pattern
+.Ic +(foo|bar)
+matches the strings
+.Dq foo ,
+.Dq bar ,
+.Dq foobar ,
+etc.
+.Sm off
+.It ?( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches the empty string or a string that matches one of the specified
+patterns.
+Example: The pattern
+.Ic ?(foo|bar)
+only matches the strings
+.Dq ,
+.Dq foo ,
+and
+.Dq bar .
+.Sm off
+.It @( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches a string that matches one of the specified patterns.
+Example: The pattern
+.Ic @(foo|bar)
+only matches the strings
+.Dq foo
+and
+.Dq bar .
+.Sm off
+.It !( Ar pattern\*(Ba No ...\*(Ba Ar pattern )
+.Sm on
+Matches any string that does not match one of the specified patterns.
+Examples: The pattern
+.Ic !(foo|bar)
+matches all strings except
+.Dq foo
+and
+.Dq bar ;
+the pattern
+.Ic !(*)
+matches no strings; the pattern
+.Ic !(?)*\&
+matches all strings (think about it).
+.El
+.Pp
+Note that
+.Nm pdksh
+currently never matches
+.Sq \&.
+and
+.Sq .. ,
+but the original
+.Nm ksh ,
+Bourne
+.Nm sh ,
+and bash do, so this may have to change (too bad).
+.Pp
+Note that none of the above pattern elements match either a period
+.Pq Sq \&.
+at the start of a file name or a slash
+.Pq Sq / ,
+even if they are explicitly used in a [..] sequence; also, the names
+.Sq \&.
+and
+.Sq ..
+are never matched, even by the pattern
+.Sq .* .
+.Pp
+If the
+.Ic markdirs
+option is set, any directories that result from file name generation are marked
+with a trailing
+.Ql / .
+.Ss Input/output redirection
+When a command is executed, its standard input, standard output, and standard
+error (file descriptors 0, 1, and 2, respectively) are normally inherited from
+the shell.
+Three exceptions to this are commands in pipelines, for which
+standard input and/or standard output are those set up by the pipeline,
+asynchronous commands created when job control is disabled, for which standard
+input is initially set to be from
+.Pa /dev/null ,
+and commands for which any of the following redirections have been specified:
+.Bl -tag -width Ds
+.It \*(Gt Ar file
+Standard output is redirected to
+.Ar file .
+If
+.Ar file
+does not exist, it is created; if it does exist, is a regular file, and the
+.Ic noclobber
+option is set, an error occurs; otherwise, the file is truncated.
+Note that this means the command
+.Ic cmd \*(Lt foo \*(Gt foo
+will open
+.Ar foo
+for reading and then truncate it when it opens it for writing, before
+.Ar cmd
+gets a chance to actually read
+.Ar foo .
+.It \*(Gt\*(Ba Ar file
+Same as
+.Ic \*(Gt ,
+except the file is truncated, even if the
+.Ic noclobber
+option is set.
+.It \*(Gt\*(Gt Ar file
+Same as
+.Ic \*(Gt ,
+except if
+.Ar file
+exists it is appended to instead of being truncated.
+Also, the file is opened
+in append mode, so writes always go to the end of the file (see
+.Xr open 2 ) .
+.It \*(Lt Ar file
+Standard input is redirected from
+.Ar file ,
+which is opened for reading.
+.It \*(Lt\*(Gt Ar file
+Same as
+.Ic \*(Lt ,
+except the file is opened for reading and writing.
+.It \*(Lt\*(Lt Ar marker
+After reading the command line containing this kind of redirection (called a
+.Dq here document ) ,
+the shell copies lines from the command source into a temporary file until a
+line matching
+.Ar marker
+is read.
+When the command is executed, standard input is redirected from the
+temporary file.
+If
+.Ar marker
+contains no quoted characters, the contents of the temporary file are processed
+as if enclosed in double quotes each time the command is executed, so
+parameter, command, and arithmetic substitutions are performed, along with
+backslash
+.Pq Sq \e
+escapes for
+.Ql $ ,
+.Ql ` ,
+.Ql \e ,
+and
+.Ql \enewline .
+If multiple here documents are used on the same command line, they are saved in
+order.
+.It \*(Lt\*(Lt- Ar marker
+Same as
+.Ic \*(Lt\*(Lt ,
+except leading tabs are stripped from lines in the here document.
+.It \*(Lt& Ar fd
+Standard input is duplicated from file descriptor
+.Ar fd .
+.Ar fd
+can be a single digit, indicating the number of an existing file descriptor;
+the letter
+.Ql p ,
+indicating the file descriptor associated with the output of the current
+co-process; or the character
+.Ql - ,
+indicating standard input is to be closed.
+.It \*(Gt& Ar fd
+Same as
+.Ic \*(Lt& ,
+except the operation is done on standard output.
+.El
+.Pp
+In any of the above redirections, the file descriptor that is redirected
+(i.e. standard input or standard output)
+can be explicitly given by preceding the
+redirection with a single digit.
+Parameter, command, and arithmetic
+substitutions, tilde substitutions, and (if the shell is interactive)
+file name generation are all performed on the
+.Ar file ,
+.Ar marker ,
+and
+.Ar fd
+arguments of redirections.
+Note, however, that the results of any file name
+generation are only used if a single file is matched; if multiple files match,
+the word with the expanded file name generation characters is used.
+Note
+that in restricted shells, redirections which can create files cannot be used.
+.Pp
+For simple-commands, redirections may appear anywhere in the command; for
+compound-commands
+.Po
+.Ic if
+statements, etc.
+.Pc ,
+any redirections must appear at the end.
+Redirections are processed after
+pipelines are created and in the order they are given, so the following
+will print an error with a line number prepended to it:
+.Pp
+.D1 $ cat /foo/bar 2\*(Gt&1 \*(Gt /dev/null \*(Ba cat -n
+.Ss Arithmetic expressions
+Integer arithmetic expressions can be used with the
+.Ic let
+command, inside $((..)) expressions, inside array references (e.g.\&
+.Ar name Ns Bq Ar expr ) ,
+as numeric arguments to the
+.Ic test
+command, and as the value of an assignment to an integer parameter.
+.Pp
+Expressions may contain alpha-numeric parameter identifiers, array references,
+and integer constants and may be combined with the following C operators
+(listed and grouped in increasing order of precedence):
+.Pp
+Unary operators:
+.Bd -literal -offset indent
++ - ! ~ ++ --
+.Ed
+.Pp
+Binary operators:
+.Bd -literal -offset indent
+,
+= *= /= %= += -= \*(Lt\*(Lt= \*(Gt\*(Gt= &= ^= \*(Ba=
+\*(Ba\*(Ba
+&&
+\*(Ba
+^
+&
+== !=
+\*(Lt \*(Lt= \*(Gt= \*(Gt
+\*(Lt\*(Lt \*(Gt\*(Gt
++ -
+* / %
+.Ed
+.Pp
+Ternary operators:
+.Bd -literal -offset indent
+?: (precedence is immediately higher than assignment)
+.Ed
+.Pp
+Grouping operators:
+.Bd -literal -offset indent
+( )
+.Ed
+.Pp
+A parameter that is NULL or unset evaluates to 0.
+Integer constants may be specified with arbitrary bases using the notation
+.Ar base Ns # Ns Ar number ,
+where
+.Ar base
+is a decimal integer specifying the base, and
+.Ar number
+is a number in the specified base.
+Additionally,
+integers may be prefixed with
+.Sq 0X
+or
+.Sq 0x
+(specifying base 16)
+or
+.Sq 0
+(base 8)
+in all forms of arithmetic expressions,
+except as numeric arguments to the
+.Ic test
+command.
+.Pp
+The operators are evaluated as follows:
+.Bl -tag -width Ds -offset indent
+.It unary +
+Result is the argument (included for completeness).
+.It unary -
+Negation.
+.It \&!
+Logical NOT;
+the result is 1 if argument is zero, 0 if not.
+.It ~
+Arithmetic (bit-wise) NOT.
+.It ++
+Increment; must be applied to a parameter (not a literal or other expression).
+The parameter is incremented by 1.
+When used as a prefix operator, the result
+is the incremented value of the parameter; when used as a postfix operator, the
+result is the original value of the parameter.
+.It --
+Similar to
+.Ic ++ ,
+except the parameter is decremented by 1.
+.It \&,
+Separates two arithmetic expressions; the left-hand side is evaluated first,
+then the right.
+The result is the value of the expression on the right-hand side.
+.It =
+Assignment; the variable on the left is set to the value on the right.
+.It Xo
+.No *= /= += -= \*(Lt\*(Lt=
+.No \*(Gt\*(Gt= &= ^= \*(Ba=
+.Xc
+Assignment operators.
+.Sm off
+.Ao Ar var Ac Xo
+.Aq Ar op
+.No = Aq Ar expr
+.Xc
+.Sm on
+is the same as
+.Sm off
+.Ao Ar var Ac Xo
+.No = Aq Ar var
+.Aq Ar op
+.Aq Ar expr ,
+.Xc
+.Sm on
+with any operator precedence in
+.Aq Ar expr
+preserved.
+For example,
+.Dq var1 *= 5 + 3
+is the same as specifying
+.Dq var1 = var1 * (5 + 3) .
+.It \*(Ba\*(Ba
+Logical OR;
+the result is 1 if either argument is non-zero, 0 if not.
+The right argument is evaluated only if the left argument is zero.
+.It &&
+Logical AND;
+the result is 1 if both arguments are non-zero, 0 if not.
+The right argument is evaluated only if the left argument is non-zero.
+.It \*(Ba
+Arithmetic (bit-wise) OR.
+.It ^
+Arithmetic (bit-wise) XOR
+(exclusive-OR).
+.It &
+Arithmetic (bit-wise) AND.
+.It ==
+Equal; the result is 1 if both arguments are equal, 0 if not.
+.It !=
+Not equal; the result is 0 if both arguments are equal, 1 if not.
+.It \*(Lt
+Less than; the result is 1 if the left argument is less than the right, 0 if
+not.
+.It \*(Lt= \*(Gt= \*(Gt
+Less than or equal, greater than or equal, greater than.
+See
+.Ic \*(Lt .
+.It \*(Lt\*(Lt \*(Gt\*(Gt
+Shift left (right); the result is the left argument with its bits shifted left
+(right) by the amount given in the right argument.
+.It + - * /
+Addition, subtraction, multiplication, and division.
+.It %
+Remainder; the result is the remainder of the division of the left argument by
+the right.
+The sign of the result is unspecified if either argument is negative.
+.It Xo
+.Sm off
+.Aq Ar arg1 ?
+.Aq Ar arg2 :
+.Aq Ar arg3
+.Sm on
+.Xc
+If
+.Aq Ar arg1
+is non-zero, the result is
+.Aq Ar arg2 ;
+otherwise the result is
+.Aq Ar arg3 .
+.El
+.Ss Functions
+Functions are defined using either Korn shell
+.Ic function Ar function-name
+syntax or the Bourne/POSIX shell
+.Ar function-name Ns ()
+syntax (see below for the difference between the two forms).
+Functions are like
+.Li .-scripts
+(i.e. scripts sourced using the
+.Sq \&.
+built-in)
+in that they are executed in the current environment.
+However, unlike
+.Li .-scripts ,
+shell arguments (i.e. positional parameters $1, $2, etc.)\&
+are never visible inside them.
+When the shell is determining the location of a command, functions
+are searched after special built-in commands, before regular and
+non-regular built-ins, and before the
+.Ev PATH
+is searched.
+.Pp
+An existing function may be deleted using
+.Ic unset Fl f Ar function-name .
+A list of functions can be obtained using
+.Ic typeset +f
+and the function definitions can be listed using
+.Ic typeset -f .
+The
+.Ic autoload
+command (which is an alias for
+.Ic typeset -fu )
+may be used to create undefined functions: when an undefined function is
+executed, the shell searches the path specified in the
+.Ev FPATH
+parameter for a file with the same name as the function, which, if found, is
+read and executed.
+If after executing the file the named function is found to
+be defined, the function is executed; otherwise, the normal command search is
+continued (i.e. the shell searches the regular built-in command table and
+.Ev PATH ) .
+Note that if a command is not found using
+.Ev PATH ,
+an attempt is made to autoload a function using
+.Ev FPATH
+(this is an undocumented feature of the original Korn shell).
+.Pp
+Functions can have two attributes,
+.Dq trace
+and
+.Dq export ,
+which can be set with
+.Ic typeset -ft
+and
+.Ic typeset -fx ,
+respectively.
+When a traced function is executed, the shell's
+.Ic xtrace
+option is turned on for the function's duration; otherwise, the
+.Ic xtrace
+option is turned off.
+The
+.Dq export
+attribute of functions is currently not used.
+In the original Korn shell,
+exported functions are visible to shell scripts that are executed.
+.Pp
+Since functions are executed in the current shell environment, parameter
+assignments made inside functions are visible after the function completes.
+If this is not the desired effect, the
+.Ic typeset
+command can be used inside a function to create a local parameter.
+Note that special parameters (e.g.\&
+.Ic \&$$ , $! )
+can't be scoped in this way.
+.Pp
+The exit status of a function is that of the last command executed in the
+function.
+A function can be made to finish immediately using the
+.Ic return
+command; this may also be used to explicitly specify the exit status.
+.Pp
+Functions defined with the
+.Ic function
+reserved word are treated differently in the following ways from functions
+defined with the
+.Ic ()
+notation:
+.Bl -bullet
+.It
+The $0 parameter is set to the name of the function
+(Bourne-style functions leave $0 untouched).
+.It
+Parameter assignments preceding function calls are not kept in the shell
+environment (executing Bourne-style functions will keep assignments).
+.It
+.Ev OPTIND
+is saved/reset and restored on entry and exit from the function so
+.Ic getopts
+can be used properly both inside and outside the function (Bourne-style
+functions leave
+.Ev OPTIND
+untouched, so using
+.Ic getopts
+inside a function interferes with using
+.Ic getopts
+outside the function).
+.El
+.Pp
+In the future, the following differences will also be added:
+.Bl -bullet
+.It
+A separate trap/signal environment will be used during the execution of
+functions.
+This will mean that traps set inside a function will not affect the
+shell's traps and signals that are not ignored in the shell (but may be
+trapped) will have their default effect in a function.
+.It
+The EXIT trap, if set in a function, will be executed after the function
+returns.
+.El
+.Ss POSIX mode
+The shell is intended to be POSIX compliant;
+however, in some cases, POSIX behaviour is contrary either to
+the original Korn shell behaviour or to user convenience.
+How the shell behaves in these cases is determined by the state of the
+.Ic posix
+option
+.Pq Ic set -o posix .
+If it is on, the POSIX behaviour is followed; otherwise, it is not.
+The
+.Ic posix
+option is set automatically when the shell starts up if the environment
+contains the
+.Ev POSIXLY_CORRECT
+parameter.
+The shell can also be compiled so that it is in POSIX mode by default;
+however, this is usually not desirable.
+.Pp
+The following is a list of things that are affected by the state of the
+.Ic posix
+option:
+.Bl -bullet
+.It
+Reading of
+.Ev $ENV :
+if not in
+.Ic posix
+mode, the
+.Ev ENV
+parameter is not expanded and included when the shell starts.
+.It
+Occurrences of
+.Ic \e\&"
+inside double quoted
+.Ic `..`
+command substitutions.
+In POSIX mode, the
+.Ic \e\&"
+is interpreted when the command is interpreted;
+in non-POSIX mode,
+the backslash is stripped before the command substitution is interpreted.
+For example,
+.Ic echo \&"`echo \e\&"hi\e\&"`\&"
+produces
+.Dq \&"hi\&"
+in POSIX mode,
+.Dq hi
+in non-POSIX mode.
+To avoid problems, use the
+.Ic $(...)\&
+form of command substitution.
+.It
+.Ic kill -l
+output.
+In POSIX mode, only signal names are listed (in a single line);
+in non-POSIX mode,
+signal numbers, names, and descriptions are printed (in columns).
+In the future, a new option
+.Pq Fl v No perhaps
+will be added to distinguish the two behaviours.
+.It
+.Ic echo
+options.
+In POSIX mode,
+.Fl e
+and
+.Fl E
+are not treated as options, but printed like other arguments;
+in non-POSIX mode, these options control the interpretation
+of backslash sequences.
+.It
+.Ic fg
+exit status.
+In POSIX mode, the exit status is 0 if no errors occur;
+in non-POSIX mode, the exit status is that of the last foregrounded job.
+.It
+.Ic getopts .
+In POSIX mode, options must start with a
+.Ql - ;
+in non-POSIX mode, options can start with either
+.Ql -
+or
+.Ql + .
+.It
+.Ic set - .
+In POSIX mode, this does not clear the
+.Ic verbose
+or
+.Ic xtrace
+options; in non-POSIX mode, it does.
+.It
+.Ic set
+exit status.
+In POSIX mode, the exit status of
+.Ic set
+is 0 if there are no errors;
+in non-POSIX mode, the exit status is that of any
+command substitutions performed in generating the
+.Ic set
+command.
+For example,
+.Ic set -- `false`; echo $?\&
+prints 0 in POSIX mode, 1 in non-POSIX mode.
+This construct is used in most shell scripts that use the old
+.Xr getopt 1
+command.
+.It
+Argument expansion of the
+.Ic alias ,
+.Ic export ,
+.Ic readonly ,
+and
+.Ic typeset
+commands.
+In POSIX mode, normal argument expansion is done; in non-POSIX mode,
+field splitting, file globbing, and (normal) tilde expansion
+are turned off, while assignment tilde expansion is turned on.
+.It
+Signal specification.
+In POSIX mode, signals can be specified as digits, only
+if signal numbers match POSIX values
+(i.e. HUP=1, INT=2, QUIT=3, ABRT=6, KILL=9, ALRM=14, and TERM=15);
+in non-POSIX mode, signals can always be digits.
+.It
+Alias expansion.
+In POSIX mode, alias expansion is only carried out when reading command words;
+in non-POSIX mode, alias expansion is carried out on any
+word following an alias that ended in a space.
+For example, the following
+.Ic for
+loop uses parameter
+.Sq i
+in POSIX mode and
+.Sq j
+in non-POSIX mode:
+.Bd -literal -offset indent
+alias a='for ' i='j'
+a i in 1 2; do echo i=$i j=$j; done
+.Ed
+.It
+.Ic test .
+In POSIX mode, the expression
+.Sq Fl t
+(preceded by some number of
+.Sq \&!
+arguments) is always true as it is a non-zero length string;
+in non-POSIX mode, it tests if file descriptor 1 is a
+.Xr tty 4
+(i.e. the
+.Ar fd
+argument to the
+.Fl t
+test may be left out and defaults to 1).
+.El
+.Ss Command execution
+After evaluation of command-line arguments, redirections, and parameter
+assignments, the type of command is determined: a special built-in, a
+function, a regular built-in, or the name of a file to execute found using the
+.Ev PATH
+parameter.
+The checks are made in the above order.
+Special built-in commands differ from other commands in that the
+.Ev PATH
+parameter is not used to find them, an error during their execution can
+cause a non-interactive shell to exit, and parameter assignments that are
+specified before the command are kept after the command completes.
+Just to confuse things, if the
+.Ic posix
+option is turned off (see the
+.Ic set
+command below), some special commands are very special in that no field
+splitting, file globbing, nor tilde expansion is performed
+on arguments that look like assignments.
+Regular built-in commands are different only in that the
+.Ev PATH
+parameter is not used to find them.
+.Pp
+The original
+.Nm ksh
+and POSIX differ somewhat in which commands are considered
+special or regular:
+.Pp
+POSIX special commands
+.Pp
+.Ic \&. , \&: , break , continue ,
+.Ic eval , exec , exit , export ,
+.Ic readonly , return , set , shift ,
+.Ic times , trap , unset
+.Pp
+Additional
+.Nm
+special commands
+.Pp
+.Ic builtin , typeset
+.Pp
+Very special commands
+.Pq when POSIX mode is off
+.Pp
+.Ic alias , readonly , set , typeset
+.Pp
+POSIX regular commands
+.Pp
+.Ic alias , bg , cd , command ,
+.Ic false , fc , fg , getopts ,
+.Ic jobs , kill , pwd , read ,
+.Ic true , umask , unalias , wait
+.Pp
+Additional
+.Nm
+regular commands
+.Pp
+.Ic \&[ , echo , let , print ,
+.Ic pwd , test , ulimit , whence
+.Pp
+In the future, the additional
+.Nm
+special and regular commands may be treated
+differently from the POSIX special and regular commands.
+.Pp
+Once the type of command has been determined, any command-line parameter
+assignments are performed and exported for the duration of the command.
+.Pp
+The following describes the special and regular built-in commands:
+.Pp
+.Bl -tag -width Ds -compact
+.It Ic \&. Ar file Op Ar arg ...
+Execute the commands in
+.Ar file
+in the current environment.
+The file is searched for in the directories of
+.Ev PATH .
+If arguments are given, the positional parameters may be used to access them
+while
+.Ar file
+is being executed.
+If no arguments are given, the positional parameters are
+those of the environment the command is used in.
+.Pp
+.It Ic \&: Op Ar ...
+The null command.
+Exit status is set to zero.
+.Pp
+.It Xo Ic alias
+.Oo Fl d \*(Ba t Oo Fl r Oc \*(Ba
+.Cm +-x Oc
+.Op Fl p
+.Op Cm +
+.Oo Ar name
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+Without arguments,
+.Ic alias
+lists all aliases.
+For any name without a value, the existing alias is listed.
+Any name with a value defines an alias (see
+.Sx Aliases
+above).
+.Pp
+When listing aliases, one of two formats is used.
+Normally, aliases are listed as
+.Ar name Ns = Ns Ar value ,
+where
+.Ar value
+is quoted.
+If options were preceded with
+.Ql + ,
+or a lone
+.Ql +
+is given on the command line, only
+.Ar name
+is printed.
+.Pp
+The
+.Fl d
+option causes directory aliases, which are used in tilde expansion, to be
+listed or set (see
+.Sx Tilde expansion
+above).
+.Pp
+If the
+.Fl p
+option is used, each alias is prefixed with the string
+.Dq alias\ \& .
+.Pp
+The
+.Fl t
+option indicates that tracked aliases are to be listed/set (values specified on
+the command line are ignored for tracked aliases).
+The
+.Fl r
+option indicates that all tracked aliases are to be reset.
+.Pp
+The
+.Fl x
+option sets
+.Pq Ic +x No clears
+the export attribute of an alias, or, if no names are given, lists the aliases
+with the export attribute (exporting an alias has no effect).
+.Pp
+.It Ic bg Op Ar job ...
+Resume the specified stopped job(s) in the background.
+If no jobs are specified,
+.Ic %+
+is assumed.
+See
+.Sx Job control
+below for more information.
+.Pp
+.It Ic break Op Ar level
+Exit the
+.Ar level Ns th
+inner-most
+.Ic for ,
+.Ic until ,
+or
+.Ic while
+loop.
+.Ar level
+defaults to 1.
+.Pp
+.It Ic builtin Ar command Op Ar arg ...
+Execute the built-in command
+.Ar command .
+.Pp
+.It Xo
+.Ic cd
+.Op Fl LP
+.Op Ar dir
+.Xc
+Set the working directory to
+.Ar dir .
+If the parameter
+.Ev CDPATH
+is set, it lists the search path for the directory containing
+.Ar dir .
+A
+.Dv NULL
+path means the current directory.
+If
+.Ar dir
+is found in any component of the
+.Ev CDPATH
+search path other than the
+.Dv NULL
+path, the name of the new working directory will be written to standard output.
+If
+.Ar dir
+is missing, the home directory
+.Ev HOME
+is used.
+If
+.Ar dir
+is
+.Ql - ,
+the previous working directory is used (see the
+.Ev OLDPWD
+parameter).
+.Pp
+If the
+.Fl L
+option (logical path) is used or if the
+.Ic physical
+option isn't set (see the
+.Ic set
+command below), references to
+.Sq ..
+in
+.Ar dir
+are relative to the path used to get to the directory.
+If the
+.Fl P
+option (physical path) is used or if the
+.Ic physical
+option is set,
+.Sq ..
+is relative to the filesystem directory tree.
+The
+.Ev PWD
+and
+.Ev OLDPWD
+parameters are updated to reflect the current and old working directory,
+respectively.
+.Pp
+.It Xo
+.Ic cd
+.Op Fl LP
+.Ar old new
+.Xc
+The string
+.Ar new
+is substituted for
+.Ar old
+in the current directory, and the shell attempts to change to the new
+directory.
+.Pp
+.It Xo
+.Ic command
+.Op Fl p
+.Ar cmd
+.Op Ar arg ...
+.Xc
+.Ar cmd
+is executed exactly as if
+.Ic command
+had not been specified, with two exceptions:
+firstly,
+.Ar cmd
+cannot be a shell function;
+and secondly, special built-in commands lose their specialness
+(i.e. redirection and utility errors do not cause the shell to
+exit, and command assignments are not permanent).
+.Pp
+If the
+.Fl p
+option is given, a default search path is used instead of the current value of
+.Ev PATH
+(the actual value of the default path is system dependent: on
+POSIX-ish systems, it is the value returned by
+.Ic getconf CS_PATH ) .
+.Pp
+.It Ic continue Op Ar level
+Jumps to the beginning of the
+.Ar level Ns th
+inner-most
+.Ic for ,
+.Ic until ,
+or
+.Ic while
+loop.
+.Ar level
+defaults to 1.
+.Pp
+.It Xo
+.Ic echo
+.Op Fl Een
+.Op Ar arg ...
+.Xc
+Prints its arguments (separated by spaces) followed by a newline, to the
+standard output.
+The newline is suppressed if any of the arguments contain the
+backslash sequence
+.Ql \ec .
+See the
+.Ic print
+command below for a list of other backslash sequences that are recognized.
+.Pp
+The options are provided for compatibility with
+.Bx
+shell scripts.
+The
+.Fl n
+option suppresses the trailing newline,
+.Fl e
+enables backslash interpretation (a no-op, since this is normally done), and
+.Fl E
+suppresses backslash interpretation.
+If the
+.Ic posix
+option is set, only the first argument is treated as an option, and only
+if it is exactly
+.Dq -n .
+.Pp
+.It Ic eval Ar command ...
+The arguments are concatenated (with spaces between them) to form a single
+string which the shell then parses and executes in the current environment.
+.Pp
+.It Xo
+.Ic exec
+.Op Ar command Op Ar arg ...
+.Xc
+The command is executed without forking, replacing the shell process.
+.Pp
+If no command is given except for I/O redirection, the I/O redirection is
+permanent and the shell is
+not replaced.
+Any file descriptors which are opened or
+.Xr dup 2 Ns 'd
+in this way are made available to other executed commands (note that the Korn
+shell differs here: it does not pass on file descriptors greater than 2).
+.Pp
+.It Ic exit Op Ar status
+The shell exits with the specified exit status.
+If
+.Ar status
+is not specified, the exit status is the current value of the
+.Ic $?\&
+parameter.
+.Pp
+.It Xo
+.Ic export
+.Op Fl p
+.Op Ar parameter Ns Op = Ns Ar value
+.Xc
+Sets the export attribute of the named parameters.
+Exported parameters are passed in the environment to executed commands.
+If values are specified, the named parameters are also assigned.
+.Pp
+If no parameters are specified, the names of all parameters with the export
+attribute are printed one per line, unless the
+.Fl p
+option is used, in which case
+.Ic export
+commands defining all exported parameters, including their values, are printed.
+.Pp
+.It Ic false
+A command that exits with a non-zero status.
+.Pp
+.It Xo
+.Ic fc
+.Cm -e - \*(Ba Fl s
+.Op Fl g
+.Op Ar old Ns = Ns Ar new
+.Op Ar prefix
+.Xc
+Re-execute the selected command (the previous command by default) after
+performing the optional substitution of
+.Ar old
+with
+.Ar new .
+If
+.Fl g
+is specified, all occurrences of
+.Ar old
+are replaced with
+.Ar new .
+The meaning of
+.Cm -e -
+and
+.Fl s
+is identical: re-execute the selected command without invoking an editor.
+This command is usually accessed with the predefined
+.Ic alias r='fc -e -' .
+.Pp
+.It Ic fg Op Ar job ...
+Resume the specified job(s) in the foreground.
+If no jobs are specified,
+.Ic %+
+is assumed.
+See
+.Sx Job control
+below for more information.
+.Pp
+.It Xo
+.Ic getopts
+.Ar optstring name
+.Op Ar arg ...
+.Xc
+Used by shell procedures to parse the specified arguments (or positional
+parameters, if no arguments are given) and to check for legal options.
+.Ar optstring
+contains the option letters that
+.Ic getopts
+is to recognize.
+If a letter is followed by a colon, the option is expected to
+have an argument.
+Options that do not take arguments may be grouped in a single argument.
+If an option takes an argument and the option character is not the
+last character of the argument it is found in, the remainder of the argument is
+taken to be the option's argument; otherwise, the next argument is the option's
+argument.
+.Pp
+Each time
+.Ic getopts
+is invoked, it places the next option in the shell parameter
+.Ar name
+and the index of the argument to be processed by the next call to
+.Ic getopts
+in the shell parameter
+.Ev OPTIND .
+If the option was introduced with a
+.Ql + ,
+the option placed in
+.Ar name
+is prefixed with a
+.Ql + .
+When an option requires an argument,
+.Ic getopts
+places it in the shell parameter
+.Ev OPTARG .
+.Pp
+When an illegal option or a missing option argument is encountered, a question
+mark or a colon is placed in
+.Ar name
+(indicating an illegal option or missing argument, respectively) and
+.Ev OPTARG
+is set to the option character that caused the problem.
+Furthermore, if
+.Ar optstring
+does not begin with a colon, a question mark is placed in
+.Ar name ,
+.Ev OPTARG
+is unset, and an error message is printed to standard error.
+.Pp
+When the end of the options is encountered,
+.Ic getopts
+exits with a non-zero exit status.
+Options end at the first (non-option
+argument) argument that does not start with a
+.Ql - ,
+or when a
+.Ql --
+argument is encountered.
+.Pp
+Option parsing can be reset by setting
+.Ev OPTIND
+to 1 (this is done automatically whenever the shell or a shell procedure is
+invoked).
+.Pp
+Warning: Changing the value of the shell parameter
+.Ev OPTIND
+to a value other than 1, or parsing different sets of arguments without
+resetting
+.Ev OPTIND ,
+may lead to unexpected results.
+.Pp
+.It Xo
+.Ic hash
+.Op Fl r
+.Op Ar name ...
+.Xc
+Without arguments, any hashed executable command pathnames are listed.
+The
+.Fl r
+option causes all hashed commands to be removed from the hash table.
+Each
+.Ar name
+is searched as if it were a command name and added to the hash table if it is
+an executable command.
+.Pp
+.It Xo
+.Ic jobs
+.Op Fl lnp
+.Op Ar job ...
+.Xc
+Display information about the specified job(s); if no jobs are specified, all
+jobs are displayed.
+The
+.Fl n
+option causes information to be displayed only for jobs that have changed
+state since the last notification.
+If the
+.Fl l
+option is used, the process ID of each process in a job is also listed.
+The
+.Fl p
+option causes only the process group of each job to be printed.
+See
+.Sx Job control
+below for the format of
+.Ar job
+and the displayed job.
+.Pp
+.It Xo
+.Ic kill
+.Oo Fl s Ar signame \*(Ba
+.No - Ns Ar signum \*(Ba
+.No - Ns Ar signame Oc
+.No { Ar job \*(Ba pid \*(Ba pgrp No }
+.Ar ...
+.Xc
+Send the specified signal to the specified jobs, process IDs, or process
+groups.
+If no signal is specified, the
+.Dv TERM
+signal is sent.
+If a job is specified, the signal is sent to the job's process group.
+See
+.Sx Job control
+below for the format of
+.Ar job .
+.Pp
+.It Xo
+.Ic kill
+.Fl l
+.Op Ar exit-status ...
+.Xc
+Print the signal name corresponding to
+.Ar exit-status .
+If no arguments are specified, a list of all the signals, their numbers, and
+a short description of them are printed.
+.Pp
+.It Xo
+.Ic print
+.Oo
+.Fl nrsu Ns Oo Ar n Oc \*(Ba
+.Fl R Op Fl en
+.Oc
+.Op Ar argument ...
+.Xc
+.Ic print
+prints its arguments on the standard output, separated by spaces and
+terminated with a newline.
+The
+.Fl n
+option suppresses the newline.
+By default, certain C escapes are translated.
+These include
+.Ql \eb ,
+.Ql \ef ,
+.Ql \en ,
+.Ql \er ,
+.Ql \et ,
+.Ql \ev ,
+and
+.Ql \e0###
+.Po
+.Ql #
+is an octal digit, of which there may be 0 to 3
+.Pc .
+.Ql \ec
+is equivalent to using the
+.Fl n
+option.
+.Ql \e
+expansion may be inhibited with the
+.Fl r
+option.
+The
+.Fl s
+option prints to the history file instead of standard output; and the
+.Fl u
+option prints to file descriptor
+.Ar n
+.Po
+.Ar n
+defaults to 1 if omitted
+.Pc .
+.Pp
+The
+.Fl R
+option is used to emulate, to some degree, the
+.Bx
+.Xr echo 1
+command, which does not process
+.Ql \e
+sequences unless the
+.Fl e
+option is given.
+As above, the
+.Fl n
+option suppresses the trailing newline.
+.Pp
+.It Ic pwd Op Fl LP
+Print the present working directory.
+If the
+.Fl L
+option is used or if the
+.Ic physical
+option isn't set (see the
+.Ic set
+command below), the logical path is printed (i.e. the path used to
+.Ic cd
+to the current directory).
+If the
+.Fl P
+option (physical path) is used or if the
+.Ic physical
+option is set, the path determined from the filesystem (by following
+.Sq ..
+directories to the root directory) is printed.
+.Pp
+.It Xo
+.Ic read
+.Op Fl rsu Ns Op Ar n
+.Op Ar parameter ...
+.Xc
+Reads a line of input from the standard input, separates the line into fields
+using the
+.Ev IFS
+parameter (see
+.Sx Substitution
+above), and assigns each field to the specified parameters.
+If there are more parameters than fields, the extra parameters are set to
+.Dv NULL ,
+or alternatively, if there are more fields than parameters, the last parameter
+is assigned the remaining fields (inclusive of any separating spaces).
+If no parameters are specified, the
+.Ev REPLY
+parameter is used.
+If the input line ends in a backslash and the
+.Fl r
+option was not used, the backslash and the newline are stripped and more input
+is read.
+If no input is read,
+.Ic read
+exits with a non-zero status.
+.Pp
+The first parameter may have a question mark and a string appended to it, in
+which case the string is used as a prompt (printed to standard error before
+any input is read) if the input is a
+.Xr tty 4
+(e.g.\&
+.Ic read nfoo?'number of foos: ' ) .
+.Pp
+The
+.Fl u Ns Ar n
+option causes input to be read from file descriptor
+.Ar n
+.Pf ( Ar n
+defaults to 0 if omitted).
+If the
+.Fl s
+option is used, input is saved to the history file.
+.Pp
+.It Xo
+.Ic readonly
+.Op Fl p
+.Oo Ar parameter
+.Op Ns = Ns Ar value
+.Ar ... Oc
+.Xc
+Sets the read-only attribute of the named parameters.
+If values are given,
+parameters are set to them before setting the attribute.
+Once a parameter is
+made read-only, it cannot be unset and its value cannot be changed.
+.Pp
+If no parameters are specified, the names of all parameters with the read-only
+attribute are printed one per line, unless the
+.Fl p
+option is used, in which case
+.Ic readonly
+commands defining all read-only parameters, including their values, are
+printed.
+.Pp
+.It Ic return Op Ar status
+Returns from a function or
+.Ic .\&
+script, with exit status
+.Ar status .
+If no
+.Ar status
+is given, the exit status of the last executed command is used.
+If used outside of a function or
+.Ic .\&
+script, it has the same effect as
+.Ic exit .
+Note that
+.Nm pdksh
+treats both profile and
+.Ev ENV
+files as
+.Ic .\&
+scripts, while the original Korn shell only treats profiles as
+.Ic .\&
+scripts.
+.Pp
+.It Xo
+.Ic set Op Ic +-abCefhkmnpsuvXx
+.Op Ic +-o Ar option
+.Op Ic +-A Ar name
+.Op Fl -
+.Op Ar arg ...
+.Xc
+The
+.Ic set
+command can be used to set
+.Pq Ic -
+or clear
+.Pq Ic +
+shell options, set the positional parameters, or set an array parameter.
+Options can be changed using the
+.Cm +-o Ar option
+syntax, where
+.Ar option
+is the long name of an option, or using the
+.Cm +- Ns Ar letter
+syntax, where
+.Ar letter
+is the option's single letter name (not all options have a single letter name).
+The following table lists both option letters (if they exist) and long names
+along with a description of what the option does:
+.Bl -tag -width 15n
+.It Fl A Ar name
+Sets the elements of the array parameter
+.Ar name
+to
+.Ar arg ...
+If
+.Fl A
+is used, the array is reset (i.e. emptied) first; if
+.Ic +A
+is used, the first N elements are set (where N is the number of arguments);
+the rest are left untouched.
+.It Fl a \*(Ba Ic allexport
+All new parameters are created with the export attribute.
+.It Fl b \*(Ba Ic notify
+Print job notification messages asynchronously, instead of just before the
+prompt.
+Only used if job control is enabled
+.Pq Fl m .
+.It Fl C \*(Ba Ic noclobber
+Prevent \*(Gt redirection from overwriting existing files.
+Instead, \*(Gt\*(Ba must be used to force an overwrite.
+.It Fl e \*(Ba Ic errexit
+Exit (after executing the
+.Dv ERR
+trap) as soon as an error occurs or a command fails (i.e. exits with a
+non-zero status).
+This does not apply to commands whose exit status is
+explicitly tested by a shell construct such as
+.Ic if ,
+.Ic until ,
+.Ic while ,
+or
+.Ic !\&
+statements.
+For
+.Ic &&
+or
+.Ic || ,
+only the status of the last command is tested.
+.It Fl f \*(Ba Ic noglob
+Do not expand file name patterns.
+.It Fl h \*(Ba Ic trackall
+Create tracked aliases for all executed commands (see
+.Sx Aliases
+above).
+Enabled by default for non-interactive shells.
+.It Fl k \*(Ba Ic keyword
+Parameter assignments are recognized anywhere in a command.
+.It Fl m \*(Ba Ic monitor
+Enable job control (default for interactive shells).
+.It Fl n \*(Ba Ic noexec
+Do not execute any commands.
+Useful for checking the syntax of scripts
+(ignored if interactive).
+.It Fl p \*(Ba Ic privileged
+The shell is a privileged shell.
+It is set automatically if, when the shell starts,
+the real UID or GID does not match
+the effective UID (EUID) or GID (EGID), respectively.
+See above for a description of what this means.
+.It Fl s \*(Ba Ic stdin
+If used when the shell is invoked, commands are read from standard input.
+Set automatically if the shell is invoked with no arguments.
+.Pp
+When
+.Fl s
+is used with the
+.Ic set
+command it causes the specified arguments to be sorted before assigning them to
+the positional parameters (or to array
+.Ar name ,
+if
+.Fl A
+is used).
+.It Fl u \*(Ba Ic nounset
+Referencing of an unset parameter is treated as an error, unless one of the
+.Ql - ,
+.Ql + ,
+or
+.Ql =
+modifiers is used.
+.It Fl v \*(Ba Ic verbose
+Write shell input to standard error as it is read.
+.It Fl X \*(Ba Ic markdirs
+Mark directories with a trailing
+.Ql /
+during file name generation.
+.It Fl x \*(Ba Ic xtrace
+Print commands and parameter assignments when they are executed, preceded by
+the value of
+.Ev PS4 .
+.It Ic bgnice
+Background jobs are run with lower priority.
+.It Ic ignoreeof
+The shell will not exit when end-of-file is read;
+.Ic exit
+must be used.
+.It Ic interactive
+The shell is an interactive shell.
+This option can only be used when the shell is invoked.
+See above for a description of what this means.
+.It Ic login
+The shell is a login shell.
+This option can only be used when the shell is invoked.
+See above for a description of what this means.
+.It Ic nohup
+Do not kill running jobs with a
+.Dv SIGHUP
+signal when a login shell exits.
+Currently set by default, but this will
+change in the future to be compatible with the original Korn shell (which
+doesn't have this option, but does send the
+.Dv SIGHUP
+signal).
+.It Ic nolog
+No effect.
+In the original Korn shell, this prevents function definitions from
+being stored in the history file.
+.It Ic physical
+Causes the
+.Ic cd
+and
+.Ic pwd
+commands to use
+.Dq physical
+(i.e. the filesystem's)
+.Sq ..
+directories instead of
+.Dq logical
+directories (i.e. the shell handles
+.Sq .. ,
+which allows the user to be oblivious of symbolic links to directories).
+Clear by default.
+Note that setting this option does not affect the current value of the
+.Ev PWD
+parameter; only the
+.Ic cd
+command changes
+.Ev PWD .
+See the
+.Ic cd
+and
+.Ic pwd
+commands above for more details.
+.It Ic posix
+Enable POSIX mode.
+See
+.Sx POSIX mode
+above.
+.It Ic restricted
+The shell is a restricted shell.
+This option can only be used when the shell is invoked.
+See above for a description of what this means.
+.It Ic vi
+Enable
+.Xr vi 1 Ns -like
+command-line editing (interactive shells only).
+.It Ic vi-esccomplete
+In vi command-line editing, do command and file name completion when escape
+(^[) is entered in command mode.
+.It Ic vi-show8
+Prefix characters with the eighth bit set with
+.Sq M- .
+If this option is not set, characters in the range 128\-160 are printed as is,
+which may cause problems.
+.It Ic vi-tabcomplete
+In vi command-line editing, do command and file name completion when tab (^I)
+is entered in insert mode.
+.It Ic viraw
+No effect.
+In the original Korn shell, unless
+.Ic viraw
+was set, the vi command-line mode would let the
+.Xr tty 4
+driver do the work until ESC (^[) was entered.
+.Nm pdksh
+is always in viraw mode.
+.El
+.Pp
+These options can also be used upon invocation of the shell.
+The current set of
+options (with single letter names) can be found in the parameter
+.Sq $- .
+.Ic set Fl o
+with no option name will list all the options and whether each is on or off;
+.Ic set +o
+will print the long names of all options that are currently on.
+.Pp
+Remaining arguments, if any, are positional parameters and are assigned, in
+order, to the positional parameters (i.e. $1, $2, etc.).
+If options end with
+.Ql --
+and there are no remaining arguments, all positional parameters are cleared.
+If no options or arguments are given, the values of all names are printed.
+For unknown historical reasons, a lone
+.Ql -
+option is treated specially \- it clears both the
+.Fl x
+and
+.Fl v
+options.
+.Pp
+.It Ic shift Op Ar number
+The positional parameters
+.Ar number Ns +1 ,
+.Ar number Ns +2 ,
+etc. are renamed to
+.Sq 1 ,
+.Sq 2 ,
+etc.
+.Ar number
+defaults to 1.
+.Pp
+.It Ic test Ar expression
+.It Ic \&[ Ar expression Ic \&]
+.Ic test
+evaluates the
+.Ar expression
+and returns zero status if true, 1 if false, or greater than 1 if there
+was an error.
+It is normally used as the condition command of
+.Ic if
+and
+.Ic while
+statements.
+Symbolic links are followed for all
+.Ar file
+expressions except
+.Fl h
+and
+.Fl L .
+.Pp
+The following basic expressions are available:
+.Bl -tag -width 17n
+.It Fl a Ar file
+.Ar file
+exists.
+.It Fl b Ar file
+.Ar file
+is a block special device.
+.It Fl c Ar file
+.Ar file
+is a character special device.
+.It Fl d Ar file
+.Ar file
+is a directory.
+.It Fl e Ar file
+.Ar file
+exists.
+.It Fl f Ar file
+.Ar file
+is a regular file.
+.It Fl G Ar file
+.Ar file Ns 's
+group is the shell's effective group ID.
+.It Fl g Ar file
+.Ar file Ns 's
+mode has the setgid bit set.
+.It Fl h Ar file
+.Ar file
+is a symbolic link.
+.It Fl k Ar file
+.Ar file Ns 's
+mode has the
+.Xr sticky 8
+bit set.
+.It Fl L Ar file
+.Ar file
+is a symbolic link.
+.It Fl O Ar file
+.Ar file Ns 's
+owner is the shell's effective user ID.
+.It Fl o Ar option
+Shell
+.Ar option
+is set (see the
+.Ic set
+command above for a list of options).
+As a non-standard extension, if the option starts with a
+.Ql \&! ,
+the test is negated; the test always fails if
+.Ar option
+doesn't exist (so [ -o foo -o -o !foo ] returns true if and only if option
+.Ar foo
+exists).
+.It Fl p Ar file
+.Ar file
+is a named pipe.
+.It Fl r Ar file
+.Ar file
+exists and is readable.
+.It Fl S Ar file
+.Ar file
+is a
+.Xr unix 4 Ns -domain
+socket.
+.It Fl s Ar file
+.Ar file
+is not empty.
+.It Fl t Op Ar fd
+File descriptor
+.Ar fd
+is a
+.Xr tty 4
+device.
+If the
+.Ic posix
+option is not set,
+.Ar fd
+may be left out, in which case it is taken to be 1 (the behaviour differs due
+to the special POSIX rules described above).
+.It Fl u Ar file
+.Ar file Ns 's
+mode has the setuid bit set.
+.It Fl w Ar file
+.Ar file
+exists and is writable.
+.It Fl x Ar file
+.Ar file
+exists and is executable.
+.It Ar file1 Fl nt Ar file2
+.Ar file1
+is newer than
+.Ar file2 .
+.It Ar file1 Fl ot Ar file2
+.Ar file1
+is older than
+.Ar file2 .
+.It Ar file1 Fl ef Ar file2
+.Ar file1
+is the same file as
+.Ar file2 .
+.It Ar string
+.Ar string
+has non-zero length.
+.It Fl n Ar string
+.Ar string
+is not empty.
+.It Fl z Ar string
+.Ar string
+is empty.
+.It Ar string No = Ar string
+Strings are equal.
+.It Ar string No != Ar string
+Strings are not equal.
+.It Ar number Fl eq Ar number
+Numbers compare equal.
+.It Ar number Fl ne Ar number
+Numbers compare not equal.
+.It Ar number Fl ge Ar number
+Numbers compare greater than or equal.
+.It Ar number Fl gt Ar number
+Numbers compare greater than.
+.It Ar number Fl le Ar number
+Numbers compare less than or equal.
+.It Ar number Fl \< Ar number
+Numbers compare less than.
+.El
+.Pp
+The above basic expressions, in which unary operators have precedence over
+binary operators, may be combined with the following operators (listed in
+increasing order of precedence):
+.Bd -literal -offset indent
+expr -o expr Logical OR.
+expr -a expr Logical AND.
+! expr Logical NOT.
+( expr ) Grouping.
+.Ed
+.Pp
+On operating systems not supporting
+.Pa /dev/fd/ Ns Ar n
+devices (where
+.Ar n
+is a file descriptor number), the
+.Ic test
+command will attempt to fake it for all tests that operate on files (except the
+.Fl e
+test).
+For example,
+[ -w /dev/fd/2 ] tests if file descriptor 2 is writable.
+.Pp
+Note that some special rules are applied (courtesy of POSIX)
+if the number of
+arguments to
+.Ic test
+or
+.Ic \&[ ... \&]
+is less than five: if leading
+.Ql \&!
+arguments can be stripped such that only one argument remains then a string
+length test is performed (again, even if the argument is a unary operator); if
+leading
+.Ql \&!
+arguments can be stripped such that three arguments remain and the second
+argument is a binary operator, then the binary operation is performed (even
+if the first argument is a unary operator, including an unstripped
+.Ql \&! ) .
+.Pp
+.Sy Note :
+A common mistake is to use
+.Dq if \&[ $foo = bar \&] ,
+which fails if parameter
+.Dq foo
+is
+.Dv NULL
+or unset, if it has embedded spaces (i.e.\&
+.Ev IFS
+characters), or if it is a unary operator like
+.Sq \&!
+or
+.Sq Fl n .
+Use tests like
+.Dq if \&[ \&"X$foo\&" = Xbar \&]
+instead.
+.Pp
+.It Ic times
+Print the accumulated user and system times used both by the shell
+and by processes that the shell started which have exited.
+The format of the output is:
+.Bd -literal -offset indent
+0m0.00s 0m0.00s
+0m0.00s 0m0.00s
+.Ed
+.Pp
+.It Ic trap Op Ar handler signal ...
+Sets a trap handler that is to be executed when any of the specified signals are
+received.
+.Ar handler
+is either a
+.Dv NULL
+string, indicating the signals are to be ignored, a minus sign
+.Pq Sq - ,
+indicating that the default action is to be taken for the signals (see
+.Xr signal 3 ) ,
+or a string containing shell commands to be evaluated and executed at the first
+opportunity (i.e. when the current command completes, or before printing the
+next
+.Ev PS1
+prompt) after receipt of one of the signals.
+.Ar signal
+is the name of a signal (e.g.\&
+.Dv PIPE
+or
+.Dv ALRM )
+or the number of the signal (see the
+.Ic kill -l
+command above).
+.Pp
+There are two special signals:
+.Dv EXIT
+(also known as 0), which is executed when the shell is about to exit, and
+.Dv ERR ,
+which is executed after an error occurs (an error is something that would cause
+the shell to exit if the
+.Fl e
+or
+.Ic errexit
+option were set \- see the
+.Ic set
+command above).
+.Dv EXIT
+handlers are executed in the environment of the last executed command.
+Note
+that for non-interactive shells, the trap handler cannot be changed for signals
+that were ignored when the shell started.
+.Pp
+With no arguments,
+.Ic trap
+lists, as a series of
+.Ic trap
+commands, the current state of the traps that have been set since the shell
+started.
+Note that the output of
+.Ic trap
+cannot be usefully piped to another process (an artifact of the fact that
+traps are cleared when subprocesses are created).
+.Pp
+The original Korn shell's
+.Dv DEBUG
+trap and the handling of
+.Dv ERR
+and
+.Dv EXIT
+traps in functions are not yet implemented.
+.Pp
+.It Ic true
+A command that exits with a zero value.
+.Pp
+.It Xo
+.Ic typeset
+.Oo
+.Op Ic +-lprtUux
+.Op Fl L Ns Op Ar n
+.Op Fl R Ns Op Ar n
+.Op Fl Z Ns Op Ar n
+.Op Fl i Ns Op Ar n
+.No \*(Ba Fl f Op Fl tux
+.Oc
+.Oo
+.Ar name
+.Op Ns = Ns Ar value
+.Ar ...
+.Oc
+.Xc
+Display or set parameter attributes.
+With no
+.Ar name
+arguments, parameter attributes are displayed; if no options are used, the
+current attributes of all parameters are printed as
+.Ic typeset
+commands; if an option is given (or
+.Ql -
+with no option letter), all parameters and their values with the specified
+attributes are printed; if options are introduced with
+.Ql + ,
+parameter values are not printed.
+.Pp
+If
+.Ar name
+arguments are given, the attributes of the named parameters are set
+.Pq Ic -
+or cleared
+.Pq Ic + .
+Values for parameters may optionally be specified.
+If
+.Ic typeset
+is used inside a function, any newly created parameters are local to the
+function.
+.Pp
+When
+.Fl f
+is used,
+.Ic typeset
+operates on the attributes of functions.
+As with parameters, if no
+.Ar name
+arguments are given,
+functions are listed with their values (i.e. definitions) unless
+options are introduced with
+.Ql + ,
+in which case only the function names are reported.
+.Bl -tag -width Ds
+.It Fl f
+Function mode.
+Display or set functions and their attributes, instead of parameters.
+.It Fl i Ns Op Ar n
+Integer attribute.
+.Ar n
+specifies the base to use when displaying the integer (if not specified, the
+base given in the first assignment is used).
+Parameters with this attribute may
+be assigned values containing arithmetic expressions.
+.It Fl L Ns Op Ar n
+Left justify attribute.
+.Ar n
+specifies the field width.
+If
+.Ar n
+is not specified, the current width of a parameter (or the width of its first
+assigned value) is used.
+Leading whitespace (and zeros, if used with the
+.Fl Z
+option) is stripped.
+If necessary, values are either truncated or space padded
+to fit the field width.
+.It Fl l
+Lower case attribute.
+All upper case characters in values are converted to lower case.
+(In the original Korn shell, this parameter meant
+.Dq long integer
+when used with the
+.Fl i
+option.)
+.It Fl p
+Print complete
+.Ic typeset
+commands that can be used to re-create the attributes (but not the values) of
+parameters.
+This is the default action (option exists for ksh93 compatibility).
+.It Fl R Ns Op Ar n
+Right justify attribute.
+.Ar n
+specifies the field width.
+If
+.Ar n
+is not specified, the current width of a parameter (or the width of its first
+assigned value) is used.
+Trailing whitespace is stripped.
+If necessary, values are either stripped of leading characters or space
+padded to make them fit the field width.
+.It Fl r
+Read-only attribute.
+Parameters with this attribute may not be assigned to or unset.
+Once this attribute is set, it cannot be turned off.
+.It Fl t
+Tag attribute.
+Has no meaning to the shell; provided for application use.
+.Pp
+For functions,
+.Fl t
+is the trace attribute.
+When functions with the trace attribute are executed, the
+.Ic xtrace
+.Pq Fl x
+shell option is temporarily turned on.
+.It Fl U
+Unsigned integer attribute.
+Integers are printed as unsigned values (only
+useful when combined with the
+.Fl i
+option).
+This option is not in the original Korn shell.
+.It Fl u
+Upper case attribute.
+All lower case characters in values are converted to upper case.
+(In the original Korn shell, this parameter meant
+.Dq unsigned integer
+when used with the
+.Fl i
+option, which meant upper case letters would never be used for bases greater
+than 10.
+See the
+.Fl U
+option.)
+.Pp
+For functions,
+.Fl u
+is the undefined attribute.
+See
+.Sx Functions
+above for the implications of this.
+.It Fl x
+Export attribute.
+Parameters (or functions) are placed in the environment of
+any executed commands.
+Exported functions are not yet implemented.
+.It Fl Z Ns Op Ar n
+Zero fill attribute.
+If not combined with
+.Fl L ,
+this is the same as
+.Fl R ,
+except zero padding is used instead of space padding.
+.El
+.Pp
+.It Xo
+.Ic ulimit
+.Op Fl acdfHlmnpSst Op Ar value
+.Ar ...
+.Xc
+Display or set process limits.
+If no options are used, the file size limit
+.Pq Fl f
+is assumed.
+.Ar value ,
+if specified, may be either an arithmetic expression starting with a
+number or the word
+.Dq unlimited .
+The limits affect the shell and any processes created by the shell after a
+limit is imposed; limits may not be increased once they are set.
+.Bl -tag -width 5n
+.It Fl a
+Display all limits; unless
+.Fl H
+is used, soft limits are displayed.
+.It Fl c Ar n
+Impose a size limit of
+.Ar n
+blocks on the size of core dumps.
+.It Fl d Ar n
+Impose a size limit of
+.Ar n
+kilobytes on the size of the data area.
+.It Fl f Ar n
+Impose a size limit of
+.Ar n
+blocks on files written by the shell and its child processes (files of any
+size may be read).
+.It Fl H
+Set the hard limit only (the default is to set both hard and soft limits).
+.It Fl l Ar n
+Impose a limit of
+.Ar n
+kilobytes on the amount of locked (wired) physical memory.
+.It Fl m Ar n
+Impose a limit of
+.Ar n
+kilobytes on the amount of physical memory used.
+.It Fl n Ar n
+Impose a limit of
+.Ar n
+file descriptors that can be open at once.
+.It Fl p Ar n
+Impose a limit of
+.Ar n
+processes that can be run by the user at any one time.
+.It Fl S
+Set the soft limit only (the default is to set both hard and soft limits).
+.It Fl s Ar n
+Impose a size limit of
+.Ar n
+kilobytes on the size of the stack area.
+.It Fl t Ar n
+Impose a time limit of
+.Ar n
+CPU seconds spent in user mode to be used by each process.
+.\".It Fl v Ar n
+.\"Impose a limit of
+.\".Ar n
+.\"kilobytes on the amount of virtual memory used.
+.El
+.Pp
+As far as
+.Ic ulimit
+is concerned, a block is 512 bytes.
+.Pp
+.It Xo
+.Ic umask
+.Op Fl S
+.Op Ar mask
+.Xc
+Display or set the file permission creation mask, or umask (see
+.Xr umask 2 ) .
+If the
+.Fl S
+option is used, the mask displayed or set is symbolic; otherwise, it is an
+octal number.
+.Pp
+Symbolic masks are like those used by
+.Xr chmod 1 .
+When used, they describe what permissions may be made available (as opposed to
+octal masks in which a set bit means the corresponding bit is to be cleared).
+For example,
+.Dq ug=rwx,o=
+sets the mask so files will not be readable, writable, or executable by
+.Dq others ,
+and is equivalent (on most systems) to the octal mask
+.Dq 007 .
+.Pp
+.It Xo
+.Ic unalias
+.Op Fl adt
+.Op Ar name ...
+.Xc
+The aliases for the given names are removed.
+If the
+.Fl a
+option is used, all aliases are removed.
+If the
+.Fl t
+or
+.Fl d
+options are used, the indicated operations are carried out on tracked or
+directory aliases, respectively.
+.Pp
+.It Xo
+.Ic unset
+.Op Fl fv
+.Ar parameter ...
+.Xc
+Unset the named parameters
+.Po
+.Fl v ,
+the default
+.Pc
+or functions
+.Pq Fl f .
+The exit status is non-zero if any of the parameters have the read-only
+attribute set, zero otherwise.
+.Pp
+.It Ic wait Op Ar job ...
+Wait for the specified job(s) to finish.
+The exit status of
+.Ic wait
+is that of the last specified job; if the last job is killed by a signal, the
+exit status is 128 + the number of the signal (see
+.Ic kill -l Ar exit-status
+above); if the last specified job can't be found (because it never existed, or
+had already finished), the exit status of
+.Ic wait
+is 127.
+See
+.Sx Job control
+below for the format of
+.Ar job .
+.Ic wait
+will return if a signal for which a trap has been set is received, or if a
+.Dv SIGHUP ,
+.Dv SIGINT ,
+or
+.Dv SIGQUIT
+signal is received.
+.Pp
+If no jobs are specified,
+.Ic wait
+waits for all currently running jobs (if any) to finish and exits with a zero
+status.
+If job monitoring is enabled, the completion status of jobs is printed
+(this is not the case when jobs are explicitly specified).
+.Pp
+.It Xo
+.Ic whence
+.Op Fl pv
+.Op Ar name ...
+.Xc
+For each
+.Ar name ,
+the type of command is listed (reserved word, built-in, alias,
+function, tracked alias, or executable).
+If the
+.Fl p
+option is used, a path search is performed even if
+.Ar name
+is a reserved word, alias, etc.
+Without the
+.Fl v
+option,
+.Ic whence
+is similar to
+.Ic command Fl v
+except that
+.Ic whence
+will find reserved words and won't print aliases as alias commands.
+With the
+.Fl v
+option,
+.Ic whence
+is the same as
+.Ic command Fl V .
+Note that for
+.Ic whence ,
+the
+.Fl p
+option does not affect the search path used, as it does for
+.Ic command .
+If the type of one or more of the names could not be determined, the exit
+status is non-zero.
+.El
+.Ss Job control
+Job control refers to the shell's ability to monitor and control jobs, which
+are processes or groups of processes created for commands or pipelines.
+At a minimum, the shell keeps track of the status of the background (i.e.\&
+asynchronous) jobs that currently exist; this information can be displayed
+using the
+.Ic jobs
+commands.
+If job control is fully enabled (using
+.Ic set -m
+or
+.Ic set -o monitor ) ,
+as it is for interactive shells, the processes of a job are placed in their
+own process group.
+Foreground jobs can be stopped by typing the suspend
+character from the terminal (normally ^Z), jobs can be restarted in either the
+foreground or background using the
+.Ic fg
+and
+.Ic bg
+commands, and the state of the terminal is saved or restored when a foreground
+job is stopped or restarted, respectively.
+.Pp
+Note that only commands that create processes (e.g. asynchronous commands,
+subshell commands, and non-built-in, non-function commands) can be stopped;
+commands like
+.Ic read
+cannot be.
+.Pp
+When a job is created, it is assigned a job number.
+For interactive shells, this number is printed inside
+.Dq [..] ,
+followed by the process IDs of the processes in the job when an asynchronous
+command is run.
+A job may be referred to in the
+.Ic bg ,
+.Ic fg ,
+.Ic jobs ,
+.Ic kill ,
+and
+.Ic wait
+commands either by the process ID of the last process in the command pipeline
+(as stored in the
+.Ic $!\&
+parameter) or by prefixing the job number with a percent
+sign
+.Pq Sq % .
+Other percent sequences can also be used to refer to jobs:
+.Bl -tag -width "%+ | %% | %XX"
+.It %+ \*(Ba %% \*(Ba %
+The most recently stopped job, or, if there are no stopped jobs, the oldest
+running job.
+.It %-
+The job that would be the
+.Ic %+
+job if the latter did not exist.
+.It % Ns Ar n
+The job with job number
+.Ar n .
+.It %? Ns Ar string
+The job with its command containing the string
+.Ar string
+(an error occurs if multiple jobs are matched).
+.It % Ns Ar string
+The job with its command starting with the string
+.Ar string
+(an error occurs if multiple jobs are matched).
+.El
+.Pp
+When a job changes state (e.g. a background job finishes or foreground job is
+stopped), the shell prints the following status information:
+.Pp
+.D1 [ Ns Ar number ] Ar flag status command
+.Pp
+where...
+.Bl -tag -width "command"
+.It Ar number
+is the job number of the job;
+.It Ar flag
+is the
+.Ql +
+or
+.Ql -
+character if the job is the
+.Ic %+
+or
+.Ic %-
+job, respectively, or space if it is neither;
+.It Ar status
+indicates the current state of the job and can be:
+.Bl -tag -width "RunningXX"
+.It Done Op Ar number
+The job exited.
+.Ar number
+is the exit status of the job, which is omitted if the status is zero.
+.It Running
+The job has neither stopped nor exited (note that running does not necessarily
+mean consuming CPU time \-
+the process could be blocked waiting for some event).
+.It Stopped Op Ar signal
+The job was stopped by the indicated
+.Ar signal
+(if no signal is given, the job was stopped by
+.Dv SIGTSTP ) .
+.It Ar signal-description Op Dq core dumped
+The job was killed by a signal (e.g. memory fault, hangup); use
+.Ic kill -l
+for a list of signal descriptions.
+The
+.Dq core dumped
+message indicates the process created a core file.
+.El
+.It Ar command
+is the command that created the process.
+If there are multiple processes in
+the job, each process will have a line showing its
+.Ar command
+and possibly its
+.Ar status ,
+if it is different from the status of the previous process.
+.El
+.Pp
+When an attempt is made to exit the shell while there are jobs in the stopped
+state, the shell warns the user that there are stopped jobs and does not exit.
+If another attempt is immediately made to exit the shell, the stopped jobs are
+sent a
+.Dv SIGHUP
+signal and the shell exits.
+Similarly, if the
+.Ic nohup
+option is not set and there are running jobs when an attempt is made to exit
+a login shell, the shell warns the user and does not exit.
+If another attempt
+is immediately made to exit the shell, the running jobs are sent a
+.Dv SIGHUP
+signal and the shell exits.
+.Sh FILES
+.Bl -tag -width "/etc/suid_profileXX" -compact
+.It Pa ~/.profile
+User's login profile.
+.It Pa /etc/profile
+System login profile.
+.It Pa /etc/suid_profile
+Privileged shell profile.
+.It Pa /etc/shells
+Shell database.
+.El
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr ed 1 ,
+.Xr ksh 1 ,
+.Xr mg 1 ,
+.Xr stty 1 ,
+.Xr vi 1 ,
+.Xr shells 5 ,
+.Xr environ 7 ,
+.Xr script 7
+.Rs
+.%A Morris Bolsky
+.%A David Korn
+.%B The KornShell Command and Programming Language, 2nd Edition
+.%D 1995
+.%I Prentice Hall
+.%O ISBN 0131827006
+.Re
+.Rs
+.%A Stephen G. Kochan
+.%A Patrick H. Wood
+.%B UNIX Shell Programming, 3rd Edition
+.%D 2003
+.%I Sams
+.%O ISBN 0672324903
+.Re
+.Rs
+.%A IEEE Inc.
+.%D 1993
+.%O ISBN 1-55937-266-9
+.%T IEEE Standard for Information Technology \- Portable Operating \
+ System Interface (POSIX) \- Part 2: Shell and Utilities
+.Re
+.Sh NOTES
+.Nm
+is implemented as a run-time option of
+.Nm pdksh ,
+with only those
+.Nm
+features whose syntax or semantics are incompatible with a traditional Bourne
+shell disabled.
+Since this leaves some
+.Nm
+extensions exposed, caution should be used where backwards compatibility with
+traditional Bourne or POSIX compliant shells is an issue.
+.Sh AUTHORS
+.An -nosplit
+This shell is based on the public domain 7th edition Bourne shell clone by
+.An Charles Forsyth
+and parts of the BRL shell by
+.An Doug A. Gwyn ,
+.An Doug Kingston ,
+.An Ron Natalie ,
+.An Arnold Robbins ,
+.An Lou Salkind ,
+and others.
+The first release of
+.Nm pdksh
+was created by
+.An Eric Gisin ,
+and it was subsequently maintained by
+.An John R. MacMillan Aq Mt change!john@sq.sq.com ,
+.An Simon J. Gerraty Aq Mt sjg@zen.void.oz.au ,
+and
+.An Michael Rendell Aq Mt michael@cs.mun.ca .
+The
+.Pa CONTRIBUTORS
+file in the source distribution contains a more complete list of people and
+their part in the shell's development.
+.\" .Sh BUGS
+.\" Any bugs in
+.\" .Nm pdksh
+.\" should be reported to pdksh@cs.mun.ca.
+.\" Please include the version of
+.\" .Nm pdksh
+.\" .Po
+.\" .Ic echo $KSH_VERSION
+.\" shows it
+.\" .Pc ,
+.\" the machine, operating system, and compiler you are using and a description of
+.\" how to repeat the bug (a small shell script that demonstrates the bug is best).
+.\" The following, if relevant (if you are not sure, include them), can also be
+.\" helpful: options you are using (both
+.\" .Pa options.h
+.\" and
+.\" .Ic set Fl o Ic options )
+.\" and a copy of your
+.\" .Pa config.h
+.\" (the file generated by the
+.\" .Pa configure
+.\" script).
+.\" New versions of
+.\" .Nm pdksh
+.\" can be obtained from ftp://ftp.cs.mun.ca/pub/pdksh.
+.\" .Pp
+.\" BTW, the most frequently reported bug is:
+.\" .Bd -literal -offset indent
+.\" $ echo hi | read a; echo $a # Does not print hi
+.\" .Ed
+.\" .Pp
+.\" I'm aware of this and there is no need to report it.
diff --git a/sh.h b/sh.h
@@ -0,0 +1,420 @@
+/* $OpenBSD: sh.h,v 1.33 2013/12/18 13:53:12 millert Exp $ */
+
+/*
+ * Public Domain Bourne/Korn shell
+ */
+
+/* $From: sh.h,v 1.2 1994/05/19 18:32:40 michael Exp michael $ */
+
+#include "config.h" /* system and option configuration info */
+
+/* Start of common headers */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <setjmp.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+
+#include <signal.h>
+
+#include <paths.h>
+
+/* Find a integer type that is at least 32 bits (or die) - SIZEOF_* defined
+ * by autoconf (assumes an 8 bit byte, but I'm not concerned).
+ * NOTE: INT32 may end up being more than 32 bits.
+ */
+# define INT32 int
+
+/* end of common headers */
+
+/* some useful #defines */
+#ifdef EXTERN
+# define I__(i) = i
+#else
+# define I__(i)
+# define EXTERN extern
+# define EXTERN_DEFINED
+#endif
+
+#define EXECSHELL _PATH_BSHELL
+#define EXECSHELL_STR "EXECSHELL"
+
+#define NELEM(a) (sizeof(a) / sizeof((a)[0]))
+#define sizeofN(type, n) (sizeof(type) * (n))
+#define BIT(i) (1<<(i)) /* define bit in flag */
+
+/* Table flag type - needs > 16 and < 32 bits */
+typedef INT32 Tflag;
+
+#define NUFILE 32 /* Number of user-accessible files */
+#define FDBASE 10 /* First file usable by Shell */
+
+/* Make MAGIC a char that might be printed to make bugs more obvious, but
+ * not a char that is used often. Also, can't use the high bit as it causes
+ * portability problems (calling strchr(x, 0x80|'x') is error prone).
+ */
+#define MAGIC (7) /* prefix for *?[!{,} during expand */
+#define ISMAGIC(c) ((unsigned char)(c) == MAGIC)
+#define NOT '!' /* might use ^ (ie, [!...] vs [^..]) */
+
+#define LINE 2048 /* input line size */
+#define PATH 1024 /* pathname size (todo: PATH_MAX/pathconf()) */
+
+EXTERN const char *kshname; /* $0 */
+EXTERN pid_t kshpid; /* $$, shell pid */
+EXTERN pid_t procpid; /* pid of executing process */
+EXTERN uid_t ksheuid; /* effective uid of shell */
+EXTERN int exstat; /* exit status */
+EXTERN int subst_exstat; /* exit status of last $(..)/`..` */
+EXTERN const char *safe_prompt; /* safe prompt if PS1 substitution fails */
+EXTERN char username[]; /* username for \u prompt expansion */
+
+/*
+ * Area-based allocation built on malloc/free
+ */
+typedef struct Area {
+ struct link *freelist; /* free list */
+} Area;
+
+EXTERN Area aperm; /* permanent object space */
+#define APERM &aperm
+#define ATEMP &e->area
+
+#ifdef KSH_DEBUG
+# define kshdebug_init() kshdebug_init_()
+# define kshdebug_printf(a) kshdebug_printf_ a
+# define kshdebug_dump(a) kshdebug_dump_ a
+#else /* KSH_DEBUG */
+# define kshdebug_init()
+# define kshdebug_printf(a)
+# define kshdebug_dump(a)
+#endif /* KSH_DEBUG */
+
+/*
+ * parsing & execution environment
+ */
+EXTERN struct env {
+ short type; /* environment type - see below */
+ short flags; /* EF_* */
+ Area area; /* temporary allocation area */
+ struct block *loc; /* local variables and functions */
+ short *savefd; /* original redirected fd's */
+ struct env *oenv; /* link to previous environment */
+ sigjmp_buf jbuf; /* long jump back to env creator */
+ struct temp *temps; /* temp files */
+} *e;
+
+/* struct env.type values */
+#define E_NONE 0 /* dummy environment */
+#define E_PARSE 1 /* parsing command # */
+#define E_FUNC 2 /* executing function # */
+#define E_INCL 3 /* including a file via . # */
+#define E_EXEC 4 /* executing command tree */
+#define E_LOOP 5 /* executing for/while # */
+#define E_ERRH 6 /* general error handler # */
+/* # indicates env has valid jbuf (see unwind()) */
+
+/* struct env.flag values */
+#define EF_FUNC_PARSE BIT(0) /* function being parsed */
+#define EF_BRKCONT_PASS BIT(1) /* set if E_LOOP must pass break/continue on */
+#define EF_FAKE_SIGDIE BIT(2) /* hack to get info from unwind to quitenv */
+
+/* Do breaks/continues stop at env type e? */
+#define STOP_BRKCONT(t) ((t) == E_NONE || (t) == E_PARSE \
+ || (t) == E_FUNC || (t) == E_INCL)
+/* Do returns stop at env type e? */
+#define STOP_RETURN(t) ((t) == E_FUNC || (t) == E_INCL)
+
+/* values for siglongjmp(e->jbuf, 0) */
+#define LRETURN 1 /* return statement */
+#define LEXIT 2 /* exit statement */
+#define LERROR 3 /* errorf() called */
+#define LLEAVE 4 /* untrappable exit/error */
+#define LINTR 5 /* ^C noticed */
+#define LBREAK 6 /* break statement */
+#define LCONTIN 7 /* continue statement */
+#define LSHELL 8 /* return to interactive shell() */
+#define LAEXPR 9 /* error in arithmetic expression */
+
+/* option processing */
+#define OF_CMDLINE 0x01 /* command line */
+#define OF_SET 0x02 /* set builtin */
+#define OF_SPECIAL 0x04 /* a special variable changing */
+#define OF_INTERNAL 0x08 /* set internally by shell */
+#define OF_ANY (OF_CMDLINE | OF_SET | OF_SPECIAL | OF_INTERNAL)
+
+struct option {
+ const char *name; /* long name of option */
+ char c; /* character flag (if any) */
+ short flags; /* OF_* */
+};
+extern const struct option options[];
+
+/*
+ * flags (the order of these enums MUST match the order in misc.c(options[]))
+ */
+enum sh_flag {
+ FEXPORT = 0, /* -a: export all */
+#ifdef BRACE_EXPAND
+ FBRACEEXPAND, /* enable {} globbing */
+#endif
+ FBGNICE, /* bgnice */
+ FCOMMAND, /* -c: (invocation) execute specified command */
+ FCSHHISTORY, /* csh-style history enabled */
+#ifdef EMACS
+ FEMACS, /* emacs command editing */
+ FEMACSUSEMETA, /* use 8th bit as meta */
+#endif
+ FERREXIT, /* -e: quit on error */
+#ifdef EMACS
+ FGMACS, /* gmacs command editing */
+#endif
+ FIGNOREEOF, /* eof does not exit */
+ FTALKING, /* -i: interactive */
+ FKEYWORD, /* -k: name=value anywhere */
+ FLOGIN, /* -l: a login shell */
+ FMARKDIRS, /* mark dirs with / in file name completion */
+ FMONITOR, /* -m: job control monitoring */
+ FNOCLOBBER, /* -C: don't overwrite existing files */
+ FNOEXEC, /* -n: don't execute any commands */
+ FNOGLOB, /* -f: don't do file globbing */
+ FNOHUP, /* -H: don't kill running jobs when login shell exits */
+ FNOLOG, /* don't save functions in history (ignored) */
+#ifdef JOBS
+ FNOTIFY, /* -b: asynchronous job completion notification */
+#endif
+ FNOUNSET, /* -u: using an unset var is an error */
+ FPHYSICAL, /* -o physical: don't do logical cd's/pwd's */
+ FPOSIX, /* -o posix: be posixly correct */
+ FPRIVILEGED, /* -p: use suid_profile */
+ FRESTRICTED, /* -r: restricted shell */
+ FSH, /* -o sh: favor sh behaviour */
+ FSTDIN, /* -s: (invocation) parse stdin */
+ FTRACKALL, /* -h: create tracked aliases for all commands */
+ FVERBOSE, /* -v: echo input */
+#ifdef VI
+ FVI, /* vi command editing */
+ FVIRAW, /* always read in raw mode (ignored) */
+ FVISHOW8, /* display chars with 8th bit set as is (versus M-) */
+ FVITABCOMPLETE, /* enable tab as file name completion char */
+ FVIESCCOMPLETE, /* enable ESC as file name completion in command mode */
+#endif
+ FXTRACE, /* -x: execution trace */
+ FTALKING_I, /* (internal): initial shell was interactive */
+ FNFLAGS /* (place holder: how many flags are there) */
+};
+
+#define Flag(f) (shell_flags[(int) (f)])
+
+EXTERN char shell_flags [FNFLAGS];
+
+EXTERN char null [] I__(""); /* null value for variable */
+EXTERN char space [] I__(" ");
+EXTERN char newline [] I__("\n");
+
+enum temp_type {
+ TT_HEREDOC_EXP, /* expanded heredoc */
+ TT_HIST_EDIT /* temp file used for history editing (fc -e) */
+};
+typedef enum temp_type Temp_type;
+/* temp/heredoc files. The file is removed when the struct is freed. */
+struct temp {
+ struct temp *next;
+ struct shf *shf;
+ int pid; /* pid of process parsed here-doc */
+ Temp_type type;
+ char *name;
+};
+
+/*
+ * stdio and our IO routines
+ */
+
+#define shl_spare (&shf_iob[0]) /* for c_read()/c_print() */
+#define shl_stdout (&shf_iob[1])
+#define shl_out (&shf_iob[2])
+EXTERN int shl_stdout_ok;
+
+/*
+ * trap handlers
+ */
+typedef struct trap {
+ int signal; /* signal number */
+ const char *name; /* short name */
+ const char *mess; /* descriptive name */
+ char *trap; /* trap command */
+ volatile sig_atomic_t set; /* trap pending */
+ int flags; /* TF_* */
+ sig_t cursig; /* current handler (valid if TF_ORIG_* set) */
+ sig_t shtrap; /* shell signal handler */
+} Trap;
+
+/* values for Trap.flags */
+#define TF_SHELL_USES BIT(0) /* shell uses signal, user can't change */
+#define TF_USER_SET BIT(1) /* user has (tried to) set trap */
+#define TF_ORIG_IGN BIT(2) /* original action was SIG_IGN */
+#define TF_ORIG_DFL BIT(3) /* original action was SIG_DFL */
+#define TF_EXEC_IGN BIT(4) /* restore SIG_IGN just before exec */
+#define TF_EXEC_DFL BIT(5) /* restore SIG_DFL just before exec */
+#define TF_DFL_INTR BIT(6) /* when received, default action is LINTR */
+#define TF_TTY_INTR BIT(7) /* tty generated signal (see j_waitj) */
+#define TF_CHANGED BIT(8) /* used by runtrap() to detect trap changes */
+#define TF_FATAL BIT(9) /* causes termination if not trapped */
+
+/* values for setsig()/setexecsig() flags argument */
+#define SS_RESTORE_MASK 0x3 /* how to restore a signal before an exec() */
+#define SS_RESTORE_CURR 0 /* leave current handler in place */
+#define SS_RESTORE_ORIG 1 /* restore original handler */
+#define SS_RESTORE_DFL 2 /* restore to SIG_DFL */
+#define SS_RESTORE_IGN 3 /* restore to SIG_IGN */
+#define SS_FORCE BIT(3) /* set signal even if original signal ignored */
+#define SS_USER BIT(4) /* user is doing the set (ie, trap command) */
+#define SS_SHTRAP BIT(5) /* trap for internal use (CHLD,ALRM,WINCH) */
+
+#define SIGEXIT_ 0 /* for trap EXIT */
+#define SIGERR_ NSIG /* for trap ERR */
+
+EXTERN volatile sig_atomic_t trap; /* traps pending? */
+EXTERN volatile sig_atomic_t intrsig; /* pending trap interrupts command */
+EXTERN volatile sig_atomic_t fatal_trap;/* received a fatal signal */
+extern volatile sig_atomic_t got_sigwinch;
+extern Trap sigtraps[NSIG+1];
+
+/*
+ * TMOUT support
+ */
+/* values for ksh_tmout_state */
+enum tmout_enum {
+ TMOUT_EXECUTING = 0, /* executing commands */
+ TMOUT_READING, /* waiting for input */
+ TMOUT_LEAVING /* have timed out */
+};
+EXTERN unsigned int ksh_tmout;
+EXTERN enum tmout_enum ksh_tmout_state I__(TMOUT_EXECUTING);
+
+/* For "You have stopped jobs" message */
+EXTERN int really_exit;
+
+/*
+ * fast character classes
+ */
+#define C_ALPHA BIT(0) /* a-z_A-Z */
+#define C_DIGIT BIT(1) /* 0-9 */
+#define C_LEX1 BIT(2) /* \0 \t\n|&;<>() */
+#define C_VAR1 BIT(3) /* *@#!$-? */
+#define C_IFSWS BIT(4) /* \t \n (IFS white space) */
+#define C_SUBOP1 BIT(5) /* "=-+?" */
+#define C_SUBOP2 BIT(6) /* "#%" */
+#define C_IFS BIT(7) /* $IFS */
+#define C_QUOTE BIT(8) /* \n\t"#$&'()*;<>?[\`| (needing quoting) */
+
+extern short ctypes [];
+
+#define ctype(c, t) !!(ctypes[(unsigned char)(c)]&(t))
+#define letter(c) ctype(c, C_ALPHA)
+#define digit(c) ctype(c, C_DIGIT)
+#define letnum(c) ctype(c, C_ALPHA|C_DIGIT)
+
+EXTERN int ifs0 I__(' '); /* for "$*" */
+
+/* Argument parsing for built-in commands and getopts command */
+
+/* Values for Getopt.flags */
+#define GF_ERROR BIT(0) /* call errorf() if there is an error */
+#define GF_PLUSOPT BIT(1) /* allow +c as an option */
+#define GF_NONAME BIT(2) /* don't print argv[0] in errors */
+
+/* Values for Getopt.info */
+#define GI_MINUS BIT(0) /* an option started with -... */
+#define GI_PLUS BIT(1) /* an option started with +... */
+#define GI_MINUSMINUS BIT(2) /* arguments were ended with -- */
+
+typedef struct {
+ int optind;
+ int uoptind;/* what user sees in $OPTIND */
+ char *optarg;
+ int flags; /* see GF_* */
+ int info; /* see GI_* */
+ unsigned int p; /* 0 or index into argv[optind - 1] */
+ char buf[2]; /* for bad option OPTARG value */
+} Getopt;
+
+EXTERN Getopt builtin_opt; /* for shell builtin commands */
+EXTERN Getopt user_opt; /* parsing state for getopts builtin command */
+
+/* This for co-processes */
+
+typedef INT32 Coproc_id; /* something that won't (realisticly) wrap */
+struct coproc {
+ int read; /* pipe from co-process's stdout */
+ int readw; /* other side of read (saved temporarily) */
+ int write; /* pipe to co-process's stdin */
+ Coproc_id id; /* id of current output pipe */
+ int njobs; /* number of live jobs using output pipe */
+ void *job; /* 0 or job of co-process using input pipe */
+};
+EXTERN struct coproc coproc;
+
+/* Used in jobs.c and by coprocess stuff in exec.c */
+EXTERN sigset_t sm_default, sm_sigchld;
+
+extern const char ksh_version[];
+
+/* name of called builtin function (used by error functions) */
+EXTERN char *builtin_argv0;
+EXTERN Tflag builtin_flag; /* flags of called builtin (SPEC_BI, etc.) */
+
+/* current working directory, and size of memory allocated for same */
+EXTERN char *current_wd;
+EXTERN int current_wd_size;
+
+#ifdef EDIT
+/* Minimum required space to work with on a line - if the prompt leaves less
+ * space than this on a line, the prompt is truncated.
+ */
+# define MIN_EDIT_SPACE 7
+/* Minimum allowed value for x_cols: 2 for prompt, 3 for " < " at end of line
+ */
+# define MIN_COLS (2 + MIN_EDIT_SPACE + 3)
+EXTERN int x_cols I__(80); /* tty columns */
+#else
+# define x_cols 80 /* for pr_menu(exec.c) */
+#endif
+
+/* These to avoid bracket matching problems */
+#define OPAREN '('
+#define CPAREN ')'
+#define OBRACK '['
+#define CBRACK ']'
+#define OBRACE '{'
+#define CBRACE '}'
+
+/* Determine the location of the system (common) profile */
+#define KSH_SYSTEM_PROFILE "/etc/profile"
+
+/* Used by v_evaluate() and setstr() to control action when error occurs */
+#define KSH_UNWIND_ERROR 0 /* unwind the stack (longjmp) */
+#define KSH_RETURN_ERROR 1 /* return 1/0 for success/failure */
+
+#include "shf.h"
+#include "table.h"
+#include "tree.h"
+#include "expand.h"
+#include "lex.h"
+#include "proto.h"
+
+/* be sure not to interfere with anyone else's idea about EXTERN */
+#ifdef EXTERN_DEFINED
+# undef EXTERN_DEFINED
+# undef EXTERN
+#endif
+#undef I__
diff --git a/shf.c b/shf.c
@@ -0,0 +1,1161 @@
+/* $OpenBSD: shf.c,v 1.16 2013/04/19 17:36:09 millert Exp $ */
+
+/*
+ * Shell file I/O routines
+ */
+
+#include "sh.h"
+#include <sys/stat.h>
+#include "ksh_limval.h"
+
+
+/* flags to shf_emptybuf() */
+#define EB_READSW 0x01 /* about to switch to reading */
+#define EB_GROW 0x02 /* grow buffer if necessary (STRING+DYNAMIC) */
+
+/*
+ * Replacement stdio routines. Stdio is too flakey on too many machines
+ * to be useful when you have multiple processes using the same underlying
+ * file descriptors.
+ */
+
+static int shf_fillbuf(struct shf *);
+static int shf_emptybuf(struct shf *, int);
+
+/* Open a file. First three args are for open(), last arg is flags for
+ * this package. Returns NULL if file could not be opened, or if a dup
+ * fails.
+ */
+struct shf *
+shf_open(const char *name, int oflags, int mode, int sflags)
+{
+ struct shf *shf;
+ int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+ int fd;
+
+ /* Done before open so if alloca fails, fd won't be lost. */
+ shf = (struct shf *) alloc(sizeof(struct shf) + bsize, ATEMP);
+ shf->areap = ATEMP;
+ shf->buf = (unsigned char *) &shf[1];
+ shf->bsize = bsize;
+ shf->flags = SHF_ALLOCS;
+ /* Rest filled in by reopen. */
+
+ fd = open(name, oflags, mode);
+ if (fd < 0) {
+ afree(shf, shf->areap);
+ return NULL;
+ }
+ if ((sflags & SHF_MAPHI) && fd < FDBASE) {
+ int nfd;
+
+ nfd = fcntl(fd, F_DUPFD, FDBASE);
+ close(fd);
+ if (nfd < 0) {
+ afree(shf, shf->areap);
+ return NULL;
+ }
+ fd = nfd;
+ }
+ sflags &= ~SHF_ACCMODE;
+ sflags |= (oflags & O_ACCMODE) == O_RDONLY ? SHF_RD :
+ ((oflags & O_ACCMODE) == O_WRONLY ? SHF_WR : SHF_RDWR);
+
+ return shf_reopen(fd, sflags, shf);
+}
+
+/* Set up the shf structure for a file descriptor. Doesn't fail. */
+struct shf *
+shf_fdopen(int fd, int sflags, struct shf *shf)
+{
+ int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+
+ /* use fcntl() to figure out correct read/write flags */
+ if (sflags & SHF_GETFL) {
+ int flags = fcntl(fd, F_GETFL, 0);
+
+ if (flags < 0)
+ /* will get an error on first read/write */
+ sflags |= SHF_RDWR;
+ else {
+ switch (flags & O_ACCMODE) {
+ case O_RDONLY:
+ sflags |= SHF_RD;
+ break;
+ case O_WRONLY:
+ sflags |= SHF_WR;
+ break;
+ case O_RDWR:
+ sflags |= SHF_RDWR;
+ break;
+ }
+ }
+ }
+
+ if (!(sflags & (SHF_RD | SHF_WR)))
+ internal_errorf(1, "shf_fdopen: missing read/write");
+
+ if (shf) {
+ if (bsize) {
+ shf->buf = (unsigned char *) alloc(bsize, ATEMP);
+ sflags |= SHF_ALLOCB;
+ } else
+ shf->buf = (unsigned char *) 0;
+ } else {
+ shf = (struct shf *) alloc(sizeof(struct shf) + bsize, ATEMP);
+ shf->buf = (unsigned char *) &shf[1];
+ sflags |= SHF_ALLOCS;
+ }
+ shf->areap = ATEMP;
+ shf->fd = fd;
+ shf->rp = shf->wp = shf->buf;
+ shf->rnleft = 0;
+ shf->rbsize = bsize;
+ shf->wnleft = 0; /* force call to shf_emptybuf() */
+ shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize;
+ shf->flags = sflags;
+ shf->errno_ = 0;
+ shf->bsize = bsize;
+ if (sflags & SHF_CLEXEC)
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ return shf;
+}
+
+/* Set up an existing shf (and buffer) to use the given fd */
+struct shf *
+shf_reopen(int fd, int sflags, struct shf *shf)
+{
+ int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE;
+
+ /* use fcntl() to figure out correct read/write flags */
+ if (sflags & SHF_GETFL) {
+ int flags = fcntl(fd, F_GETFL, 0);
+
+ if (flags < 0)
+ /* will get an error on first read/write */
+ sflags |= SHF_RDWR;
+ else {
+ switch (flags & O_ACCMODE) {
+ case O_RDONLY:
+ sflags |= SHF_RD;
+ break;
+ case O_WRONLY:
+ sflags |= SHF_WR;
+ break;
+ case O_RDWR:
+ sflags |= SHF_RDWR;
+ break;
+ }
+ }
+ }
+
+ if (!(sflags & (SHF_RD | SHF_WR)))
+ internal_errorf(1, "shf_reopen: missing read/write");
+ if (!shf || !shf->buf || shf->bsize < bsize)
+ internal_errorf(1, "shf_reopen: bad shf/buf/bsize");
+
+ /* assumes shf->buf and shf->bsize already set up */
+ shf->fd = fd;
+ shf->rp = shf->wp = shf->buf;
+ shf->rnleft = 0;
+ shf->rbsize = bsize;
+ shf->wnleft = 0; /* force call to shf_emptybuf() */
+ shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize;
+ shf->flags = (shf->flags & (SHF_ALLOCS | SHF_ALLOCB)) | sflags;
+ shf->errno_ = 0;
+ if (sflags & SHF_CLEXEC)
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ return shf;
+}
+
+/* Open a string for reading or writing. If reading, bsize is the number
+ * of bytes that can be read. If writing, bsize is the maximum number of
+ * bytes that can be written. If shf is not null, it is filled in and
+ * returned, if it is null, shf is allocated. If writing and buf is null
+ * and SHF_DYNAMIC is set, the buffer is allocated (if bsize > 0, it is
+ * used for the initial size). Doesn't fail.
+ * When writing, a byte is reserved for a trailing null - see shf_sclose().
+ */
+struct shf *
+shf_sopen(char *buf, int bsize, int sflags, struct shf *shf)
+{
+ /* can't have a read+write string */
+ if (!(sflags & (SHF_RD | SHF_WR)) ||
+ (sflags & (SHF_RD | SHF_WR)) == (SHF_RD | SHF_WR))
+ internal_errorf(1, "shf_sopen: flags 0x%x", sflags);
+
+ if (!shf) {
+ shf = (struct shf *) alloc(sizeof(struct shf), ATEMP);
+ sflags |= SHF_ALLOCS;
+ }
+ shf->areap = ATEMP;
+ if (!buf && (sflags & SHF_WR) && (sflags & SHF_DYNAMIC)) {
+ if (bsize <= 0)
+ bsize = 64;
+ sflags |= SHF_ALLOCB;
+ buf = alloc(bsize, shf->areap);
+ }
+ shf->fd = -1;
+ shf->buf = shf->rp = shf->wp = (unsigned char *) buf;
+ shf->rnleft = bsize;
+ shf->rbsize = bsize;
+ shf->wnleft = bsize - 1; /* space for a '\0' */
+ shf->wbsize = bsize;
+ shf->flags = sflags | SHF_STRING;
+ shf->errno_ = 0;
+ shf->bsize = bsize;
+
+ return shf;
+}
+
+/* Flush and close file descriptor, free the shf structure */
+int
+shf_close(struct shf *shf)
+{
+ int ret = 0;
+
+ if (shf->fd >= 0) {
+ ret = shf_flush(shf);
+ if (close(shf->fd) < 0)
+ ret = EOF;
+ }
+ if (shf->flags & SHF_ALLOCS)
+ afree(shf, shf->areap);
+ else if (shf->flags & SHF_ALLOCB)
+ afree(shf->buf, shf->areap);
+
+ return ret;
+}
+
+/* Flush and close file descriptor, don't free file structure */
+int
+shf_fdclose(struct shf *shf)
+{
+ int ret = 0;
+
+ if (shf->fd >= 0) {
+ ret = shf_flush(shf);
+ if (close(shf->fd) < 0)
+ ret = EOF;
+ shf->rnleft = 0;
+ shf->rp = shf->buf;
+ shf->wnleft = 0;
+ shf->fd = -1;
+ }
+
+ return ret;
+}
+
+/* Close a string - if it was opened for writing, it is null terminated;
+ * returns a pointer to the string and frees shf if it was allocated
+ * (does not free string if it was allocated).
+ */
+char *
+shf_sclose(struct shf *shf)
+{
+ unsigned char *s = shf->buf;
+
+ /* null terminate */
+ if (shf->flags & SHF_WR) {
+ shf->wnleft++;
+ shf_putc('\0', shf);
+ }
+ if (shf->flags & SHF_ALLOCS)
+ afree(shf, shf->areap);
+ return (char *) s;
+}
+
+/* Un-read what has been read but not examined, or write what has been
+ * buffered. Returns 0 for success, EOF for (write) error.
+ */
+int
+shf_flush(struct shf *shf)
+{
+ if (shf->flags & SHF_STRING)
+ return (shf->flags & SHF_WR) ? EOF : 0;
+
+ if (shf->fd < 0)
+ internal_errorf(1, "shf_flush: no fd");
+
+ if (shf->flags & SHF_ERROR) {
+ errno = shf->errno_;
+ return EOF;
+ }
+
+ if (shf->flags & SHF_READING) {
+ shf->flags &= ~(SHF_EOF | SHF_READING);
+ if (shf->rnleft > 0) {
+ lseek(shf->fd, (off_t) -shf->rnleft, SEEK_CUR);
+ shf->rnleft = 0;
+ shf->rp = shf->buf;
+ }
+ return 0;
+ } else if (shf->flags & SHF_WRITING)
+ return shf_emptybuf(shf, 0);
+
+ return 0;
+}
+
+/* Write out any buffered data. If currently reading, flushes the read
+ * buffer. Returns 0 for success, EOF for (write) error.
+ */
+static int
+shf_emptybuf(struct shf *shf, int flags)
+{
+ int ret = 0;
+
+ if (!(shf->flags & SHF_STRING) && shf->fd < 0)
+ internal_errorf(1, "shf_emptybuf: no fd");
+
+ if (shf->flags & SHF_ERROR) {
+ errno = shf->errno_;
+ return EOF;
+ }
+
+ if (shf->flags & SHF_READING) {
+ if (flags & EB_READSW) /* doesn't happen */
+ return 0;
+ ret = shf_flush(shf);
+ shf->flags &= ~SHF_READING;
+ }
+ if (shf->flags & SHF_STRING) {
+ unsigned char *nbuf;
+
+ /* Note that we assume SHF_ALLOCS is not set if SHF_ALLOCB
+ * is set... (changing the shf pointer could cause problems)
+ */
+ if (!(flags & EB_GROW) || !(shf->flags & SHF_DYNAMIC) ||
+ !(shf->flags & SHF_ALLOCB))
+ return EOF;
+ /* allocate more space for buffer */
+ nbuf = (unsigned char *) aresize(shf->buf, shf->wbsize * 2,
+ shf->areap);
+ shf->rp = nbuf + (shf->rp - shf->buf);
+ shf->wp = nbuf + (shf->wp - shf->buf);
+ shf->rbsize += shf->wbsize;
+ shf->wnleft += shf->wbsize;
+ shf->wbsize *= 2;
+ shf->buf = nbuf;
+ } else {
+ if (shf->flags & SHF_WRITING) {
+ int ntowrite = shf->wp - shf->buf;
+ unsigned char *buf = shf->buf;
+ int n;
+
+ while (ntowrite > 0) {
+ n = write(shf->fd, buf, ntowrite);
+ if (n < 0) {
+ if (errno == EINTR &&
+ !(shf->flags & SHF_INTERRUPT))
+ continue;
+ shf->flags |= SHF_ERROR;
+ shf->errno_ = errno;
+ shf->wnleft = 0;
+ if (buf != shf->buf) {
+ /* allow a second flush
+ * to work */
+ memmove(shf->buf, buf,
+ ntowrite);
+ shf->wp = shf->buf + ntowrite;
+ }
+ return EOF;
+ }
+ buf += n;
+ ntowrite -= n;
+ }
+ if (flags & EB_READSW) {
+ shf->wp = shf->buf;
+ shf->wnleft = 0;
+ shf->flags &= ~SHF_WRITING;
+ return 0;
+ }
+ }
+ shf->wp = shf->buf;
+ shf->wnleft = shf->wbsize;
+ }
+ shf->flags |= SHF_WRITING;
+
+ return ret;
+}
+
+/* Fill up a read buffer. Returns EOF for a read error, 0 otherwise. */
+static int
+shf_fillbuf(struct shf *shf)
+{
+ if (shf->flags & SHF_STRING)
+ return 0;
+
+ if (shf->fd < 0)
+ internal_errorf(1, "shf_fillbuf: no fd");
+
+ if (shf->flags & (SHF_EOF | SHF_ERROR)) {
+ if (shf->flags & SHF_ERROR)
+ errno = shf->errno_;
+ return EOF;
+ }
+
+ if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF)
+ return EOF;
+
+ shf->flags |= SHF_READING;
+
+ shf->rp = shf->buf;
+ while (1) {
+ shf->rnleft = blocking_read(shf->fd, (char *) shf->buf,
+ shf->rbsize);
+ if (shf->rnleft < 0 && errno == EINTR &&
+ !(shf->flags & SHF_INTERRUPT))
+ continue;
+ break;
+ }
+ if (shf->rnleft <= 0) {
+ if (shf->rnleft < 0) {
+ shf->flags |= SHF_ERROR;
+ shf->errno_ = errno;
+ shf->rnleft = 0;
+ shf->rp = shf->buf;
+ return EOF;
+ }
+ shf->flags |= SHF_EOF;
+ }
+ return 0;
+}
+
+/* Read a buffer from shf. Returns the number of bytes read into buf,
+ * if no bytes were read, returns 0 if end of file was seen, EOF if
+ * a read error occurred.
+ */
+int
+shf_read(char *buf, int bsize, struct shf *shf)
+{
+ int orig_bsize = bsize;
+ int ncopy;
+
+ if (!(shf->flags & SHF_RD))
+ internal_errorf(1, "shf_read: flags %x", shf->flags);
+
+ if (bsize <= 0)
+ internal_errorf(1, "shf_read: bsize %d", bsize);
+
+ while (bsize > 0) {
+ if (shf->rnleft == 0 &&
+ (shf_fillbuf(shf) == EOF || shf->rnleft == 0))
+ break;
+ ncopy = shf->rnleft;
+ if (ncopy > bsize)
+ ncopy = bsize;
+ memcpy(buf, shf->rp, ncopy);
+ buf += ncopy;
+ bsize -= ncopy;
+ shf->rp += ncopy;
+ shf->rnleft -= ncopy;
+ }
+ /* Note: fread(3S) returns 0 for errors - this doesn't */
+ return orig_bsize == bsize ? (shf_error(shf) ? EOF : 0) :
+ orig_bsize - bsize;
+}
+
+/* Read up to a newline or EOF. The newline is put in buf; buf is always
+ * null terminated. Returns NULL on read error or if nothing was read before
+ * end of file, returns a pointer to the null byte in buf otherwise.
+ */
+char *
+shf_getse(char *buf, int bsize, struct shf *shf)
+{
+ unsigned char *end;
+ int ncopy;
+ char *orig_buf = buf;
+
+ if (!(shf->flags & SHF_RD))
+ internal_errorf(1, "shf_getse: flags %x", shf->flags);
+
+ if (bsize <= 0)
+ return (char *) 0;
+
+ --bsize; /* save room for null */
+ do {
+ if (shf->rnleft == 0) {
+ if (shf_fillbuf(shf) == EOF)
+ return NULL;
+ if (shf->rnleft == 0) {
+ *buf = '\0';
+ return buf == orig_buf ? NULL : buf;
+ }
+ }
+ end = (unsigned char *) memchr((char *) shf->rp, '\n',
+ shf->rnleft);
+ ncopy = end ? end - shf->rp + 1 : shf->rnleft;
+ if (ncopy > bsize)
+ ncopy = bsize;
+ memcpy(buf, (char *) shf->rp, ncopy);
+ shf->rp += ncopy;
+ shf->rnleft -= ncopy;
+ buf += ncopy;
+ bsize -= ncopy;
+ } while (!end && bsize);
+ *buf = '\0';
+ return buf;
+}
+
+/* Returns the char read. Returns EOF for error and end of file. */
+int
+shf_getchar(struct shf *shf)
+{
+ if (!(shf->flags & SHF_RD))
+ internal_errorf(1, "shf_getchar: flags %x", shf->flags);
+
+ if (shf->rnleft == 0 && (shf_fillbuf(shf) == EOF || shf->rnleft == 0))
+ return EOF;
+ --shf->rnleft;
+ return *shf->rp++;
+}
+
+/* Put a character back in the input stream. Returns the character if
+ * successful, EOF if there is no room.
+ */
+int
+shf_ungetc(int c, struct shf *shf)
+{
+ if (!(shf->flags & SHF_RD))
+ internal_errorf(1, "shf_ungetc: flags %x", shf->flags);
+
+ if ((shf->flags & SHF_ERROR) || c == EOF ||
+ (shf->rp == shf->buf && shf->rnleft))
+ return EOF;
+
+ if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF)
+ return EOF;
+
+ if (shf->rp == shf->buf)
+ shf->rp = shf->buf + shf->rbsize;
+ if (shf->flags & SHF_STRING) {
+ /* Can unget what was read, but not something different - we
+ * don't want to modify a string.
+ */
+ if (shf->rp[-1] != c)
+ return EOF;
+ shf->flags &= ~SHF_EOF;
+ shf->rp--;
+ shf->rnleft++;
+ return c;
+ }
+ shf->flags &= ~SHF_EOF;
+ *--(shf->rp) = c;
+ shf->rnleft++;
+ return c;
+}
+
+/* Write a character. Returns the character if successful, EOF if
+ * the char could not be written.
+ */
+int
+shf_putchar(int c, struct shf *shf)
+{
+ if (!(shf->flags & SHF_WR))
+ internal_errorf(1, "shf_putchar: flags %x", shf->flags);
+
+ if (c == EOF)
+ return EOF;
+
+ if (shf->flags & SHF_UNBUF) {
+ char cc = c;
+ int n;
+
+ if (shf->fd < 0)
+ internal_errorf(1, "shf_putchar: no fd");
+ if (shf->flags & SHF_ERROR) {
+ errno = shf->errno_;
+ return EOF;
+ }
+ while ((n = write(shf->fd, &cc, 1)) != 1)
+ if (n < 0) {
+ if (errno == EINTR &&
+ !(shf->flags & SHF_INTERRUPT))
+ continue;
+ shf->flags |= SHF_ERROR;
+ shf->errno_ = errno;
+ return EOF;
+ }
+ } else {
+ /* Flush deals with strings and sticky errors */
+ if (shf->wnleft == 0 && shf_emptybuf(shf, EB_GROW) == EOF)
+ return EOF;
+ shf->wnleft--;
+ *shf->wp++ = c;
+ }
+
+ return c;
+}
+
+/* Write a string. Returns the length of the string if successful, EOF if
+ * the string could not be written.
+ */
+int
+shf_puts(const char *s, struct shf *shf)
+{
+ if (!s)
+ return EOF;
+
+ return shf_write(s, strlen(s), shf);
+}
+
+/* Write a buffer. Returns nbytes if successful, EOF if there is an error. */
+int
+shf_write(const char *buf, int nbytes, struct shf *shf)
+{
+ int orig_nbytes = nbytes;
+ int n;
+ int ncopy;
+
+ if (!(shf->flags & SHF_WR))
+ internal_errorf(1, "shf_write: flags %x", shf->flags);
+
+ if (nbytes < 0)
+ internal_errorf(1, "shf_write: nbytes %d", nbytes);
+
+ /* Don't buffer if buffer is empty and we're writting a large amount. */
+ if ((ncopy = shf->wnleft) &&
+ (shf->wp != shf->buf || nbytes < shf->wnleft)) {
+ if (ncopy > nbytes)
+ ncopy = nbytes;
+ memcpy(shf->wp, buf, ncopy);
+ nbytes -= ncopy;
+ buf += ncopy;
+ shf->wp += ncopy;
+ shf->wnleft -= ncopy;
+ }
+ if (nbytes > 0) {
+ /* Flush deals with strings and sticky errors */
+ if (shf_emptybuf(shf, EB_GROW) == EOF)
+ return EOF;
+ if (nbytes > shf->wbsize) {
+ ncopy = nbytes;
+ if (shf->wbsize)
+ ncopy -= nbytes % shf->wbsize;
+ nbytes -= ncopy;
+ while (ncopy > 0) {
+ n = write(shf->fd, buf, ncopy);
+ if (n < 0) {
+ if (errno == EINTR &&
+ !(shf->flags & SHF_INTERRUPT))
+ continue;
+ shf->flags |= SHF_ERROR;
+ shf->errno_ = errno;
+ shf->wnleft = 0;
+ /* Note: fwrite(3S) returns 0 for
+ * errors - this doesn't */
+ return EOF;
+ }
+ buf += n;
+ ncopy -= n;
+ }
+ }
+ if (nbytes > 0) {
+ memcpy(shf->wp, buf, nbytes);
+ shf->wp += nbytes;
+ shf->wnleft -= nbytes;
+ }
+ }
+
+ return orig_nbytes;
+}
+
+int
+shf_fprintf(struct shf *shf, const char *fmt, ...)
+{
+ va_list args;
+ int n;
+
+ va_start(args, fmt);
+ n = shf_vfprintf(shf, fmt, args);
+ va_end(args);
+
+ return n;
+}
+
+int
+shf_snprintf(char *buf, int bsize, const char *fmt, ...)
+{
+ struct shf shf;
+ va_list args;
+ int n;
+
+ if (!buf || bsize <= 0)
+ internal_errorf(1, "shf_snprintf: buf %lx, bsize %d",
+ (long) buf, bsize);
+
+ shf_sopen(buf, bsize, SHF_WR, &shf);
+ va_start(args, fmt);
+ n = shf_vfprintf(&shf, fmt, args);
+ va_end(args);
+ shf_sclose(&shf); /* null terminates */
+ return n;
+}
+
+char *
+shf_smprintf(const char *fmt, ...)
+{
+ struct shf shf;
+ va_list args;
+
+ shf_sopen((char *) 0, 0, SHF_WR|SHF_DYNAMIC, &shf);
+ va_start(args, fmt);
+ shf_vfprintf(&shf, fmt, args);
+ va_end(args);
+ return shf_sclose(&shf); /* null terminates */
+}
+
+#undef FP /* if you want floating point stuff */
+
+#define BUF_SIZE 128
+#define FPBUF_SIZE (DMAXEXP+16)/* this must be >
+ * MAX(DMAXEXP, log10(pow(2, DSIGNIF)))
+ * + ceil(log10(DMAXEXP)) + 8 (I think).
+ * Since this is hard to express as a
+ * constant, just use a large buffer.
+ */
+
+/*
+ * What kinda of machine we on? Hopefully the C compiler will optimize
+ * this out...
+ *
+ * For shorts, we want sign extend for %d but not for %[oxu] - on 16 bit
+ * machines it don't matter. Assumes C compiler has converted shorts to
+ * ints before pushing them.
+ */
+#define POP_INT(f, s, a) \
+ (((f) & FL_LLONG) ? va_arg((a), unsigned long long) : \
+ ((f) & FL_LONG) ? va_arg((a), unsigned long) : \
+ (sizeof(int) < sizeof(long) ? ((s) ? \
+ (long) va_arg((a), int) : va_arg((a), unsigned)) : \
+ va_arg((a), unsigned)))
+
+#define ABIGNUM 32000 /* big numer that will fit in a short */
+#define LOG2_10 3.321928094887362347870319429 /* log base 2 of 10 */
+
+#define FL_HASH 0x001 /* `#' seen */
+#define FL_PLUS 0x002 /* `+' seen */
+#define FL_RIGHT 0x004 /* `-' seen */
+#define FL_BLANK 0x008 /* ` ' seen */
+#define FL_SHORT 0x010 /* `h' seen */
+#define FL_LONG 0x020 /* `l' seen */
+#define FL_LLONG 0x040 /* `ll' seen */
+#define FL_ZERO 0x080 /* `0' seen */
+#define FL_DOT 0x100 /* '.' seen */
+#define FL_UPPER 0x200 /* format character was uppercase */
+#define FL_NUMBER 0x400 /* a number was formated %[douxefg] */
+
+
+#ifdef FP
+#include <math.h>
+
+static double
+my_ceil(double d)
+{
+ double i;
+
+ return d - modf(d, &i) + (d < 0 ? -1 : 1);
+}
+#endif /* FP */
+
+int
+shf_vfprintf(struct shf *shf, const char *fmt, va_list args)
+{
+ char c, *s;
+ int tmp = 0;
+ int field, precision;
+ int len;
+ int flags;
+ unsigned long long llnum;
+ /* %#o produces the longest output */
+ char numbuf[(BITS(long long) + 2) / 3 + 1];
+ /* this stuff for dealing with the buffer */
+ int nwritten = 0;
+#ifdef FP
+ /* should be in <math.h>
+ * extern double frexp();
+ */
+ extern char *ecvt();
+
+ double fpnum;
+ int expo, decpt;
+ char style;
+ char fpbuf[FPBUF_SIZE];
+#endif /* FP */
+
+ if (!fmt)
+ return 0;
+
+ while ((c = *fmt++)) {
+ if (c != '%') {
+ shf_putc(c, shf);
+ nwritten++;
+ continue;
+ }
+ /*
+ * This will accept flags/fields in any order - not
+ * just the order specified in printf(3), but this is
+ * the way _doprnt() seems to work (on bsd and sysV).
+ * The only restriction is that the format character must
+ * come last :-).
+ */
+ flags = field = precision = 0;
+ for ( ; (c = *fmt++) ; ) {
+ switch (c) {
+ case '#':
+ flags |= FL_HASH;
+ continue;
+
+ case '+':
+ flags |= FL_PLUS;
+ continue;
+
+ case '-':
+ flags |= FL_RIGHT;
+ continue;
+
+ case ' ':
+ flags |= FL_BLANK;
+ continue;
+
+ case '0':
+ if (!(flags & FL_DOT))
+ flags |= FL_ZERO;
+ continue;
+
+ case '.':
+ flags |= FL_DOT;
+ precision = 0;
+ continue;
+
+ case '*':
+ tmp = va_arg(args, int);
+ if (flags & FL_DOT)
+ precision = tmp;
+ else if ((field = tmp) < 0) {
+ field = -field;
+ flags |= FL_RIGHT;
+ }
+ continue;
+
+ case 'l':
+ if (*fmt == 'l') {
+ fmt++;
+ flags |= FL_LLONG;
+ } else
+ flags |= FL_LONG;
+ continue;
+
+ case 'h':
+ flags |= FL_SHORT;
+ continue;
+ }
+ if (digit(c)) {
+ tmp = c - '0';
+ while (c = *fmt++, digit(c))
+ tmp = tmp * 10 + c - '0';
+ --fmt;
+ if (tmp < 0) /* overflow? */
+ tmp = 0;
+ if (flags & FL_DOT)
+ precision = tmp;
+ else
+ field = tmp;
+ continue;
+ }
+ break;
+ }
+
+ if (precision < 0)
+ precision = 0;
+
+ if (!c) /* nasty format */
+ break;
+
+ if (c >= 'A' && c <= 'Z') {
+ flags |= FL_UPPER;
+ c = c - 'A' + 'a';
+ }
+
+ switch (c) {
+ case 'p': /* pointer */
+ flags &= ~(FL_LLONG | FL_LONG | FL_SHORT);
+ if (sizeof(char *) > sizeof(int))
+ flags |= FL_LONG; /* hope it fits.. */
+ /* aaahhh... */
+ case 'd':
+ case 'i':
+ case 'o':
+ case 'u':
+ case 'x':
+ flags |= FL_NUMBER;
+ s = &numbuf[sizeof(numbuf)];
+ llnum = POP_INT(flags, c == 'd', args);
+ switch (c) {
+ case 'd':
+ case 'i':
+ if (0 > (long long) llnum)
+ llnum = - (long long) llnum, tmp = 1;
+ else
+ tmp = 0;
+ /* aaahhhh..... */
+
+ case 'u':
+ do {
+ *--s = llnum % 10 + '0';
+ llnum /= 10;
+ } while (llnum);
+
+ if (c != 'u') {
+ if (tmp)
+ *--s = '-';
+ else if (flags & FL_PLUS)
+ *--s = '+';
+ else if (flags & FL_BLANK)
+ *--s = ' ';
+ }
+ break;
+
+ case 'o':
+ do {
+ *--s = (llnum & 0x7) + '0';
+ llnum >>= 3;
+ } while (llnum);
+
+ if ((flags & FL_HASH) && *s != '0')
+ *--s = '0';
+ break;
+
+ case 'p':
+ case 'x':
+ {
+ const char *digits = (flags & FL_UPPER) ?
+ "0123456789ABCDEF" :
+ "0123456789abcdef";
+ do {
+ *--s = digits[llnum & 0xf];
+ llnum >>= 4;
+ } while (llnum);
+
+ if (flags & FL_HASH) {
+ *--s = (flags & FL_UPPER) ? 'X' : 'x';
+ *--s = '0';
+ }
+ }
+ }
+ len = &numbuf[sizeof(numbuf)] - s;
+ if (flags & FL_DOT) {
+ if (precision > len) {
+ field = precision;
+ flags |= FL_ZERO;
+ } else
+ precision = len; /* no loss */
+ }
+ break;
+
+#ifdef FP
+ case 'e':
+ case 'g':
+ case 'f':
+ {
+ char *p;
+
+ /*
+ * This could probably be done better,
+ * but it seems to work. Note that gcvt()
+ * is not used, as you cannot tell it to
+ * not strip the zeros.
+ */
+ flags |= FL_NUMBER;
+ if (!(flags & FL_DOT))
+ precision = 6; /* default */
+ /*
+ * Assumes doubles are pushed on
+ * the stack. If this is not so, then
+ * FL_LLONG/FL_LONG/FL_SHORT should be checked.
+ */
+ fpnum = va_arg(args, double);
+ s = fpbuf;
+ style = c;
+ /*
+ * This is the same as
+ * expo = ceil(log10(fpnum))
+ * but doesn't need -lm. This is an
+ * approximation as expo is rounded up.
+ */
+ (void) frexp(fpnum, &expo);
+ expo = my_ceil(expo / LOG2_10);
+
+ if (expo < 0)
+ expo = 0;
+
+ p = ecvt(fpnum, precision + 1 + expo,
+ &decpt, &tmp);
+ if (c == 'g') {
+ if (decpt < -4 || decpt > precision)
+ style = 'e';
+ else
+ style = 'f';
+ if (decpt > 0 && (precision -= decpt) < 0)
+ precision = 0;
+ }
+ if (tmp)
+ *s++ = '-';
+ else if (flags & FL_PLUS)
+ *s++ = '+';
+ else if (flags & FL_BLANK)
+ *s++ = ' ';
+
+ if (style == 'e')
+ *s++ = *p++;
+ else {
+ if (decpt > 0) {
+ /* Overflow check - should
+ * never have this problem.
+ */
+ if (decpt > &fpbuf[sizeof(fpbuf)] - s - 8)
+ decpt = &fpbuf[sizeof(fpbuf)] - s - 8;
+ (void) memcpy(s, p, decpt);
+ s += decpt;
+ p += decpt;
+ } else
+ *s++ = '0';
+ }
+
+ /* print the fraction? */
+ if (precision > 0) {
+ *s++ = '.';
+ /* Overflow check - should
+ * never have this problem.
+ */
+ if (precision > &fpbuf[sizeof(fpbuf)] - s - 7)
+ precision = &fpbuf[sizeof(fpbuf)] - s - 7;
+ for (tmp = decpt; tmp++ < 0 &&
+ precision > 0 ; precision--)
+ *s++ = '0';
+ tmp = strlen(p);
+ if (precision > tmp)
+ precision = tmp;
+ /* Overflow check - should
+ * never have this problem.
+ */
+ if (precision > &fpbuf[sizeof(fpbuf)] - s - 7)
+ precision = &fpbuf[sizeof(fpbuf)] - s - 7;
+ (void) memcpy(s, p, precision);
+ s += precision;
+ /*
+ * `g' format strips trailing
+ * zeros after the decimal.
+ */
+ if (c == 'g' && !(flags & FL_HASH)) {
+ while (*--s == '0')
+ ;
+ if (*s != '.')
+ s++;
+ }
+ } else if (flags & FL_HASH)
+ *s++ = '.';
+
+ if (style == 'e') {
+ *s++ = (flags & FL_UPPER) ? 'E' : 'e';
+ if (--decpt >= 0)
+ *s++ = '+';
+ else {
+ *s++ = '-';
+ decpt = -decpt;
+ }
+ p = &numbuf[sizeof(numbuf)];
+ for (tmp = 0; tmp < 2 || decpt ; tmp++) {
+ *--p = '0' + decpt % 10;
+ decpt /= 10;
+ }
+ tmp = &numbuf[sizeof(numbuf)] - p;
+ (void) memcpy(s, p, tmp);
+ s += tmp;
+ }
+
+ len = s - fpbuf;
+ s = fpbuf;
+ precision = len;
+ break;
+ }
+#endif /* FP */
+
+ case 's':
+ if (!(s = va_arg(args, char *)))
+ s = "(null %s)";
+ len = strlen(s);
+ break;
+
+ case 'c':
+ flags &= ~FL_DOT;
+ numbuf[0] = va_arg(args, int);
+ s = numbuf;
+ len = 1;
+ break;
+
+ case '%':
+ default:
+ numbuf[0] = c;
+ s = numbuf;
+ len = 1;
+ break;
+ }
+
+ /*
+ * At this point s should point to a string that is
+ * to be formatted, and len should be the length of the
+ * string.
+ */
+ if (!(flags & FL_DOT) || len < precision)
+ precision = len;
+ if (field > precision) {
+ field -= precision;
+ if (!(flags & FL_RIGHT)) {
+ field = -field;
+ /* skip past sign or 0x when padding with 0 */
+ if ((flags & FL_ZERO) && (flags & FL_NUMBER)) {
+ if (*s == '+' || *s == '-' || *s ==' ') {
+ shf_putc(*s, shf);
+ s++;
+ precision--;
+ nwritten++;
+ } else if (*s == '0') {
+ shf_putc(*s, shf);
+ s++;
+ nwritten++;
+ if (--precision > 0 &&
+ (*s | 0x20) == 'x') {
+ shf_putc(*s, shf);
+ s++;
+ precision--;
+ nwritten++;
+ }
+ }
+ c = '0';
+ } else
+ c = flags & FL_ZERO ? '0' : ' ';
+ if (field < 0) {
+ nwritten += -field;
+ for ( ; field < 0 ; field++)
+ shf_putc(c, shf);
+ }
+ } else
+ c = ' ';
+ } else
+ field = 0;
+
+ if (precision > 0) {
+ nwritten += precision;
+ for ( ; precision-- > 0 ; s++)
+ shf_putc(*s, shf);
+ }
+ if (field > 0) {
+ nwritten += field;
+ for ( ; field > 0 ; --field)
+ shf_putc(c, shf);
+ }
+ }
+
+ return shf_error(shf) ? EOF : nwritten;
+}
diff --git a/shf.h b/shf.h
@@ -0,0 +1,82 @@
+/* $OpenBSD: shf.h,v 1.6 2005/12/11 18:53:51 deraadt Exp $ */
+
+#ifndef SHF_H
+# define SHF_H
+
+/*
+ * Shell file I/O routines
+ */
+
+#define SHF_BSIZE 512
+
+#define shf_fileno(shf) ((shf)->fd)
+#define shf_setfileno(shf,nfd) ((shf)->fd = (nfd))
+#define shf_getc(shf) ((shf)->rnleft > 0 ? (shf)->rnleft--, *(shf)->rp++ : \
+ shf_getchar(shf))
+#define shf_putc(c, shf) ((shf)->wnleft == 0 ? shf_putchar((c), (shf)) : \
+ ((shf)->wnleft--, *(shf)->wp++ = (c)))
+#define shf_eof(shf) ((shf)->flags & SHF_EOF)
+#define shf_error(shf) ((shf)->flags & SHF_ERROR)
+#define shf_errno(shf) ((shf)->errno_)
+#define shf_clearerr(shf) ((shf)->flags &= ~(SHF_EOF | SHF_ERROR))
+
+/* Flags passed to shf_*open() */
+#define SHF_RD 0x0001
+#define SHF_WR 0x0002
+#define SHF_RDWR (SHF_RD|SHF_WR)
+#define SHF_ACCMODE 0x0003 /* mask */
+#define SHF_GETFL 0x0004 /* use fcntl() to figure RD/WR flags */
+#define SHF_UNBUF 0x0008 /* unbuffered I/O */
+#define SHF_CLEXEC 0x0010 /* set close on exec flag */
+#define SHF_MAPHI 0x0020 /* make fd > FDBASE (and close orig)
+ * (shf_open() only) */
+#define SHF_DYNAMIC 0x0040 /* string: increase buffer as needed */
+#define SHF_INTERRUPT 0x0080 /* EINTR in read/write causes error */
+/* Flags used internally */
+#define SHF_STRING 0x0100 /* a string, not a file */
+#define SHF_ALLOCS 0x0200 /* shf and shf->buf were alloc()ed */
+#define SHF_ALLOCB 0x0400 /* shf->buf was alloc()ed */
+#define SHF_ERROR 0x0800 /* read()/write() error */
+#define SHF_EOF 0x1000 /* read eof (sticky) */
+#define SHF_READING 0x2000 /* currently reading: rnleft,rp valid */
+#define SHF_WRITING 0x4000 /* currently writing: wnleft,wp valid */
+
+
+struct shf {
+ int flags; /* see SHF_* */
+ unsigned char *rp; /* read: current position in buffer */
+ int rbsize; /* size of buffer (1 if SHF_UNBUF) */
+ int rnleft; /* read: how much data left in buffer */
+ unsigned char *wp; /* write: current position in buffer */
+ int wbsize; /* size of buffer (0 if SHF_UNBUF) */
+ int wnleft; /* write: how much space left in buffer */
+ unsigned char *buf; /* buffer */
+ int fd; /* file descriptor */
+ int errno_; /* saved value of errno after error */
+ int bsize; /* actual size of buf */
+ Area *areap; /* area shf/buf were allocated in */
+};
+
+extern struct shf shf_iob[];
+
+struct shf *shf_open(const char *, int, int, int);
+struct shf *shf_fdopen(int, int, struct shf *);
+struct shf *shf_reopen(int, int, struct shf *);
+struct shf *shf_sopen(char *, int, int, struct shf *);
+int shf_close(struct shf *);
+int shf_fdclose(struct shf *);
+char *shf_sclose(struct shf *);
+int shf_flush(struct shf *);
+int shf_read(char *, int, struct shf *);
+char *shf_getse(char *, int, struct shf *);
+int shf_getchar(struct shf *s);
+int shf_ungetc(int, struct shf *);
+int shf_putchar(int, struct shf *);
+int shf_puts(const char *, struct shf *);
+int shf_write(const char *, int, struct shf *);
+int shf_fprintf(struct shf *, const char *, ...);
+int shf_snprintf(char *, int, const char *, ...);
+char *shf_smprintf(const char *, ...);
+int shf_vfprintf(struct shf *, const char *, va_list);
+
+#endif /* SHF_H */
diff --git a/syn.c b/syn.c
@@ -0,0 +1,897 @@
+/* $OpenBSD: syn.c,v 1.29 2013/06/03 18:40:05 jca Exp $ */
+
+/*
+ * shell parser (C version)
+ */
+
+#include "sh.h"
+#include "c_test.h"
+
+struct nesting_state {
+ int start_token; /* token than began nesting (eg, FOR) */
+ int start_line; /* line nesting began on */
+};
+
+static void yyparse(void);
+static struct op *pipeline(int);
+static struct op *andor(void);
+static struct op *c_list(int);
+static struct ioword *synio(int);
+static void musthave(int, int);
+static struct op *nested(int, int, int);
+static struct op *get_command(int);
+static struct op *dogroup(void);
+static struct op *thenpart(void);
+static struct op *elsepart(void);
+static struct op *caselist(void);
+static struct op *casepart(int);
+static struct op *function_body(char *, int);
+static char ** wordlist(void);
+static struct op *block(int, struct op *, struct op *, char **);
+static struct op *newtp(int);
+static void syntaxerr(const char *) __attribute__((__noreturn__));
+static void nesting_push(struct nesting_state *, int);
+static void nesting_pop(struct nesting_state *);
+static int assign_command(char *);
+static int inalias(struct source *);
+static int dbtestp_isa(Test_env *, Test_meta);
+static const char *dbtestp_getopnd(Test_env *, Test_op, int);
+static int dbtestp_eval(Test_env *, Test_op, const char *, const char *,
+ int);
+static void dbtestp_error(Test_env *, int, const char *);
+
+static struct op *outtree; /* yyparse output */
+
+static struct nesting_state nesting; /* \n changed to ; */
+
+static int reject; /* token(cf) gets symbol again */
+static int symbol; /* yylex value */
+
+#define REJECT (reject = 1)
+#define ACCEPT (reject = 0)
+#define token(cf) \
+ ((reject) ? (ACCEPT, symbol) : (symbol = yylex(cf)))
+#define tpeek(cf) \
+ ((reject) ? (symbol) : (REJECT, symbol = yylex(cf)))
+
+static void
+yyparse(void)
+{
+ int c;
+
+ ACCEPT;
+
+ outtree = c_list(source->type == SSTRING);
+ c = tpeek(0);
+ if (c == 0 && !outtree)
+ outtree = newtp(TEOF);
+ else if (c != '\n' && c != 0)
+ syntaxerr((char *) 0);
+}
+
+static struct op *
+pipeline(int cf)
+{
+ struct op *t, *p, *tl = NULL;
+
+ t = get_command(cf);
+ if (t != NULL) {
+ while (token(0) == '|') {
+ if ((p = get_command(CONTIN)) == NULL)
+ syntaxerr((char *) 0);
+ if (tl == NULL)
+ t = tl = block(TPIPE, t, p, NOWORDS);
+ else
+ tl = tl->right = block(TPIPE, tl->right, p, NOWORDS);
+ }
+ REJECT;
+ }
+ return (t);
+}
+
+static struct op *
+andor(void)
+{
+ struct op *t, *p;
+ int c;
+
+ t = pipeline(0);
+ if (t != NULL) {
+ while ((c = token(0)) == LOGAND || c == LOGOR) {
+ if ((p = pipeline(CONTIN)) == NULL)
+ syntaxerr((char *) 0);
+ t = block(c == LOGAND? TAND: TOR, t, p, NOWORDS);
+ }
+ REJECT;
+ }
+ return (t);
+}
+
+static struct op *
+c_list(int multi)
+{
+ struct op *t = NULL, *p, *tl = NULL;
+ int c;
+ int have_sep;
+
+ while (1) {
+ p = andor();
+ /* Token has always been read/rejected at this point, so
+ * we don't worry about what flags to pass token()
+ */
+ c = token(0);
+ have_sep = 1;
+ if (c == '\n' && (multi || inalias(source))) {
+ if (!p) /* ignore blank lines */
+ continue;
+ } else if (!p)
+ break;
+ else if (c == '&' || c == COPROC)
+ p = block(c == '&' ? TASYNC : TCOPROC,
+ p, NOBLOCK, NOWORDS);
+ else if (c != ';')
+ have_sep = 0;
+ if (!t)
+ t = p;
+ else if (!tl)
+ t = tl = block(TLIST, t, p, NOWORDS);
+ else
+ tl = tl->right = block(TLIST, tl->right, p, NOWORDS);
+ if (!have_sep)
+ break;
+ }
+ REJECT;
+ return t;
+}
+
+static struct ioword *
+synio(int cf)
+{
+ struct ioword *iop;
+ int ishere;
+
+ if (tpeek(cf) != REDIR)
+ return NULL;
+ ACCEPT;
+ iop = yylval.iop;
+ ishere = (iop->flag&IOTYPE) == IOHERE;
+ musthave(LWORD, ishere ? HEREDELIM : 0);
+ if (ishere) {
+ iop->delim = yylval.cp;
+ if (*ident != 0) /* unquoted */
+ iop->flag |= IOEVAL;
+ if (herep >= &heres[HERES])
+ yyerror("too many <<'s\n");
+ *herep++ = iop;
+ } else
+ iop->name = yylval.cp;
+ return iop;
+}
+
+static void
+musthave(int c, int cf)
+{
+ if ((token(cf)) != c)
+ syntaxerr((char *) 0);
+}
+
+static struct op *
+nested(int type, int smark, int emark)
+{
+ struct op *t;
+ struct nesting_state old_nesting;
+
+ nesting_push(&old_nesting, smark);
+ t = c_list(true);
+ musthave(emark, KEYWORD|ALIAS);
+ nesting_pop(&old_nesting);
+ return (block(type, t, NOBLOCK, NOWORDS));
+}
+
+static struct op *
+get_command(int cf)
+{
+ struct op *t;
+ int c, iopn = 0, syniocf;
+ struct ioword *iop, **iops;
+ XPtrV args, vars;
+ struct nesting_state old_nesting;
+
+ iops = (struct ioword **) alloc(sizeofN(struct ioword *, NUFILE+1),
+ ATEMP);
+ XPinit(args, 16);
+ XPinit(vars, 16);
+
+ syniocf = KEYWORD|ALIAS;
+ switch (c = token(cf|KEYWORD|ALIAS|VARASN)) {
+ default:
+ REJECT;
+ afree((void*) iops, ATEMP);
+ XPfree(args);
+ XPfree(vars);
+ return NULL; /* empty line */
+
+ case LWORD:
+ case REDIR:
+ REJECT;
+ syniocf &= ~(KEYWORD|ALIAS);
+ t = newtp(TCOM);
+ t->lineno = source->line;
+ while (1) {
+ cf = (t->u.evalflags ? ARRAYVAR : 0) |
+ (XPsize(args) == 0 ? ALIAS|VARASN : CMDWORD);
+ switch (tpeek(cf)) {
+ case REDIR:
+ if (iopn >= NUFILE)
+ yyerror("too many redirections\n");
+ iops[iopn++] = synio(cf);
+ break;
+
+ case LWORD:
+ ACCEPT;
+ /* the iopn == 0 and XPsize(vars) == 0 are
+ * dubious but at&t ksh acts this way
+ */
+ if (iopn == 0 && XPsize(vars) == 0 &&
+ XPsize(args) == 0 &&
+ assign_command(ident))
+ t->u.evalflags = DOVACHECK;
+ if ((XPsize(args) == 0 || Flag(FKEYWORD)) &&
+ is_wdvarassign(yylval.cp))
+ XPput(vars, yylval.cp);
+ else
+ XPput(args, yylval.cp);
+ break;
+
+ case '(':
+ /* Check for "> foo (echo hi)", which at&t ksh
+ * allows (not POSIX, but not disallowed)
+ */
+ afree(t, ATEMP);
+ if (XPsize(args) == 0 && XPsize(vars) == 0) {
+ ACCEPT;
+ goto Subshell;
+ }
+ /* Must be a function */
+ if (iopn != 0 || XPsize(args) != 1 ||
+ XPsize(vars) != 0)
+ syntaxerr((char *) 0);
+ ACCEPT;
+ /*(*/
+ musthave(')', 0);
+ t = function_body(XPptrv(args)[0], false);
+ goto Leave;
+
+ default:
+ goto Leave;
+ }
+ }
+ Leave:
+ break;
+
+ Subshell:
+ case '(':
+ t = nested(TPAREN, '(', ')');
+ break;
+
+ case '{': /*}*/
+ t = nested(TBRACE, '{', '}');
+ break;
+
+ case MDPAREN:
+ {
+ static const char let_cmd[] = {
+ CHAR, 'l', CHAR, 'e',
+ CHAR, 't', EOS
+ };
+ /* Leave KEYWORD in syniocf (allow if (( 1 )) then ...) */
+ t = newtp(TCOM);
+ t->lineno = source->line;
+ ACCEPT;
+ XPput(args, wdcopy(let_cmd, ATEMP));
+ musthave(LWORD,LETEXPR);
+ XPput(args, yylval.cp);
+ break;
+ }
+
+ case DBRACKET: /* [[ .. ]] */
+ /* Leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */
+ t = newtp(TDBRACKET);
+ ACCEPT;
+ {
+ Test_env te;
+
+ te.flags = TEF_DBRACKET;
+ te.pos.av = &args;
+ te.isa = dbtestp_isa;
+ te.getopnd = dbtestp_getopnd;
+ te.eval = dbtestp_eval;
+ te.error = dbtestp_error;
+
+ test_parse(&te);
+ }
+ break;
+
+ case FOR:
+ case SELECT:
+ t = newtp((c == FOR) ? TFOR : TSELECT);
+ musthave(LWORD, ARRAYVAR);
+ if (!is_wdvarname(yylval.cp, true))
+ yyerror("%s: bad identifier\n",
+ c == FOR ? "for" : "select");
+ t->str = str_save(ident, ATEMP);
+ nesting_push(&old_nesting, c);
+ t->vars = wordlist();
+ t->left = dogroup();
+ nesting_pop(&old_nesting);
+ break;
+
+ case WHILE:
+ case UNTIL:
+ nesting_push(&old_nesting, c);
+ t = newtp((c == WHILE) ? TWHILE : TUNTIL);
+ t->left = c_list(true);
+ t->right = dogroup();
+ nesting_pop(&old_nesting);
+ break;
+
+ case CASE:
+ t = newtp(TCASE);
+ musthave(LWORD, 0);
+ t->str = yylval.cp;
+ nesting_push(&old_nesting, c);
+ t->left = caselist();
+ nesting_pop(&old_nesting);
+ break;
+
+ case IF:
+ nesting_push(&old_nesting, c);
+ t = newtp(TIF);
+ t->left = c_list(true);
+ t->right = thenpart();
+ musthave(FI, KEYWORD|ALIAS);
+ nesting_pop(&old_nesting);
+ break;
+
+ case BANG:
+ syniocf &= ~(KEYWORD|ALIAS);
+ t = pipeline(0);
+ if (t == (struct op *) 0)
+ syntaxerr((char *) 0);
+ t = block(TBANG, NOBLOCK, t, NOWORDS);
+ break;
+
+ case TIME:
+ syniocf &= ~(KEYWORD|ALIAS);
+ t = pipeline(0);
+ if (t) {
+ t->str = alloc(2, ATEMP);
+ t->str[0] = '\0'; /* TF_* flags */
+ t->str[1] = '\0';
+ }
+ t = block(TTIME, t, NOBLOCK, NOWORDS);
+ break;
+
+ case FUNCTION:
+ musthave(LWORD, 0);
+ t = function_body(yylval.cp, true);
+ break;
+ }
+
+ while ((iop = synio(syniocf)) != NULL) {
+ if (iopn >= NUFILE)
+ yyerror("too many redirections\n");
+ iops[iopn++] = iop;
+ }
+
+ if (iopn == 0) {
+ afree((void*) iops, ATEMP);
+ t->ioact = NULL;
+ } else {
+ iops[iopn++] = NULL;
+ iops = (struct ioword **) aresize((void*) iops,
+ sizeofN(struct ioword *, iopn), ATEMP);
+ t->ioact = iops;
+ }
+
+ if (t->type == TCOM || t->type == TDBRACKET) {
+ XPput(args, NULL);
+ t->args = (char **) XPclose(args);
+ XPput(vars, NULL);
+ t->vars = (char **) XPclose(vars);
+ } else {
+ XPfree(args);
+ XPfree(vars);
+ }
+
+ return t;
+}
+
+static struct op *
+dogroup(void)
+{
+ int c;
+ struct op *list;
+
+ c = token(CONTIN|KEYWORD|ALIAS);
+ /* A {...} can be used instead of do...done for for/select loops
+ * but not for while/until loops - we don't need to check if it
+ * is a while loop because it would have been parsed as part of
+ * the conditional command list...
+ */
+ if (c == DO)
+ c = DONE;
+ else if (c == '{')
+ c = '}';
+ else
+ syntaxerr((char *) 0);
+ list = c_list(true);
+ musthave(c, KEYWORD|ALIAS);
+ return list;
+}
+
+static struct op *
+thenpart(void)
+{
+ struct op *t;
+
+ musthave(THEN, KEYWORD|ALIAS);
+ t = newtp(0);
+ t->left = c_list(true);
+ if (t->left == NULL)
+ syntaxerr((char *) 0);
+ t->right = elsepart();
+ return (t);
+}
+
+static struct op *
+elsepart(void)
+{
+ struct op *t;
+
+ switch (token(KEYWORD|ALIAS|VARASN)) {
+ case ELSE:
+ if ((t = c_list(true)) == NULL)
+ syntaxerr((char *) 0);
+ return (t);
+
+ case ELIF:
+ t = newtp(TELIF);
+ t->left = c_list(true);
+ t->right = thenpart();
+ return (t);
+
+ default:
+ REJECT;
+ }
+ return NULL;
+}
+
+static struct op *
+caselist(void)
+{
+ struct op *t, *tl;
+ int c;
+
+ c = token(CONTIN|KEYWORD|ALIAS);
+ /* A {...} can be used instead of in...esac for case statements */
+ if (c == IN)
+ c = ESAC;
+ else if (c == '{')
+ c = '}';
+ else
+ syntaxerr((char *) 0);
+ t = tl = NULL;
+ while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) { /* no ALIAS here */
+ struct op *tc = casepart(c);
+ if (tl == NULL)
+ t = tl = tc, tl->right = NULL;
+ else
+ tl->right = tc, tl = tc;
+ }
+ musthave(c, KEYWORD|ALIAS);
+ return (t);
+}
+
+static struct op *
+casepart(int endtok)
+{
+ struct op *t;
+ int c;
+ XPtrV ptns;
+
+ XPinit(ptns, 16);
+ t = newtp(TPAT);
+ c = token(CONTIN|KEYWORD); /* no ALIAS here */
+ if (c != '(')
+ REJECT;
+ do {
+ musthave(LWORD, 0);
+ XPput(ptns, yylval.cp);
+ } while ((c = token(0)) == '|');
+ REJECT;
+ XPput(ptns, NULL);
+ t->vars = (char **) XPclose(ptns);
+ musthave(')', 0);
+
+ t->left = c_list(true);
+ /* Note: Posix requires the ;; */
+ if ((tpeek(CONTIN|KEYWORD|ALIAS)) != endtok)
+ musthave(BREAK, CONTIN|KEYWORD|ALIAS);
+ return (t);
+}
+
+static struct op *
+function_body(char *name,
+ int ksh_func) /* function foo { ... } vs foo() { .. } */
+{
+ char *sname, *p;
+ struct op *t;
+ int old_func_parse;
+
+ sname = wdstrip(name);
+ /* Check for valid characters in name. posix and ksh93 say only
+ * allow [a-zA-Z_0-9] but this allows more as old pdksh's have
+ * allowed more (the following were never allowed:
+ * nul space nl tab $ ' " \ ` ( ) & | ; = < >
+ * C_QUOTE covers all but = and adds # [ ? *)
+ */
+ for (p = sname; *p; p++)
+ if (ctype(*p, C_QUOTE) || *p == '=')
+ yyerror("%s: invalid function name\n", sname);
+
+ t = newtp(TFUNCT);
+ t->str = sname;
+ t->u.ksh_func = ksh_func;
+ t->lineno = source->line;
+
+ /* Note that POSIX allows only compound statements after foo(), sh and
+ * at&t ksh allow any command, go with the later since it shouldn't
+ * break anything. However, for function foo, at&t ksh only accepts
+ * an open-brace.
+ */
+ if (ksh_func) {
+ musthave('{', CONTIN|KEYWORD|ALIAS); /* } */
+ REJECT;
+ }
+
+ old_func_parse = e->flags & EF_FUNC_PARSE;
+ e->flags |= EF_FUNC_PARSE;
+ if ((t->left = get_command(CONTIN)) == (struct op *) 0) {
+ /*
+ * Probably something like foo() followed by eof or ;.
+ * This is accepted by sh and ksh88.
+ * To make "typeset -f foo" work reliably (so its output can
+ * be used as input), we pretend there is a colon here.
+ */
+ t->left = newtp(TCOM);
+ t->left->args = (char **) alloc(sizeof(char *) * 2, ATEMP);
+ t->left->args[0] = alloc(sizeof(char) * 3, ATEMP);
+ t->left->args[0][0] = CHAR;
+ t->left->args[0][1] = ':';
+ t->left->args[0][2] = EOS;
+ t->left->args[1] = (char *) 0;
+ t->left->vars = (char **) alloc(sizeof(char *), ATEMP);
+ t->left->vars[0] = (char *) 0;
+ t->left->lineno = 1;
+ }
+ if (!old_func_parse)
+ e->flags &= ~EF_FUNC_PARSE;
+
+ return t;
+}
+
+static char **
+wordlist(void)
+{
+ int c;
+ XPtrV args;
+
+ XPinit(args, 16);
+ /* Posix does not do alias expansion here... */
+ if ((c = token(CONTIN|KEYWORD|ALIAS)) != IN) {
+ if (c != ';') /* non-POSIX, but at&t ksh accepts a ; here */
+ REJECT;
+ return NULL;
+ }
+ while ((c = token(0)) == LWORD)
+ XPput(args, yylval.cp);
+ if (c != '\n' && c != ';')
+ syntaxerr((char *) 0);
+ XPput(args, NULL);
+ return (char **) XPclose(args);
+}
+
+/*
+ * supporting functions
+ */
+
+static struct op *
+block(int type, struct op *t1, struct op *t2, char **wp)
+{
+ struct op *t;
+
+ t = newtp(type);
+ t->left = t1;
+ t->right = t2;
+ t->vars = wp;
+ return (t);
+}
+
+const struct tokeninfo {
+ const char *name;
+ short val;
+ short reserved;
+} tokentab[] = {
+ /* Reserved words */
+ { "if", IF, true },
+ { "then", THEN, true },
+ { "else", ELSE, true },
+ { "elif", ELIF, true },
+ { "fi", FI, true },
+ { "case", CASE, true },
+ { "esac", ESAC, true },
+ { "for", FOR, true },
+ { "select", SELECT, true },
+ { "while", WHILE, true },
+ { "until", UNTIL, true },
+ { "do", DO, true },
+ { "done", DONE, true },
+ { "in", IN, true },
+ { "function", FUNCTION, true },
+ { "time", TIME, true },
+ { "{", '{', true },
+ { "}", '}', true },
+ { "!", BANG, true },
+ { "[[", DBRACKET, true },
+ /* Lexical tokens (0[EOF], LWORD and REDIR handled specially) */
+ { "&&", LOGAND, false },
+ { "||", LOGOR, false },
+ { ";;", BREAK, false },
+ { "((", MDPAREN, false },
+ { "|&", COPROC, false },
+ /* and some special cases... */
+ { "newline", '\n', false },
+ { 0 }
+};
+
+void
+initkeywords(void)
+{
+ struct tokeninfo const *tt;
+ struct tbl *p;
+
+ ktinit(&keywords, APERM, 32); /* must be 2^n (currently 20 keywords) */
+ for (tt = tokentab; tt->name; tt++) {
+ if (tt->reserved) {
+ p = ktenter(&keywords, tt->name, hash(tt->name));
+ p->flag |= DEFINED|ISSET;
+ p->type = CKEYWD;
+ p->val.i = tt->val;
+ }
+ }
+}
+
+static void
+syntaxerr(const char *what)
+{
+ char redir[6]; /* 2<<- is the longest redirection, I think */
+ const char *s;
+ struct tokeninfo const *tt;
+ int c;
+
+ if (!what)
+ what = "unexpected";
+ REJECT;
+ c = token(0);
+ Again:
+ switch (c) {
+ case 0:
+ if (nesting.start_token) {
+ c = nesting.start_token;
+ source->errline = nesting.start_line;
+ what = "unmatched";
+ goto Again;
+ }
+ /* don't quote the EOF */
+ yyerror("syntax error: unexpected EOF\n");
+ /* NOTREACHED */
+
+ case LWORD:
+ s = snptreef((char *) 0, 32, "%S", yylval.cp);
+ break;
+
+ case REDIR:
+ s = snptreef(redir, sizeof(redir), "%R", yylval.iop);
+ break;
+
+ default:
+ for (tt = tokentab; tt->name; tt++)
+ if (tt->val == c)
+ break;
+ if (tt->name)
+ s = tt->name;
+ else {
+ if (c > 0 && c < 256) {
+ redir[0] = c;
+ redir[1] = '\0';
+ } else
+ shf_snprintf(redir, sizeof(redir),
+ "?%d", c);
+ s = redir;
+ }
+ }
+ yyerror("syntax error: `%s' %s\n", s, what);
+}
+
+static void
+nesting_push(struct nesting_state *save, int tok)
+{
+ *save = nesting;
+ nesting.start_token = tok;
+ nesting.start_line = source->line;
+}
+
+static void
+nesting_pop(struct nesting_state *saved)
+{
+ nesting = *saved;
+}
+
+static struct op *
+newtp(int type)
+{
+ struct op *t;
+
+ t = (struct op *) alloc(sizeof(*t), ATEMP);
+ t->type = type;
+ t->u.evalflags = 0;
+ t->args = t->vars = NULL;
+ t->ioact = NULL;
+ t->left = t->right = NULL;
+ t->str = NULL;
+ return (t);
+}
+
+struct op *
+compile(Source *s)
+{
+ nesting.start_token = 0;
+ nesting.start_line = 0;
+ herep = heres;
+ source = s;
+ yyparse();
+ return outtree;
+}
+
+/* This kludge exists to take care of sh/at&t ksh oddity in which
+ * the arguments of alias/export/readonly/typeset have no field
+ * splitting, file globbing, or (normal) tilde expansion done.
+ * at&t ksh seems to do something similar to this since
+ * $ touch a=a; typeset a=[ab]; echo "$a"
+ * a=[ab]
+ * $ x=typeset; $x a=[ab]; echo "$a"
+ * a=a
+ * $
+ */
+static int
+assign_command(char *s)
+{
+ if (Flag(FPOSIX) || !*s)
+ return 0;
+ return (strcmp(s, "alias") == 0) ||
+ (strcmp(s, "export") == 0) ||
+ (strcmp(s, "readonly") == 0) ||
+ (strcmp(s, "typeset") == 0);
+}
+
+/* Check if we are in the middle of reading an alias */
+static int
+inalias(struct source *s)
+{
+ for (; s && s->type == SALIAS; s = s->next)
+ if (!(s->flags & SF_ALIASEND))
+ return 1;
+ return 0;
+}
+
+
+/* Order important - indexed by Test_meta values
+ * Note that ||, &&, ( and ) can't appear in as unquoted strings
+ * in normal shell input, so these can be interpreted unambiguously
+ * in the evaluation pass.
+ */
+static const char dbtest_or[] = { CHAR, '|', CHAR, '|', EOS };
+static const char dbtest_and[] = { CHAR, '&', CHAR, '&', EOS };
+static const char dbtest_not[] = { CHAR, '!', EOS };
+static const char dbtest_oparen[] = { CHAR, '(', EOS };
+static const char dbtest_cparen[] = { CHAR, ')', EOS };
+const char *const dbtest_tokens[] = {
+ dbtest_or, dbtest_and, dbtest_not,
+ dbtest_oparen, dbtest_cparen
+};
+const char db_close[] = { CHAR, ']', CHAR, ']', EOS };
+const char db_lthan[] = { CHAR, '<', EOS };
+const char db_gthan[] = { CHAR, '>', EOS };
+
+/* Test if the current token is a whatever. Accepts the current token if
+ * it is. Returns 0 if it is not, non-zero if it is (in the case of
+ * TM_UNOP and TM_BINOP, the returned value is a Test_op).
+ */
+static int
+dbtestp_isa(Test_env *te, Test_meta meta)
+{
+ int c = tpeek(ARRAYVAR | (meta == TM_BINOP ? 0 : CONTIN));
+ int uqword = 0;
+ char *save = (char *) 0;
+ int ret = 0;
+
+ /* unquoted word? */
+ uqword = c == LWORD && *ident;
+
+ if (meta == TM_OR)
+ ret = c == LOGOR;
+ else if (meta == TM_AND)
+ ret = c == LOGAND;
+ else if (meta == TM_NOT)
+ ret = uqword && strcmp(yylval.cp, dbtest_tokens[(int) TM_NOT]) == 0;
+ else if (meta == TM_OPAREN)
+ ret = c == '(' /*)*/;
+ else if (meta == TM_CPAREN)
+ ret = c == /*(*/ ')';
+ else if (meta == TM_UNOP || meta == TM_BINOP) {
+ if (meta == TM_BINOP && c == REDIR &&
+ (yylval.iop->flag == IOREAD || yylval.iop->flag == IOWRITE)) {
+ ret = 1;
+ save = wdcopy(yylval.iop->flag == IOREAD ?
+ db_lthan : db_gthan, ATEMP);
+ } else if (uqword && (ret = (int) test_isop(te, meta, ident)))
+ save = yylval.cp;
+ } else /* meta == TM_END */
+ ret = uqword && strcmp(yylval.cp, db_close) == 0;
+ if (ret) {
+ ACCEPT;
+ if (meta != TM_END) {
+ if (!save)
+ save = wdcopy(dbtest_tokens[(int) meta], ATEMP);
+ XPput(*te->pos.av, save);
+ }
+ }
+ return ret;
+}
+
+static const char *
+dbtestp_getopnd(Test_env *te, Test_op op, int do_eval)
+{
+ int c = tpeek(ARRAYVAR);
+
+ if (c != LWORD)
+ return (const char *) 0;
+
+ ACCEPT;
+ XPput(*te->pos.av, yylval.cp);
+
+ return null;
+}
+
+static int
+dbtestp_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2,
+ int do_eval)
+{
+ return 1;
+}
+
+static void
+dbtestp_error(Test_env *te, int offset, const char *msg)
+{
+ te->flags |= TEF_ERROR;
+
+ if (offset < 0) {
+ REJECT;
+ /* Kludgy to say the least... */
+ symbol = LWORD;
+ yylval.cp = *(XPptrv(*te->pos.av) + XPsize(*te->pos.av) +
+ offset);
+ }
+ syntaxerr(msg);
+}
diff --git a/table.c b/table.c
@@ -0,0 +1,231 @@
+/* $OpenBSD: table.c,v 1.15 2012/02/19 07:52:30 otto Exp $ */
+
+/*
+ * dynamic hashed associative table for commands and variables
+ */
+
+#include "sh.h"
+
+#define INIT_TBLS 8 /* initial table size (power of 2) */
+
+static void texpand(struct table *, int);
+static int tnamecmp(const void *, const void *);
+
+
+unsigned int
+hash(const char *n)
+{
+ unsigned int h = 0;
+
+ while (*n != '\0')
+ h = 33*h + (unsigned char)(*n++);
+ return h;
+}
+
+void
+ktinit(struct table *tp, Area *ap, int tsize)
+{
+ tp->areap = ap;
+ tp->tbls = NULL;
+ tp->size = tp->nfree = 0;
+ if (tsize)
+ texpand(tp, tsize);
+}
+
+static void
+texpand(struct table *tp, int nsize)
+{
+ int i;
+ struct tbl *tblp, **p;
+ struct tbl **ntblp, **otblp = tp->tbls;
+ int osize = tp->size;
+
+ ntblp = (struct tbl**) alloc(sizeofN(struct tbl *, nsize), tp->areap);
+ for (i = 0; i < nsize; i++)
+ ntblp[i] = NULL;
+ tp->size = nsize;
+ tp->nfree = 7*nsize/10; /* table can get 70% full */
+ tp->tbls = ntblp;
+ if (otblp == NULL)
+ return;
+ for (i = 0; i < osize; i++)
+ if ((tblp = otblp[i]) != NULL) {
+ if ((tblp->flag&DEFINED)) {
+ for (p = &ntblp[hash(tblp->name) &
+ (tp->size-1)]; *p != NULL; p--)
+ if (p == ntblp) /* wrap */
+ p += tp->size;
+ *p = tblp;
+ tp->nfree--;
+ } else if (!(tblp->flag & FINUSE)) {
+ afree((void*)tblp, tp->areap);
+ }
+ }
+ afree((void*)otblp, tp->areap);
+}
+
+/* table */
+/* name to enter */
+/* hash(n) */
+struct tbl *
+ktsearch(struct table *tp, const char *n, unsigned int h)
+{
+ struct tbl **pp, *p;
+
+ if (tp->size == 0)
+ return NULL;
+
+ /* search for name in hashed table */
+ for (pp = &tp->tbls[h & (tp->size-1)]; (p = *pp) != NULL; pp--) {
+ if (*p->name == *n && strcmp(p->name, n) == 0 &&
+ (p->flag&DEFINED))
+ return p;
+ if (pp == tp->tbls) /* wrap */
+ pp += tp->size;
+ }
+
+ return NULL;
+}
+
+/* table */
+/* name to enter */
+/* hash(n) */
+struct tbl *
+ktenter(struct table *tp, const char *n, unsigned int h)
+{
+ struct tbl **pp, *p;
+ int len;
+
+ if (tp->size == 0)
+ texpand(tp, INIT_TBLS);
+ Search:
+ /* search for name in hashed table */
+ for (pp = &tp->tbls[h & (tp->size-1)]; (p = *pp) != NULL; pp--) {
+ if (*p->name == *n && strcmp(p->name, n) == 0)
+ return p; /* found */
+ if (pp == tp->tbls) /* wrap */
+ pp += tp->size;
+ }
+
+ if (tp->nfree <= 0) { /* too full */
+ if (tp->size <= INT_MAX/2)
+ texpand(tp, 2*tp->size);
+ else
+ internal_errorf(1, "too many vars");
+ goto Search;
+ }
+
+ /* create new tbl entry */
+ len = strlen(n) + 1;
+ p = (struct tbl *) alloc(offsetof(struct tbl, name[0]) + len,
+ tp->areap);
+ p->flag = 0;
+ p->type = 0;
+ p->areap = tp->areap;
+ p->u2.field = 0;
+ p->u.array = (struct tbl *)0;
+ memcpy(p->name, n, len);
+
+ /* enter in tp->tbls */
+ tp->nfree--;
+ *pp = p;
+ return p;
+}
+
+void
+ktdelete(struct tbl *p)
+{
+ p->flag = 0;
+}
+
+void
+ktwalk(struct tstate *ts, struct table *tp)
+{
+ ts->left = tp->size;
+ ts->next = tp->tbls;
+}
+
+struct tbl *
+ktnext(struct tstate *ts)
+{
+ while (--ts->left >= 0) {
+ struct tbl *p = *ts->next++;
+ if (p != NULL && (p->flag&DEFINED))
+ return p;
+ }
+ return NULL;
+}
+
+static int
+tnamecmp(const void *p1, const void *p2)
+{
+ char *name1 = (*(struct tbl **)p1)->name;
+ char *name2 = (*(struct tbl **)p2)->name;
+ return strcmp(name1, name2);
+}
+
+struct tbl **
+ktsort(struct table *tp)
+{
+ int i;
+ struct tbl **p, **sp, **dp;
+
+ p = (struct tbl **)alloc(sizeofN(struct tbl *, tp->size+1), ATEMP);
+ sp = tp->tbls; /* source */
+ dp = p; /* dest */
+ for (i = 0; i < tp->size; i++)
+ if ((*dp = *sp++) != NULL && (((*dp)->flag&DEFINED) ||
+ ((*dp)->flag&ARRAY)))
+ dp++;
+ i = dp - p;
+ qsortp((void**)p, (size_t)i, tnamecmp);
+ p[i] = NULL;
+ return p;
+}
+
+#ifdef PERF_DEBUG /* performance debugging */
+
+void tprintinfo(struct table *tp);
+
+void
+tprintinfo(struct table *tp)
+{
+ struct tbl *te;
+ char *n;
+ unsigned int h;
+ int ncmp;
+ int totncmp = 0, maxncmp = 0;
+ int nentries = 0;
+ struct tstate ts;
+
+ shellf("table size %d, nfree %d\n", tp->size, tp->nfree);
+ shellf(" Ncmp name\n");
+ ktwalk(&ts, tp);
+ while ((te = ktnext(&ts))) {
+ struct tbl **pp, *p;
+
+ h = hash(n = te->name);
+ ncmp = 0;
+
+ /* taken from ktsearch() and added counter */
+ for (pp = &tp->tbls[h & (tp->size-1)]; (p = *pp); pp--) {
+ ncmp++;
+ if (*p->name == *n && strcmp(p->name, n) == 0 &&
+ (p->flag&DEFINED))
+ break; /* return p; */
+ if (pp == tp->tbls) /* wrap */
+ pp += tp->size;
+ }
+ shellf(" %4d %s\n", ncmp, n);
+ totncmp += ncmp;
+ nentries++;
+ if (ncmp > maxncmp)
+ maxncmp = ncmp;
+ }
+ if (nentries)
+ shellf(" %d entries, worst ncmp %d, avg ncmp %d.%02d\n",
+ nentries, maxncmp,
+ totncmp / nentries,
+ (totncmp % nentries) * 100 / nentries);
+}
+#endif /* PERF_DEBUG */
diff --git a/table.h b/table.h
@@ -0,0 +1,183 @@
+/* $OpenBSD: table.h,v 1.8 2012/02/19 07:52:30 otto Exp $ */
+
+/* $From: table.h,v 1.3 1994/05/31 13:34:34 michael Exp $ */
+
+/*
+ * generic hashed associative table for commands and variables.
+ */
+
+struct table {
+ Area *areap; /* area to allocate entries */
+ int size, nfree; /* hash size (always 2^^n), free entries */
+ struct tbl **tbls; /* hashed table items */
+};
+
+struct tbl { /* table item */
+ Tflag flag; /* flags */
+ int type; /* command type (see below), base (if INTEGER),
+ * or offset from val.s of value (if EXPORT) */
+ Area *areap; /* area to allocate from */
+ union {
+ char *s; /* string */
+ long i; /* integer */
+ int (*f)(char **); /* int function */
+ struct op *t; /* "function" tree */
+ } val; /* value */
+ int index; /* index for an array */
+ union {
+ int field; /* field with for -L/-R/-Z */
+ int errno_; /* CEXEC/CTALIAS */
+ } u2;
+ union {
+ struct tbl *array; /* array values */
+ char *fpath; /* temporary path to undef function */
+ } u;
+ char name[4]; /* name -- variable length */
+};
+
+/* common flag bits */
+#define ALLOC BIT(0) /* val.s has been allocated */
+#define DEFINED BIT(1) /* is defined in block */
+#define ISSET BIT(2) /* has value, vp->val.[si] */
+#define EXPORT BIT(3) /* exported variable/function */
+#define TRACE BIT(4) /* var: user flagged, func: execution tracing */
+/* (start non-common flags at 8) */
+/* flag bits used for variables */
+#define SPECIAL BIT(8) /* PATH, IFS, SECONDS, etc */
+#define INTEGER BIT(9) /* val.i contains integer value */
+#define RDONLY BIT(10) /* read-only variable */
+#define LOCAL BIT(11) /* for local typeset() */
+#define ARRAY BIT(13) /* array */
+#define LJUST BIT(14) /* left justify */
+#define RJUST BIT(15) /* right justify */
+#define ZEROFIL BIT(16) /* 0 filled if RJUSTIFY, strip 0s if LJUSTIFY */
+#define LCASEV BIT(17) /* convert to lower case */
+#define UCASEV_AL BIT(18)/* convert to upper case / autoload function */
+#define INT_U BIT(19) /* unsigned integer */
+#define INT_L BIT(20) /* long integer (no-op) */
+#define IMPORT BIT(21) /* flag to typeset(): no arrays, must have = */
+#define LOCAL_COPY BIT(22) /* with LOCAL - copy attrs from existing var */
+#define EXPRINEVAL BIT(23) /* contents currently being evaluated */
+#define EXPRLVALUE BIT(24) /* useable as lvalue (temp flag) */
+/* flag bits used for taliases/builtins/aliases/keywords/functions */
+#define KEEPASN BIT(8) /* keep command assignments (eg, var=x cmd) */
+#define FINUSE BIT(9) /* function being executed */
+#define FDELETE BIT(10) /* function deleted while it was executing */
+#define FKSH BIT(11) /* function defined with function x (vs x()) */
+#define SPEC_BI BIT(12) /* a POSIX special builtin */
+#define REG_BI BIT(13) /* a POSIX regular builtin */
+/* Attributes that can be set by the user (used to decide if an unset param
+ * should be repoted by set/typeset). Does not include ARRAY or LOCAL.
+ */
+#define USERATTRIB (EXPORT|INTEGER|RDONLY|LJUST|RJUST|ZEROFIL\
+ |LCASEV|UCASEV_AL|INT_U|INT_L)
+
+/* command types */
+#define CNONE 0 /* undefined */
+#define CSHELL 1 /* built-in */
+#define CFUNC 2 /* function */
+#define CEXEC 4 /* executable command */
+#define CALIAS 5 /* alias */
+#define CKEYWD 6 /* keyword */
+#define CTALIAS 7 /* tracked alias */
+
+/* Flags for findcom()/comexec() */
+#define FC_SPECBI BIT(0) /* special builtin */
+#define FC_FUNC BIT(1) /* function builtin */
+#define FC_REGBI BIT(2) /* regular builtin */
+#define FC_UNREGBI BIT(3) /* un-regular builtin (!special,!regular) */
+#define FC_BI (FC_SPECBI|FC_REGBI|FC_UNREGBI)
+#define FC_PATH BIT(4) /* do path search */
+#define FC_DEFPATH BIT(5) /* use default path in path search */
+
+
+#define AF_ARGV_ALLOC 0x1 /* argv[] array allocated */
+#define AF_ARGS_ALLOCED 0x2 /* argument strings allocated */
+#define AI_ARGV(a, i) ((i) == 0 ? (a).argv[0] : (a).argv[(i) - (a).skip])
+#define AI_ARGC(a) ((a).argc_ - (a).skip)
+
+/* Argument info. Used for $#, $* for shell, functions, includes, etc. */
+struct arg_info {
+ int flags; /* AF_* */
+ char **argv;
+ int argc_;
+ int skip; /* first arg is argv[0], second is argv[1 + skip] */
+};
+
+/*
+ * activation record for function blocks
+ */
+struct block {
+ Area area; /* area to allocate things */
+ /*struct arg_info argi;*/
+ char **argv;
+ int argc;
+ int flags; /* see BF_* */
+ struct table vars; /* local variables */
+ struct table funs; /* local functions */
+ Getopt getopts_state;
+#if 1
+ char * error; /* error handler */
+ char * exit; /* exit handler */
+#else
+ Trap error, exit;
+#endif
+ struct block *next; /* enclosing block */
+};
+
+/* Values for struct block.flags */
+#define BF_DOGETOPTS BIT(0) /* save/restore getopts state */
+
+/*
+ * Used by ktwalk() and ktnext() routines.
+ */
+struct tstate {
+ int left;
+ struct tbl **next;
+};
+
+
+EXTERN struct table taliases; /* tracked aliases */
+EXTERN struct table builtins; /* built-in commands */
+EXTERN struct table aliases; /* aliases */
+EXTERN struct table keywords; /* keywords */
+EXTERN struct table homedirs; /* homedir() cache */
+
+struct builtin {
+ const char *name;
+ int (*func)(char **);
+};
+
+/* these really are externs! Look in table.c for them */
+extern const struct builtin shbuiltins [], kshbuiltins [];
+
+/* var spec values */
+#define V_NONE 0
+#define V_PATH 1
+#define V_IFS 2
+#define V_SECONDS 3
+#define V_OPTIND 4
+#define V_MAIL 5
+#define V_MAILPATH 6
+#define V_MAILCHECK 7
+#define V_RANDOM 8
+#define V_HISTSIZE 9
+#define V_HISTFILE 10
+#define V_VISUAL 11
+#define V_EDITOR 12
+#define V_COLUMNS 13
+#define V_POSIXLY_CORRECT 14
+#define V_TMOUT 15
+#define V_TMPDIR 16
+#define V_LINENO 17
+
+/* values for set_prompt() */
+#define PS1 0 /* command */
+#define PS2 1 /* command continuation */
+
+EXTERN char *path; /* copy of either PATH or def_path */
+EXTERN const char *def_path; /* path to use if PATH not set */
+EXTERN char *tmpdir; /* TMPDIR value */
+EXTERN const char *prompt;
+EXTERN int cur_prompt; /* PS1 or PS2 */
+EXTERN int current_lineno; /* LINENO value */
diff --git a/trap.c b/trap.c
@@ -0,0 +1,420 @@
+/* $OpenBSD: trap.c,v 1.23 2010/05/19 17:36:08 jasper Exp $ */
+
+/*
+ * signal handling
+ */
+
+#include "sh.h"
+
+Trap sigtraps[NSIG + 1];
+
+static struct sigaction Sigact_ign, Sigact_trap;
+
+void
+inittraps(void)
+{
+ int i;
+
+ /* Populate sigtraps based on sys_signame and sys_siglist. */
+ for (i = 0; i <= NSIG; i++) {
+ sigtraps[i].signal = i;
+ if (i == SIGERR_) {
+ sigtraps[i].name = "ERR";
+ sigtraps[i].mess = "Error handler";
+ } else {
+ sigtraps[i].name = sys_signame[i];
+ sigtraps[i].mess = sys_siglist[i];
+ }
+ }
+ sigtraps[SIGEXIT_].name = "EXIT"; /* our name for signal 0 */
+
+ sigemptyset(&Sigact_ign.sa_mask);
+ Sigact_ign.sa_flags = 0; /* interruptible */
+ Sigact_ign.sa_handler = SIG_IGN;
+ Sigact_trap = Sigact_ign;
+ Sigact_trap.sa_handler = trapsig;
+
+ sigtraps[SIGINT].flags |= TF_DFL_INTR | TF_TTY_INTR;
+ sigtraps[SIGQUIT].flags |= TF_DFL_INTR | TF_TTY_INTR;
+ sigtraps[SIGTERM].flags |= TF_DFL_INTR;/* not fatal for interactive */
+ sigtraps[SIGHUP].flags |= TF_FATAL;
+ sigtraps[SIGCHLD].flags |= TF_SHELL_USES;
+
+ /* these are always caught so we can clean up any temporary files. */
+ setsig(&sigtraps[SIGINT], trapsig, SS_RESTORE_ORIG);
+ setsig(&sigtraps[SIGQUIT], trapsig, SS_RESTORE_ORIG);
+ setsig(&sigtraps[SIGTERM], trapsig, SS_RESTORE_ORIG);
+ setsig(&sigtraps[SIGHUP], trapsig, SS_RESTORE_ORIG);
+}
+
+static void alarm_catcher(int sig);
+
+void
+alarm_init(void)
+{
+ sigtraps[SIGALRM].flags |= TF_SHELL_USES;
+ setsig(&sigtraps[SIGALRM], alarm_catcher,
+ SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP);
+}
+
+/* ARGSUSED */
+static void
+alarm_catcher(int sig)
+{
+ int errno_ = errno;
+
+ if (ksh_tmout_state == TMOUT_READING) {
+ int left = alarm(0);
+
+ if (left == 0) {
+ ksh_tmout_state = TMOUT_LEAVING;
+ intrsig = 1;
+ } else
+ alarm(left);
+ }
+ errno = errno_;
+}
+
+Trap *
+gettrap(const char *name, int igncase)
+{
+ int i;
+ Trap *p;
+
+ if (digit(*name)) {
+ int n;
+
+ if (getn(name, &n) && 0 <= n && n < NSIG)
+ return &sigtraps[n];
+ return NULL;
+ }
+ for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
+ if (p->name) {
+ if (igncase) {
+ if (p->name && (!strcasecmp(p->name, name) ||
+ (strlen(name) > 3 && !strncasecmp("SIG",
+ p->name, 3) &&
+ !strcasecmp(p->name, name + 3))))
+ return p;
+ } else {
+ if (p->name && (!strcmp(p->name, name) ||
+ (strlen(name) > 3 && !strncmp("SIG",
+ p->name, 3) && !strcmp(p->name, name + 3))))
+ return p;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * trap signal handler
+ */
+void
+trapsig(int i)
+{
+ Trap *p = &sigtraps[i];
+ int errno_ = errno;
+
+ trap = p->set = 1;
+ if (p->flags & TF_DFL_INTR)
+ intrsig = 1;
+ if ((p->flags & TF_FATAL) && !p->trap) {
+ fatal_trap = 1;
+ intrsig = 1;
+ }
+ if (p->shtrap)
+ (*p->shtrap)(i);
+ errno = errno_;
+}
+
+/* called when we want to allow the user to ^C out of something - won't
+ * work if user has trapped SIGINT.
+ */
+void
+intrcheck(void)
+{
+ if (intrsig)
+ runtraps(TF_DFL_INTR|TF_FATAL);
+}
+
+/* called after EINTR to check if a signal with normally causes process
+ * termination has been received.
+ */
+int
+fatal_trap_check(void)
+{
+ int i;
+ Trap *p;
+
+ /* todo: should check if signal is fatal, not the TF_DFL_INTR flag */
+ for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
+ if (p->set && (p->flags & (TF_DFL_INTR|TF_FATAL)))
+ /* return value is used as an exit code */
+ return 128 + p->signal;
+ return 0;
+}
+
+/* Returns the signal number of any pending traps: ie, a signal which has
+ * occurred for which a trap has been set or for which the TF_DFL_INTR flag
+ * is set.
+ */
+int
+trap_pending(void)
+{
+ int i;
+ Trap *p;
+
+ for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
+ if (p->set && ((p->trap && p->trap[0]) ||
+ ((p->flags & (TF_DFL_INTR|TF_FATAL)) && !p->trap)))
+ return p->signal;
+ return 0;
+}
+
+/*
+ * run any pending traps. If intr is set, only run traps that
+ * can interrupt commands.
+ */
+void
+runtraps(int flag)
+{
+ int i;
+ Trap *p;
+
+ if (ksh_tmout_state == TMOUT_LEAVING) {
+ ksh_tmout_state = TMOUT_EXECUTING;
+ warningf(false, "timed out waiting for input");
+ unwind(LEXIT);
+ } else
+ /* XXX: this means the alarm will have no effect if a trap
+ * is caught after the alarm() was started...not good.
+ */
+ ksh_tmout_state = TMOUT_EXECUTING;
+ if (!flag)
+ trap = 0;
+ if (flag & TF_DFL_INTR)
+ intrsig = 0;
+ if (flag & TF_FATAL)
+ fatal_trap = 0;
+ for (p = sigtraps, i = NSIG+1; --i >= 0; p++)
+ if (p->set && (!flag ||
+ ((p->flags & flag) && p->trap == (char *) 0)))
+ runtrap(p);
+}
+
+void
+runtrap(Trap *p)
+{
+ int i = p->signal;
+ char *trapstr = p->trap;
+ int oexstat;
+ int old_changed = 0;
+
+ p->set = 0;
+ if (trapstr == (char *) 0) { /* SIG_DFL */
+ if (p->flags & TF_FATAL) {
+ /* eg, SIGHUP */
+ exstat = 128 + i;
+ unwind(LLEAVE);
+ }
+ if (p->flags & TF_DFL_INTR) {
+ /* eg, SIGINT, SIGQUIT, SIGTERM, etc. */
+ exstat = 128 + i;
+ unwind(LINTR);
+ }
+ return;
+ }
+ if (trapstr[0] == '\0') /* SIG_IGN */
+ return;
+ if (i == SIGEXIT_ || i == SIGERR_) { /* avoid recursion on these */
+ old_changed = p->flags & TF_CHANGED;
+ p->flags &= ~TF_CHANGED;
+ p->trap = (char *) 0;
+ }
+ oexstat = exstat;
+ /* Note: trapstr is fully parsed before anything is executed, thus
+ * no problem with afree(p->trap) in settrap() while still in use.
+ */
+ command(trapstr, current_lineno);
+ exstat = oexstat;
+ if (i == SIGEXIT_ || i == SIGERR_) {
+ if (p->flags & TF_CHANGED)
+ /* don't clear TF_CHANGED */
+ afree(trapstr, APERM);
+ else
+ p->trap = trapstr;
+ p->flags |= old_changed;
+ }
+}
+
+/* clear pending traps and reset user's trap handlers; used after fork(2) */
+void
+cleartraps(void)
+{
+ int i;
+ Trap *p;
+
+ trap = 0;
+ intrsig = 0;
+ fatal_trap = 0;
+ for (i = NSIG+1, p = sigtraps; --i >= 0; p++) {
+ p->set = 0;
+ if ((p->flags & TF_USER_SET) && (p->trap && p->trap[0]))
+ settrap(p, (char *) 0);
+ }
+}
+
+/* restore signals just before an exec(2) */
+void
+restoresigs(void)
+{
+ int i;
+ Trap *p;
+
+ for (i = NSIG+1, p = sigtraps; --i >= 0; p++)
+ if (p->flags & (TF_EXEC_IGN|TF_EXEC_DFL))
+ setsig(p, (p->flags & TF_EXEC_IGN) ? SIG_IGN : SIG_DFL,
+ SS_RESTORE_CURR|SS_FORCE);
+}
+
+void
+settrap(Trap *p, char *s)
+{
+ sig_t f;
+
+ if (p->trap)
+ afree(p->trap, APERM);
+ p->trap = str_save(s, APERM); /* handles s == 0 */
+ p->flags |= TF_CHANGED;
+ f = !s ? SIG_DFL : s[0] ? trapsig : SIG_IGN;
+
+ p->flags |= TF_USER_SET;
+ if ((p->flags & (TF_DFL_INTR|TF_FATAL)) && f == SIG_DFL)
+ f = trapsig;
+ else if (p->flags & TF_SHELL_USES) {
+ if (!(p->flags & TF_ORIG_IGN) || Flag(FTALKING)) {
+ /* do what user wants at exec time */
+ p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
+ if (f == SIG_IGN)
+ p->flags |= TF_EXEC_IGN;
+ else
+ p->flags |= TF_EXEC_DFL;
+ }
+
+ /* assumes handler already set to what shell wants it
+ * (normally trapsig, but could be j_sigchld() or SIG_IGN)
+ */
+ return;
+ }
+
+ /* todo: should we let user know signal is ignored? how? */
+ setsig(p, f, SS_RESTORE_CURR|SS_USER);
+}
+
+/* Called by c_print() when writing to a co-process to ensure SIGPIPE won't
+ * kill shell (unless user catches it and exits)
+ */
+int
+block_pipe(void)
+{
+ int restore_dfl = 0;
+ Trap *p = &sigtraps[SIGPIPE];
+
+ if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
+ setsig(p, SIG_IGN, SS_RESTORE_CURR);
+ if (p->flags & TF_ORIG_DFL)
+ restore_dfl = 1;
+ } else if (p->cursig == SIG_DFL) {
+ setsig(p, SIG_IGN, SS_RESTORE_CURR);
+ restore_dfl = 1; /* restore to SIG_DFL */
+ }
+ return restore_dfl;
+}
+
+/* Called by c_print() to undo whatever block_pipe() did */
+void
+restore_pipe(int restore_dfl)
+{
+ if (restore_dfl)
+ setsig(&sigtraps[SIGPIPE], SIG_DFL, SS_RESTORE_CURR);
+}
+
+/* Set action for a signal. Action may not be set if original
+ * action was SIG_IGN, depending on the value of flags and
+ * FTALKING.
+ */
+int
+setsig(Trap *p, sig_t f, int flags)
+{
+ struct sigaction sigact;
+
+ if (p->signal == SIGEXIT_ || p->signal == SIGERR_)
+ return 1;
+
+ /* First time setting this signal? If so, get and note the current
+ * setting.
+ */
+ if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) {
+ sigaction(p->signal, &Sigact_ign, &sigact);
+ p->flags |= sigact.sa_handler == SIG_IGN ?
+ TF_ORIG_IGN : TF_ORIG_DFL;
+ p->cursig = SIG_IGN;
+ }
+
+ /* Generally, an ignored signal stays ignored, except if
+ * - the user of an interactive shell wants to change it
+ * - the shell wants for force a change
+ */
+ if ((p->flags & TF_ORIG_IGN) && !(flags & SS_FORCE) &&
+ (!(flags & SS_USER) || !Flag(FTALKING)))
+ return 0;
+
+ setexecsig(p, flags & SS_RESTORE_MASK);
+
+ /* This is here 'cause there should be a way of clearing shtraps, but
+ * don't know if this is a sane way of doing it. At the moment,
+ * all users of shtrap are lifetime users (SIGCHLD, SIGALRM, SIGWINCH).
+ */
+ if (!(flags & SS_USER))
+ p->shtrap = NULL;
+ if (flags & SS_SHTRAP) {
+ p->shtrap = f;
+ f = trapsig;
+ }
+
+ if (p->cursig != f) {
+ p->cursig = f;
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0 /* interruptible */;
+ sigact.sa_handler = f;
+ sigaction(p->signal, &sigact, (struct sigaction *) 0);
+ }
+
+ return 1;
+}
+
+/* control what signal is set to before an exec() */
+void
+setexecsig(Trap *p, int restore)
+{
+ /* XXX debugging */
+ if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL)))
+ internal_errorf(1, "setexecsig: unset signal %d(%s)",
+ p->signal, p->name);
+
+ /* restore original value for exec'd kids */
+ p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL);
+ switch (restore & SS_RESTORE_MASK) {
+ case SS_RESTORE_CURR: /* leave things as they currently are */
+ break;
+ case SS_RESTORE_ORIG:
+ p->flags |= p->flags & TF_ORIG_IGN ? TF_EXEC_IGN : TF_EXEC_DFL;
+ break;
+ case SS_RESTORE_DFL:
+ p->flags |= TF_EXEC_DFL;
+ break;
+ case SS_RESTORE_IGN:
+ p->flags |= TF_EXEC_IGN;
+ break;
+ }
+}
diff --git a/tree.c b/tree.c
@@ -0,0 +1,708 @@
+/* $OpenBSD: tree.c,v 1.20 2012/06/27 07:17:19 otto Exp $ */
+
+/*
+ * command tree climbing
+ */
+
+#include "sh.h"
+
+#define INDENT 4
+
+#define tputc(c, shf) shf_putchar(c, shf);
+static void ptree(struct op *, int, struct shf *);
+static void pioact(struct shf *, int, struct ioword *);
+static void tputC(int, struct shf *);
+static void tputS(char *, struct shf *);
+static void vfptreef(struct shf *, int, const char *, va_list);
+static struct ioword **iocopy(struct ioword **, Area *);
+static void iofree(struct ioword **, Area *);
+
+/*
+ * print a command tree
+ */
+
+static void
+ptree(struct op *t, int indent, struct shf *shf)
+{
+ char **w;
+ struct ioword **ioact;
+ struct op *t1;
+
+ Chain:
+ if (t == NULL)
+ return;
+ switch (t->type) {
+ case TCOM:
+ if (t->vars)
+ for (w = t->vars; *w != NULL; )
+ fptreef(shf, indent, "%S ", *w++);
+ else
+ fptreef(shf, indent, "#no-vars# ");
+ if (t->args)
+ for (w = t->args; *w != NULL; )
+ fptreef(shf, indent, "%S ", *w++);
+ else
+ fptreef(shf, indent, "#no-args# ");
+ break;
+ case TEXEC:
+#if 0 /* ?not useful - can't be called? */
+ /* Print original vars */
+ if (t->left->vars)
+ for (w = t->left->vars; *w != NULL; )
+ fptreef(shf, indent, "%S ", *w++);
+ else
+ fptreef(shf, indent, "#no-vars# ");
+ /* Print expanded vars */
+ if (t->args)
+ for (w = t->args; *w != NULL; )
+ fptreef(shf, indent, "%s ", *w++);
+ else
+ fptreef(shf, indent, "#no-args# ");
+ /* Print original io */
+ t = t->left;
+#else
+ t = t->left;
+ goto Chain;
+#endif
+ case TPAREN:
+ fptreef(shf, indent + 2, "( %T) ", t->left);
+ break;
+ case TPIPE:
+ fptreef(shf, indent, "%T| ", t->left);
+ t = t->right;
+ goto Chain;
+ case TLIST:
+ fptreef(shf, indent, "%T%;", t->left);
+ t = t->right;
+ goto Chain;
+ case TOR:
+ case TAND:
+ fptreef(shf, indent, "%T%s %T",
+ t->left, (t->type==TOR) ? "||" : "&&", t->right);
+ break;
+ case TBANG:
+ fptreef(shf, indent, "! ");
+ t = t->right;
+ goto Chain;
+ case TDBRACKET:
+ {
+ int i;
+
+ fptreef(shf, indent, "[[");
+ for (i = 0; t->args[i]; i++)
+ fptreef(shf, indent, " %S", t->args[i]);
+ fptreef(shf, indent, " ]] ");
+ break;
+ }
+ case TSELECT:
+ fptreef(shf, indent, "select %s ", t->str);
+ /* FALLTHROUGH */
+ case TFOR:
+ if (t->type == TFOR)
+ fptreef(shf, indent, "for %s ", t->str);
+ if (t->vars != NULL) {
+ fptreef(shf, indent, "in ");
+ for (w = t->vars; *w; )
+ fptreef(shf, indent, "%S ", *w++);
+ fptreef(shf, indent, "%;");
+ }
+ fptreef(shf, indent + INDENT, "do%N%T", t->left);
+ fptreef(shf, indent, "%;done ");
+ break;
+ case TCASE:
+ fptreef(shf, indent, "case %S in", t->str);
+ for (t1 = t->left; t1 != NULL; t1 = t1->right) {
+ fptreef(shf, indent, "%N(");
+ for (w = t1->vars; *w != NULL; w++)
+ fptreef(shf, indent, "%S%c", *w,
+ (w[1] != NULL) ? '|' : ')');
+ fptreef(shf, indent + INDENT, "%;%T%N;;", t1->left);
+ }
+ fptreef(shf, indent, "%Nesac ");
+ break;
+ case TIF:
+ case TELIF:
+ /* 3 == strlen("if ") */
+ fptreef(shf, indent + 3, "if %T", t->left);
+ for (;;) {
+ t = t->right;
+ if (t->left != NULL) {
+ fptreef(shf, indent, "%;");
+ fptreef(shf, indent + INDENT, "then%N%T",
+ t->left);
+ }
+ if (t->right == NULL || t->right->type != TELIF)
+ break;
+ t = t->right;
+ fptreef(shf, indent, "%;");
+ /* 5 == strlen("elif ") */
+ fptreef(shf, indent + 5, "elif %T", t->left);
+ }
+ if (t->right != NULL) {
+ fptreef(shf, indent, "%;");
+ fptreef(shf, indent + INDENT, "else%;%T", t->right);
+ }
+ fptreef(shf, indent, "%;fi ");
+ break;
+ case TWHILE:
+ case TUNTIL:
+ /* 6 == strlen("while"/"until") */
+ fptreef(shf, indent + 6, "%s %T",
+ (t->type==TWHILE) ? "while" : "until",
+ t->left);
+ fptreef(shf, indent, "%;do");
+ fptreef(shf, indent + INDENT, "%;%T", t->right);
+ fptreef(shf, indent, "%;done ");
+ break;
+ case TBRACE:
+ fptreef(shf, indent + INDENT, "{%;%T", t->left);
+ fptreef(shf, indent, "%;} ");
+ break;
+ case TCOPROC:
+ fptreef(shf, indent, "%T|& ", t->left);
+ break;
+ case TASYNC:
+ fptreef(shf, indent, "%T& ", t->left);
+ break;
+ case TFUNCT:
+ fptreef(shf, indent,
+ t->u.ksh_func ? "function %s %T" : "%s() %T",
+ t->str, t->left);
+ break;
+ case TTIME:
+ fptreef(shf, indent, "time %T", t->left);
+ break;
+ default:
+ fptreef(shf, indent, "<botch>");
+ break;
+ }
+ if ((ioact = t->ioact) != NULL) {
+ int need_nl = 0;
+
+ while (*ioact != NULL)
+ pioact(shf, indent, *ioact++);
+ /* Print here documents after everything else... */
+ for (ioact = t->ioact; *ioact != NULL; ) {
+ struct ioword *iop = *ioact++;
+
+ /* heredoc is 0 when tracing (set -x) */
+ if ((iop->flag & IOTYPE) == IOHERE && iop->heredoc) {
+ tputc('\n', shf);
+ shf_puts(iop->heredoc, shf);
+ fptreef(shf, indent, "%s",
+ evalstr(iop->delim, 0));
+ need_nl = 1;
+ }
+ }
+ /* Last delimiter must be followed by a newline (this often
+ * leads to an extra blank line, but its not worth worrying
+ * about)
+ */
+ if (need_nl)
+ tputc('\n', shf);
+ }
+}
+
+static void
+pioact(struct shf *shf, int indent, struct ioword *iop)
+{
+ int flag = iop->flag;
+ int type = flag & IOTYPE;
+ int expected;
+
+ expected = (type == IOREAD || type == IORDWR || type == IOHERE) ? 0 :
+ (type == IOCAT || type == IOWRITE) ? 1 :
+ (type == IODUP && (iop->unit == !(flag & IORDUP))) ? iop->unit :
+ iop->unit + 1;
+ if (iop->unit != expected)
+ tputc('0' + iop->unit, shf);
+
+ switch (type) {
+ case IOREAD:
+ fptreef(shf, indent, "< ");
+ break;
+ case IOHERE:
+ if (flag&IOSKIP)
+ fptreef(shf, indent, "<<- ");
+ else
+ fptreef(shf, indent, "<< ");
+ break;
+ case IOCAT:
+ fptreef(shf, indent, ">> ");
+ break;
+ case IOWRITE:
+ if (flag&IOCLOB)
+ fptreef(shf, indent, ">| ");
+ else
+ fptreef(shf, indent, "> ");
+ break;
+ case IORDWR:
+ fptreef(shf, indent, "<> ");
+ break;
+ case IODUP:
+ if (flag & IORDUP)
+ fptreef(shf, indent, "<&");
+ else
+ fptreef(shf, indent, ">&");
+ break;
+ }
+ /* name/delim are 0 when printing syntax errors */
+ if (type == IOHERE) {
+ if (iop->delim)
+ fptreef(shf, indent, "%S ", iop->delim);
+ } else if (iop->name)
+ fptreef(shf, indent, (iop->flag & IONAMEXP) ? "%s " : "%S ",
+ iop->name);
+}
+
+
+/*
+ * variants of fputc, fputs for ptreef and snptreef
+ */
+
+static void
+tputC(int c, struct shf *shf)
+{
+ if ((c&0x60) == 0) { /* C0|C1 */
+ tputc((c&0x80) ? '$' : '^', shf);
+ tputc(((c&0x7F)|0x40), shf);
+ } else if ((c&0x7F) == 0x7F) { /* DEL */
+ tputc((c&0x80) ? '$' : '^', shf);
+ tputc('?', shf);
+ } else
+ tputc(c, shf);
+}
+
+static void
+tputS(char *wp, struct shf *shf)
+{
+ int c, quoted=0;
+
+ /* problems:
+ * `...` -> $(...)
+ * 'foo' -> "foo"
+ * could change encoding to:
+ * OQUOTE ["'] ... CQUOTE ["']
+ * COMSUB [(`] ...\0 (handle $ ` \ and maybe " in `...` case)
+ */
+ while (1)
+ switch ((c = *wp++)) {
+ case EOS:
+ return;
+ case CHAR:
+ tputC(*wp++, shf);
+ break;
+ case QCHAR:
+ c = *wp++;
+ if (!quoted || (c == '"' || c == '`' || c == '$'))
+ tputc('\\', shf);
+ tputC(c, shf);
+ break;
+ case COMSUB:
+ tputc('$', shf);
+ tputc('(', shf);
+ while (*wp != 0)
+ tputC(*wp++, shf);
+ tputc(')', shf);
+ wp++;
+ break;
+ case EXPRSUB:
+ tputc('$', shf);
+ tputc('(', shf);
+ tputc('(', shf);
+ while (*wp != 0)
+ tputC(*wp++, shf);
+ tputc(')', shf);
+ tputc(')', shf);
+ wp++;
+ break;
+ case OQUOTE:
+ quoted = 1;
+ tputc('"', shf);
+ break;
+ case CQUOTE:
+ quoted = 0;
+ tputc('"', shf);
+ break;
+ case OSUBST:
+ tputc('$', shf);
+ if (*wp++ == '{')
+ tputc('{', shf);
+ while ((c = *wp++) != 0)
+ tputC(c, shf);
+ break;
+ case CSUBST:
+ if (*wp++ == '}')
+ tputc('}', shf);
+ break;
+ case OPAT:
+ tputc(*wp++, shf);
+ tputc('(', shf);
+ break;
+ case SPAT:
+ tputc('|', shf);
+ break;
+ case CPAT:
+ tputc(')', shf);
+ break;
+ }
+}
+
+/*
+ * this is the _only_ way to reliably handle
+ * variable args with an ANSI compiler
+ */
+/* VARARGS */
+void
+fptreef(struct shf *shf, int indent, const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ vfptreef(shf, indent, fmt, va);
+ va_end(va);
+}
+
+/* VARARGS */
+char *
+snptreef(char *s, int n, const char *fmt, ...)
+{
+ va_list va;
+ struct shf shf;
+
+ shf_sopen(s, n, SHF_WR | (s ? 0 : SHF_DYNAMIC), &shf);
+
+ va_start(va, fmt);
+ vfptreef(&shf, 0, fmt, va);
+ va_end(va);
+
+ return shf_sclose(&shf); /* null terminates */
+}
+
+static void
+vfptreef(struct shf *shf, int indent, const char *fmt, va_list va)
+{
+ int c;
+
+ while ((c = *fmt++)) {
+ if (c == '%') {
+ long n;
+ char *p;
+ int neg;
+
+ switch ((c = *fmt++)) {
+ case 'c':
+ tputc(va_arg(va, int), shf);
+ break;
+ case 's':
+ p = va_arg(va, char *);
+ while (*p)
+ tputc(*p++, shf);
+ break;
+ case 'S': /* word */
+ p = va_arg(va, char *);
+ tputS(p, shf);
+ break;
+ case 'd': case 'u': /* decimal */
+ n = (c == 'd') ? va_arg(va, int) :
+ va_arg(va, unsigned int);
+ neg = c=='d' && n<0;
+ p = ulton((neg) ? -n : n, 10);
+ if (neg)
+ *--p = '-';
+ while (*p)
+ tputc(*p++, shf);
+ break;
+ case 'T': /* format tree */
+ ptree(va_arg(va, struct op *), indent, shf);
+ break;
+ case ';': /* newline or ; */
+ case 'N': /* newline or space */
+ if (shf->flags & SHF_STRING) {
+ if (c == ';')
+ tputc(';', shf);
+ tputc(' ', shf);
+ } else {
+ int i;
+
+ tputc('\n', shf);
+ for (i = indent; i >= 8; i -= 8)
+ tputc('\t', shf);
+ for (; i > 0; --i)
+ tputc(' ', shf);
+ }
+ break;
+ case 'R':
+ pioact(shf, indent, va_arg(va, struct ioword *));
+ break;
+ default:
+ tputc(c, shf);
+ break;
+ }
+ } else
+ tputc(c, shf);
+ }
+}
+
+/*
+ * copy tree (for function definition)
+ */
+
+struct op *
+tcopy(struct op *t, Area *ap)
+{
+ struct op *r;
+ char **tw, **rw;
+
+ if (t == NULL)
+ return NULL;
+
+ r = (struct op *) alloc(sizeof(struct op), ap);
+
+ r->type = t->type;
+ r->u.evalflags = t->u.evalflags;
+
+ r->str = t->type == TCASE ? wdcopy(t->str, ap) : str_save(t->str, ap);
+
+ if (t->vars == NULL)
+ r->vars = NULL;
+ else {
+ for (tw = t->vars; *tw++ != NULL; )
+ ;
+ rw = r->vars = (char **)
+ alloc((tw - t->vars + 1) * sizeof(*tw), ap);
+ for (tw = t->vars; *tw != NULL; )
+ *rw++ = wdcopy(*tw++, ap);
+ *rw = NULL;
+ }
+
+ if (t->args == NULL)
+ r->args = NULL;
+ else {
+ for (tw = t->args; *tw++ != NULL; )
+ ;
+ rw = r->args = (char **)
+ alloc((tw - t->args + 1) * sizeof(*tw), ap);
+ for (tw = t->args; *tw != NULL; )
+ *rw++ = wdcopy(*tw++, ap);
+ *rw = NULL;
+ }
+
+ r->ioact = (t->ioact == NULL) ? NULL : iocopy(t->ioact, ap);
+
+ r->left = tcopy(t->left, ap);
+ r->right = tcopy(t->right, ap);
+ r->lineno = t->lineno;
+
+ return r;
+}
+
+char *
+wdcopy(const char *wp, Area *ap)
+{
+ size_t len = wdscan(wp, EOS) - wp;
+ return memcpy(alloc(len, ap), wp, len);
+}
+
+/* return the position of prefix c in wp plus 1 */
+char *
+wdscan(const char *wp, int c)
+{
+ int nest = 0;
+
+ while (1)
+ switch (*wp++) {
+ case EOS:
+ return (char *) wp;
+ case CHAR:
+ case QCHAR:
+ wp++;
+ break;
+ case COMSUB:
+ case EXPRSUB:
+ while (*wp++ != 0)
+ ;
+ break;
+ case OQUOTE:
+ case CQUOTE:
+ break;
+ case OSUBST:
+ nest++;
+ while (*wp++ != '\0')
+ ;
+ break;
+ case CSUBST:
+ wp++;
+ if (c == CSUBST && nest == 0)
+ return (char *) wp;
+ nest--;
+ break;
+ case OPAT:
+ nest++;
+ wp++;
+ break;
+ case SPAT:
+ case CPAT:
+ if (c == wp[-1] && nest == 0)
+ return (char *) wp;
+ if (wp[-1] == CPAT)
+ nest--;
+ break;
+ default:
+ internal_errorf(0,
+ "wdscan: unknown char 0x%x (carrying on)",
+ wp[-1]);
+ }
+}
+
+/* return a copy of wp without any of the mark up characters and
+ * with quote characters (" ' \) stripped.
+ * (string is allocated from ATEMP)
+ */
+char *
+wdstrip(const char *wp)
+{
+ struct shf shf;
+ int c;
+
+ shf_sopen((char *) 0, 32, SHF_WR | SHF_DYNAMIC, &shf);
+
+ /* problems:
+ * `...` -> $(...)
+ * x${foo:-"hi"} -> x${foo:-hi}
+ * x${foo:-'hi'} -> x${foo:-hi}
+ */
+ while (1)
+ switch ((c = *wp++)) {
+ case EOS:
+ return shf_sclose(&shf); /* null terminates */
+ case CHAR:
+ case QCHAR:
+ shf_putchar(*wp++, &shf);
+ break;
+ case COMSUB:
+ shf_putchar('$', &shf);
+ shf_putchar('(', &shf);
+ while (*wp != 0)
+ shf_putchar(*wp++, &shf);
+ shf_putchar(')', &shf);
+ break;
+ case EXPRSUB:
+ shf_putchar('$', &shf);
+ shf_putchar('(', &shf);
+ shf_putchar('(', &shf);
+ while (*wp != 0)
+ shf_putchar(*wp++, &shf);
+ shf_putchar(')', &shf);
+ shf_putchar(')', &shf);
+ break;
+ case OQUOTE:
+ break;
+ case CQUOTE:
+ break;
+ case OSUBST:
+ shf_putchar('$', &shf);
+ if (*wp++ == '{')
+ shf_putchar('{', &shf);
+ while ((c = *wp++) != 0)
+ shf_putchar(c, &shf);
+ break;
+ case CSUBST:
+ if (*wp++ == '}')
+ shf_putchar('}', &shf);
+ break;
+ case OPAT:
+ shf_putchar(*wp++, &shf);
+ shf_putchar('(', &shf);
+ break;
+ case SPAT:
+ shf_putchar('|', &shf);
+ break;
+ case CPAT:
+ shf_putchar(')', &shf);
+ break;
+ }
+}
+
+static struct ioword **
+iocopy(struct ioword **iow, Area *ap)
+{
+ struct ioword **ior;
+ int i;
+
+ for (ior = iow; *ior++ != NULL; )
+ ;
+ ior = (struct ioword **) alloc((ior - iow + 1) * sizeof(*ior), ap);
+
+ for (i = 0; iow[i] != NULL; i++) {
+ struct ioword *p, *q;
+
+ p = iow[i];
+ q = (struct ioword *) alloc(sizeof(*p), ap);
+ ior[i] = q;
+ *q = *p;
+ if (p->name != (char *) 0)
+ q->name = wdcopy(p->name, ap);
+ if (p->delim != (char *) 0)
+ q->delim = wdcopy(p->delim, ap);
+ if (p->heredoc != (char *) 0)
+ q->heredoc = str_save(p->heredoc, ap);
+ }
+ ior[i] = NULL;
+
+ return ior;
+}
+
+/*
+ * free tree (for function definition)
+ */
+
+void
+tfree(struct op *t, Area *ap)
+{
+ char **w;
+
+ if (t == NULL)
+ return;
+
+ if (t->str != NULL)
+ afree((void*)t->str, ap);
+
+ if (t->vars != NULL) {
+ for (w = t->vars; *w != NULL; w++)
+ afree((void*)*w, ap);
+ afree((void*)t->vars, ap);
+ }
+
+ if (t->args != NULL) {
+ for (w = t->args; *w != NULL; w++)
+ afree((void*)*w, ap);
+ afree((void*)t->args, ap);
+ }
+
+ if (t->ioact != NULL)
+ iofree(t->ioact, ap);
+
+ tfree(t->left, ap);
+ tfree(t->right, ap);
+
+ afree((void*)t, ap);
+}
+
+static void
+iofree(struct ioword **iow, Area *ap)
+{
+ struct ioword **iop;
+ struct ioword *p;
+
+ for (iop = iow; (p = *iop++) != NULL; ) {
+ if (p->name != NULL)
+ afree((void*)p->name, ap);
+ if (p->delim != NULL)
+ afree((void*)p->delim, ap);
+ if (p->heredoc != NULL)
+ afree((void*)p->heredoc, ap);
+ afree((void*)p, ap);
+ }
+ afree(iow, ap);
+}
diff --git a/tree.h b/tree.h
@@ -0,0 +1,141 @@
+/* $OpenBSD: tree.h,v 1.10 2005/03/28 21:28:22 deraadt Exp $ */
+
+/*
+ * command trees for compile/execute
+ */
+
+/* $From: tree.h,v 1.3 1994/05/31 13:34:34 michael Exp $ */
+
+#define NOBLOCK ((struct op *)NULL)
+#define NOWORD ((char *)NULL)
+#define NOWORDS ((char **)NULL)
+
+/*
+ * Description of a command or an operation on commands.
+ */
+struct op {
+ short type; /* operation type, see below */
+ union { /* WARNING: newtp(), tcopy() use evalflags = 0 to clear union */
+ short evalflags; /* TCOM: arg expansion eval() flags */
+ short ksh_func; /* TFUNC: function x (vs x()) */
+ } u;
+ char **args; /* arguments to a command */
+ char **vars; /* variable assignments */
+ struct ioword **ioact; /* IO actions (eg, < > >>) */
+ struct op *left, *right; /* descendents */
+ char *str; /* word for case; identifier for for,
+ * select, and functions;
+ * path to execute for TEXEC;
+ * time hook for TCOM.
+ */
+ int lineno; /* TCOM/TFUNC: LINENO for this */
+};
+
+/* Tree.type values */
+#define TEOF 0
+#define TCOM 1 /* command */
+#define TPAREN 2 /* (c-list) */
+#define TPIPE 3 /* a | b */
+#define TLIST 4 /* a ; b */
+#define TOR 5 /* || */
+#define TAND 6 /* && */
+#define TBANG 7 /* ! */
+#define TDBRACKET 8 /* [[ .. ]] */
+#define TFOR 9
+#define TSELECT 10
+#define TCASE 11
+#define TIF 12
+#define TWHILE 13
+#define TUNTIL 14
+#define TELIF 15
+#define TPAT 16 /* pattern in case */
+#define TBRACE 17 /* {c-list} */
+#define TASYNC 18 /* c & */
+#define TFUNCT 19 /* function name { command; } */
+#define TTIME 20 /* time pipeline */
+#define TEXEC 21 /* fork/exec eval'd TCOM */
+#define TCOPROC 22 /* coprocess |& */
+
+/*
+ * prefix codes for words in command tree
+ */
+#define EOS 0 /* end of string */
+#define CHAR 1 /* unquoted character */
+#define QCHAR 2 /* quoted character */
+#define COMSUB 3 /* $() substitution (0 terminated) */
+#define EXPRSUB 4 /* $(()) substitution (0 terminated) */
+#define OQUOTE 5 /* opening " or ' */
+#define CQUOTE 6 /* closing " or ' */
+#define OSUBST 7 /* opening ${ subst (followed by { or X) */
+#define CSUBST 8 /* closing } of above (followed by } or X) */
+#define OPAT 9 /* open pattern: *(, @(, etc. */
+#define SPAT 10 /* separate pattern: | */
+#define CPAT 11 /* close pattern: ) */
+
+/*
+ * IO redirection
+ */
+struct ioword {
+ int unit; /* unit affected */
+ int flag; /* action (below) */
+ char *name; /* file name (unused if heredoc) */
+ char *delim; /* delimiter for <<,<<- */
+ char *heredoc;/* content of heredoc */
+};
+
+/* ioword.flag - type of redirection */
+#define IOTYPE 0xF /* type: bits 0:3 */
+#define IOREAD 0x1 /* < */
+#define IOWRITE 0x2 /* > */
+#define IORDWR 0x3 /* <>: todo */
+#define IOHERE 0x4 /* << (here file) */
+#define IOCAT 0x5 /* >> */
+#define IODUP 0x6 /* <&/>& */
+#define IOEVAL BIT(4) /* expand in << */
+#define IOSKIP BIT(5) /* <<-, skip ^\t* */
+#define IOCLOB BIT(6) /* >|, override -o noclobber */
+#define IORDUP BIT(7) /* x<&y (as opposed to x>&y) */
+#define IONAMEXP BIT(8) /* name has been expanded */
+
+/* execute/exchild flags */
+#define XEXEC BIT(0) /* execute without forking */
+#define XFORK BIT(1) /* fork before executing */
+#define XBGND BIT(2) /* command & */
+#define XPIPEI BIT(3) /* input is pipe */
+#define XPIPEO BIT(4) /* output is pipe */
+#define XPIPE (XPIPEI|XPIPEO) /* member of pipe */
+#define XXCOM BIT(5) /* `...` command */
+#define XPCLOSE BIT(6) /* exchild: close close_fd in parent */
+#define XCCLOSE BIT(7) /* exchild: close close_fd in child */
+#define XERROK BIT(8) /* non-zero exit ok (for set -e) */
+#define XCOPROC BIT(9) /* starting a co-process */
+#define XTIME BIT(10) /* timing TCOM command */
+
+/*
+ * flags to control expansion of words (assumed by t->evalflags to fit
+ * in a short)
+ */
+#define DOBLANK BIT(0) /* perform blank interpretation */
+#define DOGLOB BIT(1) /* expand [?* */
+#define DOPAT BIT(2) /* quote *?[ */
+#define DOTILDE BIT(3) /* normal ~ expansion (first char) */
+#define DONTRUNCOMMAND BIT(4) /* do not run $(command) things */
+#define DOASNTILDE BIT(5) /* assignment ~ expansion (after =, :) */
+#define DOBRACE_ BIT(6) /* used by expand(): do brace expansion */
+#define DOMAGIC_ BIT(7) /* used by expand(): string contains MAGIC */
+#define DOTEMP_ BIT(8) /* ditto : in word part of ${..[%#=?]..} */
+#define DOVACHECK BIT(9) /* var assign check (for typeset, set, etc) */
+#define DOMARKDIRS BIT(10) /* force markdirs behaviour */
+
+/*
+ * The arguments of [[ .. ]] expressions are kept in t->args[] and flags
+ * indicating how the arguments have been munged are kept in t->vars[].
+ * The contents of t->vars[] are stuffed strings (so they can be treated
+ * like all other t->vars[]) in which the second character is the one that
+ * is examined. The DB_* defines are the values for these second characters.
+ */
+#define DB_NORM 1 /* normal argument */
+#define DB_OR 2 /* || -> -o conversion */
+#define DB_AND 3 /* && -> -a conversion */
+#define DB_BE 4 /* an inserted -BE */
+#define DB_PAT 5 /* a pattern argument */
diff --git a/tty.c b/tty.c
@@ -0,0 +1,57 @@
+/* $OpenBSD: tty.c,v 1.10 2014/08/10 02:44:26 guenther Exp $ */
+
+#include "sh.h"
+#include <sys/stat.h>
+#define EXTERN
+#include "tty.h"
+#undef EXTERN
+
+/* Initialize tty_fd. Used for saving/reseting tty modes upon
+ * foreground job completion and for setting up tty process group.
+ */
+void
+tty_init(int init_ttystate)
+{
+ int do_close = 1;
+ int tfd;
+
+ if (tty_fd >= 0) {
+ close(tty_fd);
+ tty_fd = -1;
+ }
+ tty_devtty = 1;
+
+ if ((tfd = open("/dev/tty", O_RDWR, 0)) < 0) {
+ tty_devtty = 0;
+ warningf(false, "No controlling tty (open /dev/tty: %s)",
+ strerror(errno));
+ }
+
+ if (tfd < 0) {
+ do_close = 0;
+ if (isatty(0))
+ tfd = 0;
+ else if (isatty(2))
+ tfd = 2;
+ else {
+ warningf(false, "Can't find tty file descriptor");
+ return;
+ }
+ }
+ if ((tty_fd = fcntl(tfd, F_DUPFD_CLOEXEC, FDBASE)) < 0) {
+ warningf(false, "j_ttyinit: dup of tty fd failed: %s",
+ strerror(errno));
+ } else if (init_ttystate)
+ tcgetattr(tty_fd, &tty_state);
+ if (do_close)
+ close(tfd);
+}
+
+void
+tty_close(void)
+{
+ if (tty_fd >= 0) {
+ close(tty_fd);
+ tty_fd = -1;
+ }
+}
diff --git a/tty.h b/tty.h
@@ -0,0 +1,37 @@
+/* $OpenBSD: tty.h,v 1.5 2004/12/20 11:34:26 otto Exp $ */
+
+/*
+ tty.h -- centralized definitions for a variety of terminal interfaces
+
+ created by DPK, Oct. 1986
+
+ Rearranged to work with autoconf, added TTY_state, get_tty/set_tty
+ Michael Rendell, May '94
+
+ last edit: 30-Jul-1987 D A Gwyn
+*/
+
+/* some useful #defines */
+#ifdef EXTERN
+# define I__(i) = i
+#else
+# define I__(i)
+# define EXTERN extern
+# define EXTERN_DEFINED
+#endif
+
+#include <termios.h>
+
+EXTERN int tty_fd I__(-1); /* dup'd tty file descriptor */
+EXTERN int tty_devtty; /* true if tty_fd is from /dev/tty */
+EXTERN struct termios tty_state; /* saved tty state */
+
+extern void tty_init(int);
+extern void tty_close(void);
+
+/* be sure not to interfere with anyone else's idea about EXTERN */
+#ifdef EXTERN_DEFINED
+# undef EXTERN_DEFINED
+# undef EXTERN
+#endif
+#undef I__
diff --git a/var.c b/var.c
@@ -0,0 +1,1205 @@
+/* $OpenBSD: var.c,v 1.40 2014/12/12 05:00:55 jsg Exp $ */
+
+#include "sh.h"
+#include <time.h>
+#include "ksh_limval.h"
+#include <sys/stat.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#ifndef srand_deterministic
+#define srand_deterministic(x) srand(x)
+#endif
+
+/*
+ * Variables
+ *
+ * WARNING: unreadable code, needs a rewrite
+ *
+ * if (flag&INTEGER), val.i contains integer value, and type contains base.
+ * otherwise, (val.s + type) contains string value.
+ * if (flag&EXPORT), val.s contains "name=value" for E-Z exporting.
+ */
+static struct tbl vtemp;
+static struct table specials;
+static char *formatstr(struct tbl *, const char *);
+static void export(struct tbl *, const char *);
+static int special(const char *);
+static void unspecial(const char *);
+static void getspec(struct tbl *);
+static void setspec(struct tbl *);
+static void unsetspec(struct tbl *);
+static struct tbl *arraysearch(struct tbl *, int);
+
+/*
+ * create a new block for function calls and simple commands
+ * assume caller has allocated and set up e->loc
+ */
+void
+newblock(void)
+{
+ struct block *l;
+ static char *const empty[] = {null};
+
+ l = (struct block *) alloc(sizeof(struct block), ATEMP);
+ l->flags = 0;
+ ainit(&l->area); /* todo: could use e->area (l->area => l->areap) */
+ if (!e->loc) {
+ l->argc = 0;
+ l->argv = (char **) empty;
+ } else {
+ l->argc = e->loc->argc;
+ l->argv = e->loc->argv;
+ }
+ l->exit = l->error = NULL;
+ ktinit(&l->vars, &l->area, 0);
+ ktinit(&l->funs, &l->area, 0);
+ l->next = e->loc;
+ e->loc = l;
+}
+
+/*
+ * pop a block handling special variables
+ */
+void
+popblock(void)
+{
+ struct block *l = e->loc;
+ struct tbl *vp, **vpp = l->vars.tbls, *vq;
+ int i;
+
+ e->loc = l->next; /* pop block */
+ for (i = l->vars.size; --i >= 0; )
+ if ((vp = *vpp++) != NULL && (vp->flag&SPECIAL)) {
+ if ((vq = global(vp->name))->flag & ISSET)
+ setspec(vq);
+ else
+ unsetspec(vq);
+ }
+ if (l->flags & BF_DOGETOPTS)
+ user_opt = l->getopts_state;
+ afreeall(&l->area);
+ afree(l, ATEMP);
+}
+
+/* called by main() to initialize variable data structures */
+void
+initvar(void)
+{
+ static const struct {
+ const char *name;
+ int v;
+ } names[] = {
+ { "COLUMNS", V_COLUMNS },
+ { "IFS", V_IFS },
+ { "OPTIND", V_OPTIND },
+ { "PATH", V_PATH },
+ { "POSIXLY_CORRECT", V_POSIXLY_CORRECT },
+ { "TMPDIR", V_TMPDIR },
+#ifdef HISTORY
+ { "HISTFILE", V_HISTFILE },
+ { "HISTSIZE", V_HISTSIZE },
+#endif /* HISTORY */
+#ifdef EDIT
+ { "EDITOR", V_EDITOR },
+ { "VISUAL", V_VISUAL },
+#endif /* EDIT */
+ { "MAIL", V_MAIL },
+ { "MAILCHECK", V_MAILCHECK },
+ { "MAILPATH", V_MAILPATH },
+ { "RANDOM", V_RANDOM },
+ { "SECONDS", V_SECONDS },
+ { "TMOUT", V_TMOUT },
+ { "LINENO", V_LINENO },
+ { (char *) 0, 0 }
+ };
+ int i;
+ struct tbl *tp;
+
+ ktinit(&specials, APERM, 32); /* must be 2^n (currently 17 specials) */
+ for (i = 0; names[i].name; i++) {
+ tp = ktenter(&specials, names[i].name, hash(names[i].name));
+ tp->flag = DEFINED|ISSET;
+ tp->type = names[i].v;
+ }
+}
+
+/* Used to calculate an array index for global()/local(). Sets *arrayp to
+ * non-zero if this is an array, sets *valp to the array index, returns
+ * the basename of the array.
+ */
+static const char *
+array_index_calc(const char *n, bool *arrayp, int *valp)
+{
+ const char *p;
+ int len;
+
+ *arrayp = false;
+ p = skip_varname(n, false);
+ if (p != n && *p == '[' && (len = array_ref_len(p))) {
+ char *sub, *tmp;
+ long rval;
+
+ /* Calculate the value of the subscript */
+ *arrayp = true;
+ tmp = str_nsave(p+1, len-2, ATEMP);
+ sub = substitute(tmp, 0);
+ afree(tmp, ATEMP);
+ n = str_nsave(n, p - n, ATEMP);
+ evaluate(sub, &rval, KSH_UNWIND_ERROR, true);
+ if (rval < 0 || rval > INT_MAX)
+ errorf("%s: subscript %ld out of range", n, rval);
+ *valp = rval;
+ afree(sub, ATEMP);
+ }
+ return n;
+}
+
+/*
+ * Search for variable, if not found create globally.
+ */
+struct tbl *
+global(const char *n)
+{
+ struct block *l = e->loc;
+ struct tbl *vp;
+ long num;
+ int c;
+ unsigned int h;
+ bool array;
+ int val;
+
+ /* Check to see if this is an array */
+ n = array_index_calc(n, &array, &val);
+ h = hash(n);
+ c = (unsigned char)n[0];
+ if (!letter(c)) {
+ if (array)
+ errorf("bad substitution");
+ vp = &vtemp;
+ vp->flag = DEFINED;
+ vp->type = 0;
+ vp->areap = ATEMP;
+ *vp->name = c;
+ if (digit(c)) {
+ errno = 0;
+ num = strtol(n, NULL, 10);
+ if (errno == 0 && num <= l->argc)
+ /* setstr can't fail here */
+ setstr(vp, l->argv[num], KSH_RETURN_ERROR);
+ vp->flag |= RDONLY;
+ return vp;
+ }
+ vp->flag |= RDONLY;
+ if (n[1] != '\0')
+ return vp;
+ vp->flag |= ISSET|INTEGER;
+ switch (c) {
+ case '$':
+ vp->val.i = kshpid;
+ break;
+ case '!':
+ /* If no job, expand to nothing */
+ if ((vp->val.i = j_async()) == 0)
+ vp->flag &= ~(ISSET|INTEGER);
+ break;
+ case '?':
+ vp->val.i = exstat;
+ break;
+ case '#':
+ vp->val.i = l->argc;
+ break;
+ case '-':
+ vp->flag &= ~INTEGER;
+ vp->val.s = getoptions();
+ break;
+ default:
+ vp->flag &= ~(ISSET|INTEGER);
+ }
+ return vp;
+ }
+ for (l = e->loc; ; l = l->next) {
+ vp = ktsearch(&l->vars, n, h);
+ if (vp != NULL) {
+ if (array)
+ return arraysearch(vp, val);
+ else
+ return vp;
+ }
+ if (l->next == NULL)
+ break;
+ }
+ vp = ktenter(&l->vars, n, h);
+ if (array)
+ vp = arraysearch(vp, val);
+ vp->flag |= DEFINED;
+ if (special(n))
+ vp->flag |= SPECIAL;
+ return vp;
+}
+
+/*
+ * Search for local variable, if not found create locally.
+ */
+struct tbl *
+local(const char *n, bool copy)
+{
+ struct block *l = e->loc;
+ struct tbl *vp;
+ unsigned int h;
+ bool array;
+ int val;
+
+ /* Check to see if this is an array */
+ n = array_index_calc(n, &array, &val);
+ h = hash(n);
+ if (!letter(*n)) {
+ vp = &vtemp;
+ vp->flag = DEFINED|RDONLY;
+ vp->type = 0;
+ vp->areap = ATEMP;
+ return vp;
+ }
+ vp = ktenter(&l->vars, n, h);
+ if (copy && !(vp->flag & DEFINED)) {
+ struct block *ll = l;
+ struct tbl *vq = (struct tbl *) 0;
+
+ while ((ll = ll->next) && !(vq = ktsearch(&ll->vars, n, h)))
+ ;
+ if (vq) {
+ vp->flag |= vq->flag &
+ (EXPORT | INTEGER | RDONLY | LJUST | RJUST |
+ ZEROFIL | LCASEV | UCASEV_AL | INT_U | INT_L);
+ if (vq->flag & INTEGER)
+ vp->type = vq->type;
+ vp->u2.field = vq->u2.field;
+ }
+ }
+ if (array)
+ vp = arraysearch(vp, val);
+ vp->flag |= DEFINED;
+ if (special(n))
+ vp->flag |= SPECIAL;
+ return vp;
+}
+
+/* get variable string value */
+char *
+str_val(struct tbl *vp)
+{
+ char *s;
+
+ if ((vp->flag&SPECIAL))
+ getspec(vp);
+ if (!(vp->flag&ISSET))
+ s = null; /* special to dollar() */
+ else if (!(vp->flag&INTEGER)) /* string source */
+ s = vp->val.s + vp->type;
+ else { /* integer source */
+ /* worst case number length is when base=2, so use BITS(long) */
+ /* minus base # number null */
+ char strbuf[1 + 2 + 1 + BITS(long) + 1];
+ const char *digits = (vp->flag & UCASEV_AL) ?
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" :
+ "0123456789abcdefghijklmnopqrstuvwxyz";
+ unsigned long n;
+ int base;
+
+ s = strbuf + sizeof(strbuf);
+ if (vp->flag & INT_U)
+ n = (unsigned long) vp->val.i;
+ else
+ n = (vp->val.i < 0) ? -vp->val.i : vp->val.i;
+ base = (vp->type == 0) ? 10 : vp->type;
+ if (base < 2 || base > strlen(digits))
+ base = 10;
+
+ *--s = '\0';
+ do {
+ *--s = digits[n % base];
+ n /= base;
+ } while (n != 0);
+ if (base != 10) {
+ *--s = '#';
+ *--s = digits[base % 10];
+ if (base >= 10)
+ *--s = digits[base / 10];
+ }
+ if (!(vp->flag & INT_U) && vp->val.i < 0)
+ *--s = '-';
+ if (vp->flag & (RJUST|LJUST)) /* case already dealt with */
+ s = formatstr(vp, s);
+ else
+ s = str_save(s, ATEMP);
+ }
+ return s;
+}
+
+/* get variable integer value, with error checking */
+long
+intval(struct tbl *vp)
+{
+ long num;
+ int base;
+
+ base = getint(vp, &num, false);
+ if (base == -1)
+ /* XXX check calls - is error here ok by POSIX? */
+ errorf("%s: bad number", str_val(vp));
+ return num;
+}
+
+/* set variable to string value */
+int
+setstr(struct tbl *vq, const char *s, int error_ok)
+{
+ const char *fs = NULL;
+ int no_ro_check = error_ok & 0x4;
+ error_ok &= ~0x4;
+ if ((vq->flag & RDONLY) && !no_ro_check) {
+ warningf(true, "%s: is read only", vq->name);
+ if (!error_ok)
+ errorf(null);
+ return 0;
+ }
+ if (!(vq->flag&INTEGER)) { /* string dest */
+ if ((vq->flag&ALLOC)) {
+ /* debugging */
+ if (s >= vq->val.s &&
+ s <= vq->val.s + strlen(vq->val.s))
+ internal_errorf(true,
+ "setstr: %s=%s: assigning to self",
+ vq->name, s);
+ afree((void*)vq->val.s, vq->areap);
+ }
+ vq->flag &= ~(ISSET|ALLOC);
+ vq->type = 0;
+ if (s && (vq->flag & (UCASEV_AL|LCASEV|LJUST|RJUST)))
+ fs = s = formatstr(vq, s);
+ if ((vq->flag&EXPORT))
+ export(vq, s);
+ else {
+ vq->val.s = str_save(s, vq->areap);
+ vq->flag |= ALLOC;
+ }
+ } else { /* integer dest */
+ if (!v_evaluate(vq, s, error_ok, true))
+ return 0;
+ }
+ vq->flag |= ISSET;
+ if ((vq->flag&SPECIAL))
+ setspec(vq);
+ if (fs)
+ afree((char *)fs, ATEMP);
+ return 1;
+}
+
+/* set variable to integer */
+void
+setint(struct tbl *vq, long int n)
+{
+ if (!(vq->flag&INTEGER)) {
+ struct tbl *vp = &vtemp;
+ vp->flag = (ISSET|INTEGER);
+ vp->type = 0;
+ vp->areap = ATEMP;
+ vp->val.i = n;
+ /* setstr can't fail here */
+ setstr(vq, str_val(vp), KSH_RETURN_ERROR);
+ } else
+ vq->val.i = n;
+ vq->flag |= ISSET;
+ if ((vq->flag&SPECIAL))
+ setspec(vq);
+}
+
+int
+getint(struct tbl *vp, long int *nump, bool arith)
+{
+ char *s;
+ int c;
+ int base, neg;
+ int have_base = 0;
+ long num;
+
+ if (vp->flag&SPECIAL)
+ getspec(vp);
+ /* XXX is it possible for ISSET to be set and val.s to be 0? */
+ if (!(vp->flag&ISSET) || (!(vp->flag&INTEGER) && vp->val.s == NULL))
+ return -1;
+ if (vp->flag&INTEGER) {
+ *nump = vp->val.i;
+ return vp->type;
+ }
+ s = vp->val.s + vp->type;
+ if (s == NULL) /* redundant given initial test */
+ s = null;
+ base = 10;
+ num = 0;
+ neg = 0;
+ if (arith && *s == '0' && *(s+1)) {
+ s++;
+ if (*s == 'x' || *s == 'X') {
+ s++;
+ base = 16;
+ } else if (vp->flag & ZEROFIL) {
+ while (*s == '0')
+ s++;
+ } else
+ base = 8;
+ have_base++;
+ }
+ for (c = (unsigned char)*s++; c ; c = (unsigned char)*s++) {
+ if (c == '-') {
+ neg++;
+ } else if (c == '#') {
+ base = (int) num;
+ if (have_base || base < 2 || base > 36)
+ return -1;
+ num = 0;
+ have_base = 1;
+ } else if (letnum(c)) {
+ if (isdigit(c))
+ c -= '0';
+ else if (islower(c))
+ c -= 'a' - 10; /* todo: assumes ascii */
+ else if (isupper(c))
+ c -= 'A' - 10; /* todo: assumes ascii */
+ else
+ c = -1; /* _: force error */
+ if (c < 0 || c >= base)
+ return -1;
+ num = num * base + c;
+ } else
+ return -1;
+ }
+ if (neg)
+ num = -num;
+ *nump = num;
+ return base;
+}
+
+/* convert variable vq to integer variable, setting its value from vp
+ * (vq and vp may be the same)
+ */
+struct tbl *
+setint_v(struct tbl *vq, struct tbl *vp, bool arith)
+{
+ int base;
+ long num;
+
+ if ((base = getint(vp, &num, arith)) == -1)
+ return NULL;
+ if (!(vq->flag & INTEGER) && (vq->flag & ALLOC)) {
+ vq->flag &= ~ALLOC;
+ afree(vq->val.s, vq->areap);
+ }
+ vq->val.i = num;
+ if (vq->type == 0) /* default base */
+ vq->type = base;
+ vq->flag |= ISSET|INTEGER;
+ if (vq->flag&SPECIAL)
+ setspec(vq);
+ return vq;
+}
+
+static char *
+formatstr(struct tbl *vp, const char *s)
+{
+ int olen, nlen;
+ char *p, *q;
+
+ olen = strlen(s);
+
+ if (vp->flag & (RJUST|LJUST)) {
+ if (!vp->u2.field) /* default field width */
+ vp->u2.field = olen;
+ nlen = vp->u2.field;
+ } else
+ nlen = olen;
+
+ p = (char *) alloc(nlen + 1, ATEMP);
+ if (vp->flag & (RJUST|LJUST)) {
+ int slen;
+
+ if (vp->flag & RJUST) {
+ const char *q = s + olen;
+ /* strip trailing spaces (at&t ksh uses q[-1] == ' ') */
+ while (q > s && isspace((unsigned char)q[-1]))
+ --q;
+ slen = q - s;
+ if (slen > vp->u2.field) {
+ s += slen - vp->u2.field;
+ slen = vp->u2.field;
+ }
+ shf_snprintf(p, nlen + 1,
+ ((vp->flag & ZEROFIL) && digit(*s)) ?
+ "%0*s%.*s" : "%*s%.*s",
+ vp->u2.field - slen, null, slen, s);
+ } else {
+ /* strip leading spaces/zeros */
+ while (isspace((unsigned char)*s))
+ s++;
+ if (vp->flag & ZEROFIL)
+ while (*s == '0')
+ s++;
+ shf_snprintf(p, nlen + 1, "%-*.*s",
+ vp->u2.field, vp->u2.field, s);
+ }
+ } else
+ memcpy(p, s, olen + 1);
+
+ if (vp->flag & UCASEV_AL) {
+ for (q = p; *q; q++)
+ if (islower((unsigned char)*q))
+ *q = toupper((unsigned char)*q);
+ } else if (vp->flag & LCASEV) {
+ for (q = p; *q; q++)
+ if (isupper((unsigned char)*q))
+ *q = tolower((unsigned char)*q);
+ }
+
+ return p;
+}
+
+/*
+ * make vp->val.s be "name=value" for quick exporting.
+ */
+static void
+export(struct tbl *vp, const char *val)
+{
+ char *xp;
+ char *op = (vp->flag&ALLOC) ? vp->val.s : NULL;
+ int namelen = strlen(vp->name);
+ int vallen = strlen(val) + 1;
+
+ vp->flag |= ALLOC;
+ xp = (char*)alloc(namelen + 1 + vallen, vp->areap);
+ memcpy(vp->val.s = xp, vp->name, namelen);
+ xp += namelen;
+ *xp++ = '=';
+ vp->type = xp - vp->val.s; /* offset to value */
+ memcpy(xp, val, vallen);
+ if (op != NULL)
+ afree((void*)op, vp->areap);
+}
+
+/*
+ * lookup variable (according to (set&LOCAL)),
+ * set its attributes (INTEGER, RDONLY, EXPORT, TRACE, LJUST, RJUST, ZEROFIL,
+ * LCASEV, UCASEV_AL), and optionally set its value if an assignment.
+ */
+struct tbl *
+typeset(const char *var, Tflag set, Tflag clr, int field, int base)
+{
+ struct tbl *vp;
+ struct tbl *vpbase, *t;
+ char *tvar;
+ const char *val;
+
+ /* check for valid variable name, search for value */
+ val = skip_varname(var, false);
+ if (val == var)
+ return NULL;
+ if (*val == '[') {
+ int len;
+
+ len = array_ref_len(val);
+ if (len == 0)
+ return NULL;
+ /* IMPORT is only used when the shell starts up and is
+ * setting up its environment. Allow only simple array
+ * references at this time since parameter/command substitution
+ * is preformed on the [expression], which would be a major
+ * security hole.
+ */
+ if (set & IMPORT) {
+ int i;
+ for (i = 1; i < len - 1; i++)
+ if (!digit(val[i]))
+ return NULL;
+ }
+ val += len;
+ }
+ if (*val == '=')
+ tvar = str_nsave(var, val++ - var, ATEMP);
+ else {
+ /* Importing from original environment: must have an = */
+ if (set & IMPORT)
+ return NULL;
+ tvar = (char *) var;
+ val = NULL;
+ }
+
+ /* Prevent typeset from creating a local PATH/ENV/SHELL */
+ if (Flag(FRESTRICTED) && (strcmp(tvar, "PATH") == 0 ||
+ strcmp(tvar, "ENV") == 0 || strcmp(tvar, "SHELL") == 0))
+ errorf("%s: restricted", tvar);
+
+ vp = (set&LOCAL) ? local(tvar, (set & LOCAL_COPY) ? true : false) :
+ global(tvar);
+ set &= ~(LOCAL|LOCAL_COPY);
+
+ vpbase = (vp->flag & ARRAY) ? global(arrayname(var)) : vp;
+
+ /* only allow export flag to be set. at&t ksh allows any attribute to
+ * be changed, which means it can be truncated or modified
+ * (-L/-R/-Z/-i).
+ */
+ if ((vpbase->flag&RDONLY) &&
+ (val || clr || (set & ~EXPORT)))
+ /* XXX check calls - is error here ok by POSIX? */
+ errorf("%s: is read only", tvar);
+ if (val)
+ afree(tvar, ATEMP);
+
+ /* most calls are with set/clr == 0 */
+ if (set | clr) {
+ int ok = 1;
+ /* XXX if x[0] isn't set, there will be problems: need to have
+ * one copy of attributes for arrays...
+ */
+ for (t = vpbase; t; t = t->u.array) {
+ int fake_assign;
+ char *s = NULL;
+ char *free_me = NULL;
+
+ fake_assign = (t->flag & ISSET) && (!val || t != vp) &&
+ ((set & (UCASEV_AL|LCASEV|LJUST|RJUST|ZEROFIL)) ||
+ ((t->flag & INTEGER) && (clr & INTEGER)) ||
+ (!(t->flag & INTEGER) && (set & INTEGER)));
+ if (fake_assign) {
+ if (t->flag & INTEGER) {
+ s = str_val(t);
+ free_me = (char *) 0;
+ } else {
+ s = t->val.s + t->type;
+ free_me = (t->flag & ALLOC) ? t->val.s :
+ (char *) 0;
+ }
+ t->flag &= ~ALLOC;
+ }
+ if (!(t->flag & INTEGER) && (set & INTEGER)) {
+ t->type = 0;
+ t->flag &= ~ALLOC;
+ }
+ t->flag = (t->flag | set) & ~clr;
+ /* Don't change base if assignment is to be done,
+ * in case assignment fails.
+ */
+ if ((set & INTEGER) && base > 0 && (!val || t != vp))
+ t->type = base;
+ if (set & (LJUST|RJUST|ZEROFIL))
+ t->u2.field = field;
+ if (fake_assign) {
+ if (!setstr(t, s, KSH_RETURN_ERROR)) {
+ /* Somewhat arbitrary action here:
+ * zap contents of variable, but keep
+ * the flag settings.
+ */
+ ok = 0;
+ if (t->flag & INTEGER)
+ t->flag &= ~ISSET;
+ else {
+ if (t->flag & ALLOC)
+ afree((void*) t->val.s,
+ t->areap);
+ t->flag &= ~(ISSET|ALLOC);
+ t->type = 0;
+ }
+ }
+ if (free_me)
+ afree((void *) free_me, t->areap);
+ }
+ }
+ if (!ok)
+ errorf(null);
+ }
+
+ if (val != NULL) {
+ if (vp->flag&INTEGER) {
+ /* do not zero base before assignment */
+ setstr(vp, val, KSH_UNWIND_ERROR | 0x4);
+ /* Done after assignment to override default */
+ if (base > 0)
+ vp->type = base;
+ } else
+ /* setstr can't fail (readonly check already done) */
+ setstr(vp, val, KSH_RETURN_ERROR | 0x4);
+ }
+
+ /* only x[0] is ever exported, so use vpbase */
+ if ((vpbase->flag&EXPORT) && !(vpbase->flag&INTEGER) &&
+ vpbase->type == 0)
+ export(vpbase, (vpbase->flag&ISSET) ? vpbase->val.s : null);
+
+ return vp;
+}
+
+/* Unset a variable. array_ref is set if there was an array reference in
+ * the name lookup (eg, x[2]).
+ */
+void
+unset(struct tbl *vp, int array_ref)
+{
+ if (vp->flag & ALLOC)
+ afree((void*)vp->val.s, vp->areap);
+ if ((vp->flag & ARRAY) && !array_ref) {
+ struct tbl *a, *tmp;
+
+ /* Free up entire array */
+ for (a = vp->u.array; a; ) {
+ tmp = a;
+ a = a->u.array;
+ if (tmp->flag & ALLOC)
+ afree((void *) tmp->val.s, tmp->areap);
+ afree(tmp, tmp->areap);
+ }
+ vp->u.array = (struct tbl *) 0;
+ }
+ /* If foo[0] is being unset, the remainder of the array is kept... */
+ vp->flag &= SPECIAL | (array_ref ? ARRAY|DEFINED : 0);
+ if (vp->flag & SPECIAL)
+ unsetspec(vp); /* responsible for `unspecial'ing var */
+}
+
+/* return a pointer to the first char past a legal variable name (returns the
+ * argument if there is no legal name, returns * a pointer to the terminating
+ * null if whole string is legal).
+ */
+char *
+skip_varname(const char *s, int aok)
+{
+ int alen;
+
+ if (s && letter(*s)) {
+ while (*++s && letnum(*s))
+ ;
+ if (aok && *s == '[' && (alen = array_ref_len(s)))
+ s += alen;
+ }
+ return (char *) s;
+}
+
+/* Return a pointer to the first character past any legal variable name. */
+char *
+skip_wdvarname(const char *s,
+ int aok) /* skip array de-reference? */
+{
+ if (s[0] == CHAR && letter(s[1])) {
+ do {
+ s += 2;
+ } while (s[0] == CHAR && letnum(s[1]));
+ if (aok && s[0] == CHAR && s[1] == '[') {
+ /* skip possible array de-reference */
+ const char *p = s;
+ char c;
+ int depth = 0;
+
+ while (1) {
+ if (p[0] != CHAR)
+ break;
+ c = p[1];
+ p += 2;
+ if (c == '[')
+ depth++;
+ else if (c == ']' && --depth == 0) {
+ s = p;
+ break;
+ }
+ }
+ }
+ }
+ return (char *) s;
+}
+
+/* Check if coded string s is a variable name */
+int
+is_wdvarname(const char *s, int aok)
+{
+ char *p = skip_wdvarname(s, aok);
+
+ return p != s && p[0] == EOS;
+}
+
+/* Check if coded string s is a variable assignment */
+int
+is_wdvarassign(const char *s)
+{
+ char *p = skip_wdvarname(s, true);
+
+ return p != s && p[0] == CHAR && p[1] == '=';
+}
+
+/*
+ * Make the exported environment from the exported names in the dictionary.
+ */
+char **
+makenv(void)
+{
+ struct block *l = e->loc;
+ XPtrV env;
+ struct tbl *vp, **vpp;
+ int i;
+
+ XPinit(env, 64);
+ for (l = e->loc; l != NULL; l = l->next)
+ for (vpp = l->vars.tbls, i = l->vars.size; --i >= 0; )
+ if ((vp = *vpp++) != NULL &&
+ (vp->flag&(ISSET|EXPORT)) == (ISSET|EXPORT)) {
+ struct block *l2;
+ struct tbl *vp2;
+ unsigned int h = hash(vp->name);
+
+ /* unexport any redefined instances */
+ for (l2 = l->next; l2 != NULL; l2 = l2->next) {
+ vp2 = ktsearch(&l2->vars, vp->name, h);
+ if (vp2 != NULL)
+ vp2->flag &= ~EXPORT;
+ }
+ if ((vp->flag&INTEGER)) {
+ /* integer to string */
+ char *val;
+ val = str_val(vp);
+ vp->flag &= ~(INTEGER|RDONLY);
+ /* setstr can't fail here */
+ setstr(vp, val, KSH_RETURN_ERROR);
+ }
+ XPput(env, vp->val.s);
+ }
+ XPput(env, NULL);
+ return (char **) XPclose(env);
+}
+
+/*
+ * Called after a fork in parent to bump the random number generator.
+ * Done to ensure children will not get the same random number sequence
+ * if the parent doesn't use $RANDOM.
+ */
+void
+change_random(void)
+{
+ rand();
+}
+
+/*
+ * handle special variables with side effects - PATH, SECONDS.
+ */
+
+/* Test if name is a special parameter */
+static int
+special(const char *name)
+{
+ struct tbl *tp;
+
+ tp = ktsearch(&specials, name, hash(name));
+ return tp && (tp->flag & ISSET) ? tp->type : V_NONE;
+}
+
+/* Make a variable non-special */
+static void
+unspecial(const char *name)
+{
+ struct tbl *tp;
+
+ tp = ktsearch(&specials, name, hash(name));
+ if (tp)
+ ktdelete(tp);
+}
+
+static time_t seconds; /* time SECONDS last set */
+static int user_lineno; /* what user set $LINENO to */
+
+static void
+getspec(struct tbl *vp)
+{
+ switch (special(vp->name)) {
+ case V_SECONDS:
+ vp->flag &= ~SPECIAL;
+ /* On start up the value of SECONDS is used before seconds
+ * has been set - don't do anything in this case
+ * (see initcoms[] in main.c).
+ */
+ if (vp->flag & ISSET)
+ setint(vp, (long)(time(NULL) - seconds)); /* XXX 2038 */
+ vp->flag |= SPECIAL;
+ break;
+ case V_RANDOM:
+ vp->flag &= ~SPECIAL;
+ setint(vp, (long) (rand() & 0x7fff));
+ vp->flag |= SPECIAL;
+ break;
+#ifdef HISTORY
+ case V_HISTSIZE:
+ vp->flag &= ~SPECIAL;
+ setint(vp, (long) histsize);
+ vp->flag |= SPECIAL;
+ break;
+#endif /* HISTORY */
+ case V_OPTIND:
+ vp->flag &= ~SPECIAL;
+ setint(vp, (long) user_opt.uoptind);
+ vp->flag |= SPECIAL;
+ break;
+ case V_LINENO:
+ vp->flag &= ~SPECIAL;
+ setint(vp, (long) current_lineno + user_lineno);
+ vp->flag |= SPECIAL;
+ break;
+ }
+}
+
+static void
+setspec(struct tbl *vp)
+{
+ char *s;
+
+ switch (special(vp->name)) {
+ case V_PATH:
+ if (path)
+ afree(path, APERM);
+ path = str_save(str_val(vp), APERM);
+ flushcom(1); /* clear tracked aliases */
+ break;
+ case V_IFS:
+ setctypes(s = str_val(vp), C_IFS);
+ ifs0 = *s;
+ break;
+ case V_OPTIND:
+ vp->flag &= ~SPECIAL;
+ getopts_reset((int) intval(vp));
+ vp->flag |= SPECIAL;
+ break;
+ case V_POSIXLY_CORRECT:
+ change_flag(FPOSIX, OF_SPECIAL, 1);
+ break;
+ case V_TMPDIR:
+ if (tmpdir) {
+ afree(tmpdir, APERM);
+ tmpdir = (char *) 0;
+ }
+ /* Use tmpdir iff it is an absolute path, is writable and
+ * searchable and is a directory...
+ */
+ {
+ struct stat statb;
+
+ s = str_val(vp);
+ if (s[0] == '/' && access(s, W_OK|X_OK) == 0 &&
+ stat(s, &statb) == 0 && S_ISDIR(statb.st_mode))
+ tmpdir = str_save(s, APERM);
+ }
+ break;
+#ifdef HISTORY
+ case V_HISTSIZE:
+ vp->flag &= ~SPECIAL;
+ sethistsize((int) intval(vp));
+ vp->flag |= SPECIAL;
+ break;
+ case V_HISTFILE:
+ sethistfile(str_val(vp));
+ break;
+#endif /* HISTORY */
+#ifdef EDIT
+ case V_VISUAL:
+ set_editmode(str_val(vp));
+ break;
+ case V_EDITOR:
+ if (!(global("VISUAL")->flag & ISSET))
+ set_editmode(str_val(vp));
+ break;
+ case V_COLUMNS:
+ if ((x_cols = intval(vp)) <= MIN_COLS)
+ x_cols = MIN_COLS;
+ break;
+#endif /* EDIT */
+ case V_MAIL:
+ mbset(str_val(vp));
+ break;
+ case V_MAILPATH:
+ mpset(str_val(vp));
+ break;
+ case V_MAILCHECK:
+ vp->flag &= ~SPECIAL;
+ mcset(intval(vp));
+ vp->flag |= SPECIAL;
+ break;
+ case V_RANDOM:
+ vp->flag &= ~SPECIAL;
+ srand_deterministic((unsigned int)intval(vp));
+ vp->flag |= SPECIAL;
+ break;
+ case V_SECONDS:
+ vp->flag &= ~SPECIAL;
+ seconds = time(NULL) - intval(vp); /* XXX 2038 */
+ vp->flag |= SPECIAL;
+ break;
+ case V_TMOUT:
+ /* at&t ksh seems to do this (only listen if integer) */
+ if (vp->flag & INTEGER)
+ ksh_tmout = vp->val.i >= 0 ? vp->val.i : 0;
+ break;
+ case V_LINENO:
+ vp->flag &= ~SPECIAL;
+ /* The -1 is because line numbering starts at 1. */
+ user_lineno = (unsigned int) intval(vp) - current_lineno - 1;
+ vp->flag |= SPECIAL;
+ break;
+ }
+}
+
+static void
+unsetspec(struct tbl *vp)
+{
+ switch (special(vp->name)) {
+ case V_PATH:
+ if (path)
+ afree(path, APERM);
+ path = str_save(def_path, APERM);
+ flushcom(1); /* clear tracked aliases */
+ break;
+ case V_IFS:
+ setctypes(" \t\n", C_IFS);
+ ifs0 = ' ';
+ break;
+ case V_TMPDIR:
+ /* should not become unspecial */
+ if (tmpdir) {
+ afree(tmpdir, APERM);
+ tmpdir = (char *) 0;
+ }
+ break;
+ case V_MAIL:
+ mbset((char *) 0);
+ break;
+ case V_MAILPATH:
+ mpset((char *) 0);
+ break;
+ case V_LINENO:
+ case V_MAILCHECK: /* at&t ksh leaves previous value in place */
+ case V_RANDOM:
+ case V_SECONDS:
+ case V_TMOUT: /* at&t ksh leaves previous value in place */
+ unspecial(vp->name);
+ break;
+
+ /* at&t ksh man page says OPTIND, OPTARG and _ lose special meaning,
+ * but OPTARG does not (still set by getopts) and _ is also still
+ * set in various places.
+ * Don't know what at&t does for:
+ * MAIL, MAILPATH, HISTSIZE, HISTFILE,
+ * Unsetting these in at&t ksh does not loose the `specialness':
+ * no effect: IFS, COLUMNS, PATH, TMPDIR,
+ * VISUAL, EDITOR,
+ * pdkshisms: no effect:
+ * POSIXLY_CORRECT (use set +o posix instead)
+ */
+ }
+}
+
+/*
+ * Search for (and possibly create) a table entry starting with
+ * vp, indexed by val.
+ */
+static struct tbl *
+arraysearch(struct tbl *vp, int val)
+{
+ struct tbl *prev, *curr, *new;
+ size_t namelen = strlen(vp->name) + 1;
+
+ vp->flag |= ARRAY|DEFINED;
+ vp->index = 0;
+ /* The table entry is always [0] */
+ if (val == 0)
+ return vp;
+ prev = vp;
+ curr = vp->u.array;
+ while (curr && curr->index < val) {
+ prev = curr;
+ curr = curr->u.array;
+ }
+ if (curr && curr->index == val) {
+ if (curr->flag&ISSET)
+ return curr;
+ else
+ new = curr;
+ } else
+ new = (struct tbl *)alloc(sizeof(struct tbl) + namelen,
+ vp->areap);
+ strlcpy(new->name, vp->name, namelen);
+ new->flag = vp->flag & ~(ALLOC|DEFINED|ISSET|SPECIAL);
+ new->type = vp->type;
+ new->areap = vp->areap;
+ new->u2.field = vp->u2.field;
+ new->index = val;
+ if (curr != new) { /* not reusing old array entry */
+ prev->u.array = new;
+ new->u.array = curr;
+ }
+ return new;
+}
+
+/* Return the length of an array reference (eg, [1+2]) - cp is assumed
+ * to point to the open bracket. Returns 0 if there is no matching closing
+ * bracket.
+ */
+int
+array_ref_len(const char *cp)
+{
+ const char *s = cp;
+ int c;
+ int depth = 0;
+
+ while ((c = *s++) && (c != ']' || --depth))
+ if (c == '[')
+ depth++;
+ if (!c)
+ return 0;
+ return s - cp;
+}
+
+/*
+ * Make a copy of the base of an array name
+ */
+char *
+arrayname(const char *str)
+{
+ const char *p;
+
+ if ((p = strchr(str, '[')) == 0)
+ /* Shouldn't happen, but why worry? */
+ return (char *) str;
+
+ return str_nsave(str, p - str, ATEMP);
+}
+
+/* Set (or overwrite, if !reset) the array variable var to the values in vals.
+ */
+void
+set_array(const char *var, int reset, char **vals)
+{
+ struct tbl *vp, *vq;
+ int i;
+
+ /* to get local array, use "typeset foo; set -A foo" */
+ vp = global(var);
+
+ /* Note: at&t ksh allows set -A but not set +A of a read-only var */
+ if ((vp->flag&RDONLY))
+ errorf("%s: is read only", var);
+ /* This code is quite non-optimal */
+ if (reset > 0)
+ /* trash existing values and attributes */
+ unset(vp, 0);
+ /* todo: would be nice for assignment to completely succeed or
+ * completely fail. Only really effects integer arrays:
+ * evaluation of some of vals[] may fail...
+ */
+ for (i = 0; vals[i]; i++) {
+ vq = arraysearch(vp, i);
+ /* would be nice to deal with errors here... (see above) */
+ setstr(vq, vals[i], KSH_RETURN_ERROR);
+ }
+}
diff --git a/version.c b/version.c
@@ -0,0 +1,10 @@
+/* $OpenBSD: version.c,v 1.12 1999/07/14 13:37:24 millert Exp $ */
+
+/*
+ * value of $KSH_VERSION (or $SH_VERSION)
+ */
+
+#include "sh.h"
+
+const char ksh_version [] =
+ "@(#)PD KSH v5.2.14 99/07/13.2";
diff --git a/vi.c b/vi.c
@@ -0,0 +1,2128 @@
+/* $OpenBSD: vi.c,v 1.28 2013/12/18 16:45:46 deraadt Exp $ */
+
+/*
+ * vi command editing
+ * written by John Rochester (initially for nsh)
+ * bludgeoned to fit pdksh by Larry Bouzane, Jeff Sparkes & Eric Gisin
+ *
+ */
+#include "config.h"
+#ifdef VI
+
+#include "sh.h"
+#include <ctype.h>
+#include <sys/stat.h> /* completion */
+#include "edit.h"
+
+#define CMDLEN 2048
+#define Ctrl(c) (c&0x1f)
+#define is_wordch(c) (letnum(c))
+
+struct edstate {
+ int winleft;
+ char *cbuf;
+ int cbufsize;
+ int linelen;
+ int cursor;
+};
+
+
+static int vi_hook(int);
+static void vi_reset(char *, size_t);
+static int nextstate(int);
+static int vi_insert(int);
+static int vi_cmd(int, const char *);
+static int domove(int, const char *, int);
+static int redo_insert(int);
+static void yank_range(int, int);
+static int bracktype(int);
+static void save_cbuf(void);
+static void restore_cbuf(void);
+static void edit_reset(char *, size_t);
+static int putbuf(const char *, int, int);
+static void del_range(int, int);
+static int findch(int, int, int, int);
+static int forwword(int);
+static int backword(int);
+static int endword(int);
+static int Forwword(int);
+static int Backword(int);
+static int Endword(int);
+static int grabhist(int, int);
+static int grabsearch(int, int, int, char *);
+static void redraw_line(int);
+static void refresh(int);
+static int outofwin(void);
+static void rewindow(void);
+static int newcol(int, int);
+static void display(char *, char *, int);
+static void ed_mov_opt(int, char *);
+static int expand_word(int);
+static int complete_word(int, int);
+static int print_expansions(struct edstate *, int);
+static int char_len(int);
+static void x_vi_zotc(int);
+static void vi_pprompt(int);
+static void vi_error(void);
+static void vi_macro_reset(void);
+static int x_vi_putbuf(const char *, size_t);
+
+#define C_ 0x1 /* a valid command that isn't a M_, E_, U_ */
+#define M_ 0x2 /* movement command (h, l, etc.) */
+#define E_ 0x4 /* extended command (c, d, y) */
+#define X_ 0x8 /* long command (@, f, F, t, T, etc.) */
+#define U_ 0x10 /* an UN-undoable command (that isn't a M_) */
+#define B_ 0x20 /* bad command (^@) */
+#define Z_ 0x40 /* repeat count defaults to 0 (not 1) */
+#define S_ 0x80 /* search (/, ?) */
+
+#define is_bad(c) (classify[(c)&0x7f]&B_)
+#define is_cmd(c) (classify[(c)&0x7f]&(M_|E_|C_|U_))
+#define is_move(c) (classify[(c)&0x7f]&M_)
+#define is_extend(c) (classify[(c)&0x7f]&E_)
+#define is_long(c) (classify[(c)&0x7f]&X_)
+#define is_undoable(c) (!(classify[(c)&0x7f]&U_))
+#define is_srch(c) (classify[(c)&0x7f]&S_)
+#define is_zerocount(c) (classify[(c)&0x7f]&Z_)
+
+const unsigned char classify[128] = {
+ /* 0 1 2 3 4 5 6 7 */
+ /* 0 ^@ ^A ^B ^C ^D ^E ^F ^G */
+ B_, 0, 0, 0, 0, C_|U_, C_|Z_, 0,
+ /* 01 ^H ^I ^J ^K ^L ^M ^N ^O */
+ M_, C_|Z_, 0, 0, C_|U_, 0, C_, 0,
+ /* 02 ^P ^Q ^R ^S ^T ^U ^V ^W */
+ C_, 0, C_|U_, 0, 0, 0, C_, 0,
+ /* 03 ^X ^Y ^Z ^[ ^\ ^] ^^ ^_ */
+ C_, 0, 0, C_|Z_, 0, 0, 0, 0,
+ /* 04 <space> ! " # $ % & ' */
+ M_, 0, 0, C_, M_, M_, 0, 0,
+ /* 05 ( ) * + , - . / */
+ 0, 0, C_, C_, M_, C_, 0, C_|S_,
+ /* 06 0 1 2 3 4 5 6 7 */
+ M_, 0, 0, 0, 0, 0, 0, 0,
+ /* 07 8 9 : ; < = > ? */
+ 0, 0, 0, M_, 0, C_, 0, C_|S_,
+ /* 010 @ A B C D E F G */
+ C_|X_, C_, M_, C_, C_, M_, M_|X_, C_|U_|Z_,
+ /* 011 H I J K L M N O */
+ 0, C_, 0, 0, 0, 0, C_|U_, 0,
+ /* 012 P Q R S T U V W */
+ C_, 0, C_, C_, M_|X_, C_, 0, M_,
+ /* 013 X Y Z [ \ ] ^ _ */
+ C_, C_|U_, 0, 0, C_|Z_, 0, M_, C_|Z_,
+ /* 014 ` a b c d e f g */
+ 0, C_, M_, E_, E_, M_, M_|X_, C_|Z_,
+ /* 015 h i j k l m n o */
+ M_, C_, C_|U_, C_|U_, M_, 0, C_|U_, 0,
+ /* 016 p q r s t u v w */
+ C_, 0, X_, C_, M_|X_, C_|U_, C_|U_|Z_,M_,
+ /* 017 x y z { | } ~ ^? */
+ C_, E_|U_, 0, 0, M_|Z_, 0, C_, 0
+};
+
+#define MAXVICMD 3
+#define SRCHLEN 40
+
+#define INSERT 1
+#define REPLACE 2
+
+#define VNORMAL 0 /* command, insert or replace mode */
+#define VARG1 1 /* digit prefix (first, eg, 5l) */
+#define VEXTCMD 2 /* cmd + movement (eg, cl) */
+#define VARG2 3 /* digit prefix (second, eg, 2c3l) */
+#define VXCH 4 /* f, F, t, T, @ */
+#define VFAIL 5 /* bad command */
+#define VCMD 6 /* single char command (eg, X) */
+#define VREDO 7 /* . */
+#define VLIT 8 /* ^V */
+#define VSEARCH 9 /* /, ? */
+#define VVERSION 10 /* <ESC> ^V */
+
+static char undocbuf[CMDLEN];
+
+static struct edstate *save_edstate(struct edstate *old);
+static void restore_edstate(struct edstate *old, struct edstate *new);
+static void free_edstate(struct edstate *old);
+
+static struct edstate ebuf;
+static struct edstate undobuf = { 0, undocbuf, CMDLEN, 0, 0 };
+
+static struct edstate *es; /* current editor state */
+static struct edstate *undo;
+
+static char ibuf[CMDLEN]; /* input buffer */
+static int first_insert; /* set when starting in insert mode */
+static int saved_inslen; /* saved inslen for first insert */
+static int inslen; /* length of input buffer */
+static int srchlen; /* length of current search pattern */
+static char ybuf[CMDLEN]; /* yank buffer */
+static int yanklen; /* length of yank buffer */
+static int fsavecmd = ' '; /* last find command */
+static int fsavech; /* character to find */
+static char lastcmd[MAXVICMD]; /* last non-move command */
+static int lastac; /* argcnt for lastcmd */
+static int lastsearch = ' '; /* last search command */
+static char srchpat[SRCHLEN]; /* last search pattern */
+static int insert; /* non-zero in insert mode */
+static int hnum; /* position in history */
+static int ohnum; /* history line copied (after mod) */
+static int hlast; /* 1 past last position in history */
+static int modified; /* buffer has been "modified" */
+static int state;
+
+/* Information for keeping track of macros that are being expanded.
+ * The format of buf is the alias contents followed by a null byte followed
+ * by the name (letter) of the alias. The end of the buffer is marked by
+ * a double null. The name of the alias is stored so recursive macros can
+ * be detected.
+ */
+struct macro_state {
+ unsigned char *p; /* current position in buf */
+ unsigned char *buf; /* pointer to macro(s) being expanded */
+ int len; /* how much data in buffer */
+};
+static struct macro_state macro;
+
+enum expand_mode { NONE, EXPAND, COMPLETE, PRINT };
+static enum expand_mode expanded = NONE;/* last input was expanded */
+
+int
+x_vi(char *buf, size_t len)
+{
+ int c;
+
+ vi_reset(buf, len > CMDLEN ? CMDLEN : len);
+ vi_pprompt(1);
+ x_flush();
+ while (1) {
+ if (macro.p) {
+ c = (unsigned char)*macro.p++;
+ /* end of current macro? */
+ if (!c) {
+ /* more macros left to finish? */
+ if (*macro.p++)
+ continue;
+ /* must be the end of all the macros */
+ vi_macro_reset();
+ c = x_getc();
+ }
+ } else
+ c = x_getc();
+
+ if (c == -1)
+ break;
+ if (state != VLIT) {
+ if (c == edchars.intr || c == edchars.quit) {
+ /* pretend we got an interrupt */
+ x_vi_zotc(c);
+ x_flush();
+ trapsig(c == edchars.intr ? SIGINT : SIGQUIT);
+ x_mode(false);
+ unwind(LSHELL);
+ } else if (c == edchars.eof && state != VVERSION) {
+ if (es->linelen == 0) {
+ x_vi_zotc(edchars.eof);
+ c = -1;
+ break;
+ }
+ continue;
+ }
+ }
+ if (vi_hook(c))
+ break;
+ x_flush();
+ }
+
+ x_putc('\r'); x_putc('\n'); x_flush();
+
+ if (c == -1 || len <= es->linelen)
+ return -1;
+
+ if (es->cbuf != buf)
+ memmove(buf, es->cbuf, es->linelen);
+
+ buf[es->linelen++] = '\n';
+
+ return es->linelen;
+}
+
+static int
+vi_hook(int ch)
+{
+ static char curcmd[MAXVICMD], locpat[SRCHLEN];
+ static int cmdlen, argc1, argc2;
+
+ switch (state) {
+
+ case VNORMAL:
+ if (insert != 0) {
+ if (ch == Ctrl('v')) {
+ state = VLIT;
+ ch = '^';
+ }
+ switch (vi_insert(ch)) {
+ case -1:
+ vi_error();
+ state = VNORMAL;
+ break;
+ case 0:
+ if (state == VLIT) {
+ es->cursor--;
+ refresh(0);
+ } else
+ refresh(insert != 0);
+ break;
+ case 1:
+ return 1;
+ }
+ } else {
+ if (ch == '\r' || ch == '\n')
+ return 1;
+ cmdlen = 0;
+ argc1 = 0;
+ if (ch >= '1' && ch <= '9') {
+ argc1 = ch - '0';
+ state = VARG1;
+ } else {
+ curcmd[cmdlen++] = ch;
+ state = nextstate(ch);
+ if (state == VSEARCH) {
+ save_cbuf();
+ es->cursor = 0;
+ es->linelen = 0;
+ if (ch == '/') {
+ if (putbuf("/", 1, 0) != 0)
+ return -1;
+ } else if (putbuf("?", 1, 0) != 0)
+ return -1;
+ refresh(0);
+ }
+ if (state == VVERSION) {
+ save_cbuf();
+ es->cursor = 0;
+ es->linelen = 0;
+ putbuf(ksh_version + 4,
+ strlen(ksh_version + 4), 0);
+ refresh(0);
+ }
+ }
+ }
+ break;
+
+ case VLIT:
+ if (is_bad(ch)) {
+ del_range(es->cursor, es->cursor + 1);
+ vi_error();
+ } else
+ es->cbuf[es->cursor++] = ch;
+ refresh(1);
+ state = VNORMAL;
+ break;
+
+ case VVERSION:
+ restore_cbuf();
+ state = VNORMAL;
+ refresh(0);
+ break;
+
+ case VARG1:
+ if (isdigit(ch))
+ argc1 = argc1 * 10 + ch - '0';
+ else {
+ curcmd[cmdlen++] = ch;
+ state = nextstate(ch);
+ }
+ break;
+
+ case VEXTCMD:
+ argc2 = 0;
+ if (ch >= '1' && ch <= '9') {
+ argc2 = ch - '0';
+ state = VARG2;
+ return 0;
+ } else {
+ curcmd[cmdlen++] = ch;
+ if (ch == curcmd[0])
+ state = VCMD;
+ else if (is_move(ch))
+ state = nextstate(ch);
+ else
+ state = VFAIL;
+ }
+ break;
+
+ case VARG2:
+ if (isdigit(ch))
+ argc2 = argc2 * 10 + ch - '0';
+ else {
+ if (argc1 == 0)
+ argc1 = argc2;
+ else
+ argc1 *= argc2;
+ curcmd[cmdlen++] = ch;
+ if (ch == curcmd[0])
+ state = VCMD;
+ else if (is_move(ch))
+ state = nextstate(ch);
+ else
+ state = VFAIL;
+ }
+ break;
+
+ case VXCH:
+ if (ch == Ctrl('['))
+ state = VNORMAL;
+ else {
+ curcmd[cmdlen++] = ch;
+ state = VCMD;
+ }
+ break;
+
+ case VSEARCH:
+ if (ch == '\r' || ch == '\n' /*|| ch == Ctrl('[')*/ ) {
+ restore_cbuf();
+ /* Repeat last search? */
+ if (srchlen == 0) {
+ if (!srchpat[0]) {
+ vi_error();
+ state = VNORMAL;
+ refresh(0);
+ return 0;
+ }
+ } else {
+ locpat[srchlen] = '\0';
+ (void) strlcpy(srchpat, locpat, sizeof srchpat);
+ }
+ state = VCMD;
+ } else if (ch == edchars.erase || ch == Ctrl('h')) {
+ if (srchlen != 0) {
+ srchlen--;
+ es->linelen -= char_len((unsigned char)locpat[srchlen]);
+ es->cursor = es->linelen;
+ refresh(0);
+ return 0;
+ }
+ restore_cbuf();
+ state = VNORMAL;
+ refresh(0);
+ } else if (ch == edchars.kill) {
+ srchlen = 0;
+ es->linelen = 1;
+ es->cursor = 1;
+ refresh(0);
+ return 0;
+ } else if (ch == edchars.werase) {
+ struct edstate new_es, *save_es;
+ int i;
+ int n = srchlen;
+
+ new_es.cursor = n;
+ new_es.cbuf = locpat;
+
+ save_es = es;
+ es = &new_es;
+ n = backword(1);
+ es = save_es;
+
+ for (i = srchlen; --i >= n; )
+ es->linelen -= char_len((unsigned char)locpat[i]);
+ srchlen = n;
+ es->cursor = es->linelen;
+ refresh(0);
+ return 0;
+ } else {
+ if (srchlen == SRCHLEN - 1)
+ vi_error();
+ else {
+ locpat[srchlen++] = ch;
+ if ((ch & 0x80) && Flag(FVISHOW8)) {
+ if (es->linelen + 2 > es->cbufsize)
+ vi_error();
+ es->cbuf[es->linelen++] = 'M';
+ es->cbuf[es->linelen++] = '-';
+ ch &= 0x7f;
+ }
+ if (ch < ' ' || ch == 0x7f) {
+ if (es->linelen + 2 > es->cbufsize)
+ vi_error();
+ es->cbuf[es->linelen++] = '^';
+ es->cbuf[es->linelen++] = ch ^ '@';
+ } else {
+ if (es->linelen >= es->cbufsize)
+ vi_error();
+ es->cbuf[es->linelen++] = ch;
+ }
+ es->cursor = es->linelen;
+ refresh(0);
+ }
+ return 0;
+ }
+ break;
+ }
+
+ switch (state) {
+ case VCMD:
+ state = VNORMAL;
+ switch (vi_cmd(argc1, curcmd)) {
+ case -1:
+ vi_error();
+ refresh(0);
+ break;
+ case 0:
+ if (insert != 0)
+ inslen = 0;
+ refresh(insert != 0);
+ break;
+ case 1:
+ refresh(0);
+ return 1;
+ case 2:
+ /* back from a 'v' command - don't redraw the screen */
+ return 1;
+ }
+ break;
+
+ case VREDO:
+ state = VNORMAL;
+ if (argc1 != 0)
+ lastac = argc1;
+ switch (vi_cmd(lastac, lastcmd)) {
+ case -1:
+ vi_error();
+ refresh(0);
+ break;
+ case 0:
+ if (insert != 0) {
+ if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
+ lastcmd[0] == 'C') {
+ if (redo_insert(1) != 0)
+ vi_error();
+ } else {
+ if (redo_insert(lastac) != 0)
+ vi_error();
+ }
+ }
+ refresh(0);
+ break;
+ case 1:
+ refresh(0);
+ return 1;
+ case 2:
+ /* back from a 'v' command - can't happen */
+ break;
+ }
+ break;
+
+ case VFAIL:
+ state = VNORMAL;
+ vi_error();
+ break;
+ }
+ return 0;
+}
+
+static void
+vi_reset(char *buf, size_t len)
+{
+ state = VNORMAL;
+ ohnum = hnum = hlast = histnum(-1) + 1;
+ insert = INSERT;
+ saved_inslen = inslen;
+ first_insert = 1;
+ inslen = 0;
+ modified = 1;
+ vi_macro_reset();
+ edit_reset(buf, len);
+}
+
+static int
+nextstate(int ch)
+{
+ if (is_extend(ch))
+ return VEXTCMD;
+ else if (is_srch(ch))
+ return VSEARCH;
+ else if (is_long(ch))
+ return VXCH;
+ else if (ch == '.')
+ return VREDO;
+ else if (ch == Ctrl('v'))
+ return VVERSION;
+ else if (is_cmd(ch))
+ return VCMD;
+ else
+ return VFAIL;
+}
+
+static int
+vi_insert(int ch)
+{
+ int tcursor;
+
+ if (ch == edchars.erase || ch == Ctrl('h')) {
+ if (insert == REPLACE) {
+ if (es->cursor == undo->cursor) {
+ vi_error();
+ return 0;
+ }
+ if (inslen > 0)
+ inslen--;
+ es->cursor--;
+ if (es->cursor >= undo->linelen)
+ es->linelen--;
+ else
+ es->cbuf[es->cursor] = undo->cbuf[es->cursor];
+ } else {
+ if (es->cursor == 0) {
+ /* x_putc(BEL); no annoying bell here */
+ return 0;
+ }
+ if (inslen > 0)
+ inslen--;
+ es->cursor--;
+ es->linelen--;
+ memmove(&es->cbuf[es->cursor], &es->cbuf[es->cursor+1],
+ es->linelen - es->cursor + 1);
+ }
+ expanded = NONE;
+ return 0;
+ }
+ if (ch == edchars.kill) {
+ if (es->cursor != 0) {
+ inslen = 0;
+ memmove(es->cbuf, &es->cbuf[es->cursor],
+ es->linelen - es->cursor);
+ es->linelen -= es->cursor;
+ es->cursor = 0;
+ }
+ expanded = NONE;
+ return 0;
+ }
+ if (ch == edchars.werase) {
+ if (es->cursor != 0) {
+ tcursor = backword(1);
+ memmove(&es->cbuf[tcursor], &es->cbuf[es->cursor],
+ es->linelen - es->cursor);
+ es->linelen -= es->cursor - tcursor;
+ if (inslen < es->cursor - tcursor)
+ inslen = 0;
+ else
+ inslen -= es->cursor - tcursor;
+ es->cursor = tcursor;
+ }
+ expanded = NONE;
+ return 0;
+ }
+ /* If any chars are entered before escape, trash the saved insert
+ * buffer (if user inserts & deletes char, ibuf gets trashed and
+ * we don't want to use it)
+ */
+ if (first_insert && ch != Ctrl('['))
+ saved_inslen = 0;
+ switch (ch) {
+ case '\0':
+ return -1;
+
+ case '\r':
+ case '\n':
+ return 1;
+
+ case Ctrl('['):
+ expanded = NONE;
+ if (first_insert) {
+ first_insert = 0;
+ if (inslen == 0) {
+ inslen = saved_inslen;
+ return redo_insert(0);
+ }
+ lastcmd[0] = 'a';
+ lastac = 1;
+ }
+ if (lastcmd[0] == 's' || lastcmd[0] == 'c' ||
+ lastcmd[0] == 'C')
+ return redo_insert(0);
+ else
+ return redo_insert(lastac - 1);
+
+ /* { Begin nonstandard vi commands */
+ case Ctrl('x'):
+ expand_word(0);
+ break;
+
+ case Ctrl('f'):
+ complete_word(0, 0);
+ break;
+
+ case Ctrl('e'):
+ print_expansions(es, 0);
+ break;
+
+ case Ctrl('i'):
+ if (Flag(FVITABCOMPLETE)) {
+ complete_word(0, 0);
+ break;
+ }
+ /* FALLTHROUGH */
+ /* End nonstandard vi commands } */
+
+ default:
+ if (es->linelen >= es->cbufsize - 1)
+ return -1;
+ ibuf[inslen++] = ch;
+ if (insert == INSERT) {
+ memmove(&es->cbuf[es->cursor+1], &es->cbuf[es->cursor],
+ es->linelen - es->cursor);
+ es->linelen++;
+ }
+ es->cbuf[es->cursor++] = ch;
+ if (insert == REPLACE && es->cursor > es->linelen)
+ es->linelen++;
+ expanded = NONE;
+ }
+ return 0;
+}
+
+static int
+vi_cmd(int argcnt, const char *cmd)
+{
+ int ncursor;
+ int cur, c1, c2, c3 = 0;
+ int any;
+ struct edstate *t;
+
+ if (argcnt == 0 && !is_zerocount(*cmd))
+ argcnt = 1;
+
+ if (is_move(*cmd)) {
+ if ((cur = domove(argcnt, cmd, 0)) >= 0) {
+ if (cur == es->linelen && cur != 0)
+ cur--;
+ es->cursor = cur;
+ } else
+ return -1;
+ } else {
+ /* Don't save state in middle of macro.. */
+ if (is_undoable(*cmd) && !macro.p) {
+ undo->winleft = es->winleft;
+ memmove(undo->cbuf, es->cbuf, es->linelen);
+ undo->linelen = es->linelen;
+ undo->cursor = es->cursor;
+ lastac = argcnt;
+ memmove(lastcmd, cmd, MAXVICMD);
+ }
+ switch (*cmd) {
+
+ case Ctrl('l'):
+ case Ctrl('r'):
+ redraw_line(1);
+ break;
+
+ case '@':
+ {
+ static char alias[] = "_\0";
+ struct tbl *ap;
+ int olen, nlen;
+ char *p, *nbuf;
+
+ /* lookup letter in alias list... */
+ alias[1] = cmd[1];
+ ap = ktsearch(&aliases, alias, hash(alias));
+ if (!cmd[1] || !ap || !(ap->flag & ISSET))
+ return -1;
+ /* check if this is a recursive call... */
+ if ((p = (char *) macro.p))
+ while ((p = strchr(p, '\0')) && p[1])
+ if (*++p == cmd[1])
+ return -1;
+ /* insert alias into macro buffer */
+ nlen = strlen(ap->val.s) + 1;
+ olen = !macro.p ? 2 :
+ macro.len - (macro.p - macro.buf);
+ nbuf = alloc(nlen + 1 + olen, APERM);
+ memcpy(nbuf, ap->val.s, nlen);
+ nbuf[nlen++] = cmd[1];
+ if (macro.p) {
+ memcpy(nbuf + nlen, macro.p, olen);
+ afree(macro.buf, APERM);
+ nlen += olen;
+ } else {
+ nbuf[nlen++] = '\0';
+ nbuf[nlen++] = '\0';
+ }
+ macro.p = macro.buf = (unsigned char *) nbuf;
+ macro.len = nlen;
+ }
+ break;
+
+ case 'a':
+ modified = 1; hnum = hlast;
+ if (es->linelen != 0)
+ es->cursor++;
+ insert = INSERT;
+ break;
+
+ case 'A':
+ modified = 1; hnum = hlast;
+ del_range(0, 0);
+ es->cursor = es->linelen;
+ insert = INSERT;
+ break;
+
+ case 'S':
+ es->cursor = domove(1, "^", 1);
+ del_range(es->cursor, es->linelen);
+ modified = 1; hnum = hlast;
+ insert = INSERT;
+ break;
+
+ case 'Y':
+ cmd = "y$";
+ /* ahhhhhh... */
+ case 'c':
+ case 'd':
+ case 'y':
+ if (*cmd == cmd[1]) {
+ c1 = *cmd == 'c' ? domove(1, "^", 1) : 0;
+ c2 = es->linelen;
+ } else if (!is_move(cmd[1]))
+ return -1;
+ else {
+ if ((ncursor = domove(argcnt, &cmd[1], 1)) < 0)
+ return -1;
+ if (*cmd == 'c' &&
+ (cmd[1]=='w' || cmd[1]=='W') &&
+ !isspace((unsigned char)es->cbuf[es->cursor])) {
+ while (isspace(
+ (unsigned char)es->cbuf[--ncursor]))
+ ;
+ ncursor++;
+ }
+ if (ncursor > es->cursor) {
+ c1 = es->cursor;
+ c2 = ncursor;
+ } else {
+ c1 = ncursor;
+ c2 = es->cursor;
+ if (cmd[1] == '%')
+ c2++;
+ }
+ }
+ if (*cmd != 'c' && c1 != c2)
+ yank_range(c1, c2);
+ if (*cmd != 'y') {
+ del_range(c1, c2);
+ es->cursor = c1;
+ }
+ if (*cmd == 'c') {
+ modified = 1; hnum = hlast;
+ insert = INSERT;
+ }
+ break;
+
+ case 'p':
+ modified = 1; hnum = hlast;
+ if (es->linelen != 0)
+ es->cursor++;
+ while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
+ ;
+ if (es->cursor != 0)
+ es->cursor--;
+ if (argcnt != 0)
+ return -1;
+ break;
+
+ case 'P':
+ modified = 1; hnum = hlast;
+ any = 0;
+ while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
+ any = 1;
+ if (any && es->cursor != 0)
+ es->cursor--;
+ if (argcnt != 0)
+ return -1;
+ break;
+
+ case 'C':
+ modified = 1; hnum = hlast;
+ del_range(es->cursor, es->linelen);
+ insert = INSERT;
+ break;
+
+ case 'D':
+ yank_range(es->cursor, es->linelen);
+ del_range(es->cursor, es->linelen);
+ if (es->cursor != 0)
+ es->cursor--;
+ break;
+
+ case 'g':
+ if (!argcnt)
+ argcnt = hlast;
+ /* FALLTHROUGH */
+ case 'G':
+ if (!argcnt)
+ argcnt = 1;
+ else
+ argcnt = hlast - (source->line - argcnt);
+ if (grabhist(modified, argcnt - 1) < 0)
+ return -1;
+ else {
+ modified = 0;
+ hnum = argcnt - 1;
+ }
+ break;
+
+ case 'i':
+ modified = 1; hnum = hlast;
+ insert = INSERT;
+ break;
+
+ case 'I':
+ modified = 1; hnum = hlast;
+ es->cursor = domove(1, "^", 1);
+ insert = INSERT;
+ break;
+
+ case 'j':
+ case '+':
+ case Ctrl('n'):
+ if (grabhist(modified, hnum + argcnt) < 0)
+ return -1;
+ else {
+ modified = 0;
+ hnum += argcnt;
+ }
+ break;
+
+ case 'k':
+ case '-':
+ case Ctrl('p'):
+ if (grabhist(modified, hnum - argcnt) < 0)
+ return -1;
+ else {
+ modified = 0;
+ hnum -= argcnt;
+ }
+ break;
+
+ case 'r':
+ if (es->linelen == 0)
+ return -1;
+ modified = 1; hnum = hlast;
+ if (cmd[1] == 0)
+ vi_error();
+ else {
+ int n;
+
+ if (es->cursor + argcnt > es->linelen)
+ return -1;
+ for (n = 0; n < argcnt; ++n)
+ es->cbuf[es->cursor + n] = cmd[1];
+ es->cursor += n - 1;
+ }
+ break;
+
+ case 'R':
+ modified = 1; hnum = hlast;
+ insert = REPLACE;
+ break;
+
+ case 's':
+ if (es->linelen == 0)
+ return -1;
+ modified = 1; hnum = hlast;
+ if (es->cursor + argcnt > es->linelen)
+ argcnt = es->linelen - es->cursor;
+ del_range(es->cursor, es->cursor + argcnt);
+ insert = INSERT;
+ break;
+
+ case 'v':
+ if (es->linelen == 0 && argcnt == 0)
+ return -1;
+ if (!argcnt) {
+ if (modified) {
+ es->cbuf[es->linelen] = '\0';
+ source->line++;
+ histsave(source->line, es->cbuf, 1);
+ } else
+ argcnt = source->line + 1
+ - (hlast - hnum);
+ }
+ shf_snprintf(es->cbuf, es->cbufsize,
+ argcnt ? "%s %d" : "%s",
+ "fc -e ${VISUAL:-${EDITOR:-vi}} --",
+ argcnt);
+ es->linelen = strlen(es->cbuf);
+ return 2;
+
+ case 'x':
+ if (es->linelen == 0)
+ return -1;
+ modified = 1; hnum = hlast;
+ if (es->cursor + argcnt > es->linelen)
+ argcnt = es->linelen - es->cursor;
+ yank_range(es->cursor, es->cursor + argcnt);
+ del_range(es->cursor, es->cursor + argcnt);
+ break;
+
+ case 'X':
+ if (es->cursor > 0) {
+ modified = 1; hnum = hlast;
+ if (es->cursor < argcnt)
+ argcnt = es->cursor;
+ yank_range(es->cursor - argcnt, es->cursor);
+ del_range(es->cursor - argcnt, es->cursor);
+ es->cursor -= argcnt;
+ } else
+ return -1;
+ break;
+
+ case 'u':
+ t = es;
+ es = undo;
+ undo = t;
+ break;
+
+ case 'U':
+ if (!modified)
+ return -1;
+ if (grabhist(modified, ohnum) < 0)
+ return -1;
+ modified = 0;
+ hnum = ohnum;
+ break;
+
+ case '?':
+ if (hnum == hlast)
+ hnum = -1;
+ /* ahhh */
+ case '/':
+ c3 = 1;
+ srchlen = 0;
+ lastsearch = *cmd;
+ /* FALLTHROUGH */
+ case 'n':
+ case 'N':
+ if (lastsearch == ' ')
+ return -1;
+ if (lastsearch == '?')
+ c1 = 1;
+ else
+ c1 = 0;
+ if (*cmd == 'N')
+ c1 = !c1;
+ if ((c2 = grabsearch(modified, hnum,
+ c1, srchpat)) < 0) {
+ if (c3) {
+ restore_cbuf();
+ refresh(0);
+ }
+ return -1;
+ } else {
+ modified = 0;
+ hnum = c2;
+ ohnum = hnum;
+ }
+ break;
+ case '_': {
+ int inspace;
+ char *p, *sp;
+
+ if (histnum(-1) < 0)
+ return -1;
+ p = *histpos();
+#define issp(c) (isspace((unsigned char)(c)) || (c) == '\n')
+ if (argcnt) {
+ while (*p && issp(*p))
+ p++;
+ while (*p && --argcnt) {
+ while (*p && !issp(*p))
+ p++;
+ while (*p && issp(*p))
+ p++;
+ }
+ if (!*p)
+ return -1;
+ sp = p;
+ } else {
+ sp = p;
+ inspace = 0;
+ while (*p) {
+ if (issp(*p))
+ inspace = 1;
+ else if (inspace) {
+ inspace = 0;
+ sp = p;
+ }
+ p++;
+ }
+ p = sp;
+ }
+ modified = 1; hnum = hlast;
+ if (es->cursor != es->linelen)
+ es->cursor++;
+ while (*p && !issp(*p)) {
+ argcnt++;
+ p++;
+ }
+ if (putbuf(space, 1, 0) != 0)
+ argcnt = -1;
+ else if (putbuf(sp, argcnt, 0) != 0)
+ argcnt = -1;
+ if (argcnt < 0) {
+ if (es->cursor != 0)
+ es->cursor--;
+ return -1;
+ }
+ insert = INSERT;
+ }
+ break;
+
+ case '~': {
+ char *p;
+ int i;
+
+ if (es->linelen == 0)
+ return -1;
+ for (i = 0; i < argcnt; i++) {
+ p = &es->cbuf[es->cursor];
+ if (islower((unsigned char)*p)) {
+ modified = 1; hnum = hlast;
+ *p = toupper(*p);
+ } else if (isupper((unsigned char)*p)) {
+ modified = 1; hnum = hlast;
+ *p = tolower(*p);
+ }
+ if (es->cursor < es->linelen - 1)
+ es->cursor++;
+ }
+ break;
+ }
+
+ case '#':
+ {
+ int ret = x_do_comment(es->cbuf, es->cbufsize,
+ &es->linelen);
+ if (ret >= 0)
+ es->cursor = 0;
+ return ret;
+ }
+
+ case '=': /* at&t ksh */
+ case Ctrl('e'): /* Nonstandard vi/ksh */
+ print_expansions(es, 1);
+ break;
+
+
+ case Ctrl('i'): /* Nonstandard vi/ksh */
+ if (!Flag(FVITABCOMPLETE))
+ return -1;
+ complete_word(1, argcnt);
+ break;
+
+ case Ctrl('['): /* some annoying at&t ksh's */
+ if (!Flag(FVIESCCOMPLETE))
+ return -1;
+ case '\\': /* at&t ksh */
+ case Ctrl('f'): /* Nonstandard vi/ksh */
+ complete_word(1, argcnt);
+ break;
+
+
+ case '*': /* at&t ksh */
+ case Ctrl('x'): /* Nonstandard vi/ksh */
+ expand_word(1);
+ break;
+ }
+ if (insert == 0 && es->cursor != 0 && es->cursor >= es->linelen)
+ es->cursor--;
+ }
+ return 0;
+}
+
+static int
+domove(int argcnt, const char *cmd, int sub)
+{
+ int bcount, i = 0, t;
+ int ncursor = 0;
+
+ switch (*cmd) {
+
+ case 'b':
+ if (!sub && es->cursor == 0)
+ return -1;
+ ncursor = backword(argcnt);
+ break;
+
+ case 'B':
+ if (!sub && es->cursor == 0)
+ return -1;
+ ncursor = Backword(argcnt);
+ break;
+
+ case 'e':
+ if (!sub && es->cursor + 1 >= es->linelen)
+ return -1;
+ ncursor = endword(argcnt);
+ if (sub && ncursor < es->linelen)
+ ncursor++;
+ break;
+
+ case 'E':
+ if (!sub && es->cursor + 1 >= es->linelen)
+ return -1;
+ ncursor = Endword(argcnt);
+ if (sub && ncursor < es->linelen)
+ ncursor++;
+ break;
+
+ case 'f':
+ case 'F':
+ case 't':
+ case 'T':
+ fsavecmd = *cmd;
+ fsavech = cmd[1];
+ /* drop through */
+
+ case ',':
+ case ';':
+ if (fsavecmd == ' ')
+ return -1;
+ i = fsavecmd == 'f' || fsavecmd == 'F';
+ t = fsavecmd > 'a';
+ if (*cmd == ',')
+ t = !t;
+ if ((ncursor = findch(fsavech, argcnt, t, i)) < 0)
+ return -1;
+ if (sub && t)
+ ncursor++;
+ break;
+
+ case 'h':
+ case Ctrl('h'):
+ if (!sub && es->cursor == 0)
+ return -1;
+ ncursor = es->cursor - argcnt;
+ if (ncursor < 0)
+ ncursor = 0;
+ break;
+
+ case ' ':
+ case 'l':
+ if (!sub && es->cursor + 1 >= es->linelen)
+ return -1;
+ if (es->linelen != 0) {
+ ncursor = es->cursor + argcnt;
+ if (ncursor > es->linelen)
+ ncursor = es->linelen;
+ }
+ break;
+
+ case 'w':
+ if (!sub && es->cursor + 1 >= es->linelen)
+ return -1;
+ ncursor = forwword(argcnt);
+ break;
+
+ case 'W':
+ if (!sub && es->cursor + 1 >= es->linelen)
+ return -1;
+ ncursor = Forwword(argcnt);
+ break;
+
+ case '0':
+ ncursor = 0;
+ break;
+
+ case '^':
+ ncursor = 0;
+ while (ncursor < es->linelen - 1 &&
+ isspace((unsigned char)es->cbuf[ncursor]))
+ ncursor++;
+ break;
+
+ case '|':
+ ncursor = argcnt;
+ if (ncursor > es->linelen)
+ ncursor = es->linelen;
+ if (ncursor)
+ ncursor--;
+ break;
+
+ case '$':
+ if (es->linelen != 0)
+ ncursor = es->linelen;
+ else
+ ncursor = 0;
+ break;
+
+ case '%':
+ ncursor = es->cursor;
+ while (ncursor < es->linelen &&
+ (i = bracktype(es->cbuf[ncursor])) == 0)
+ ncursor++;
+ if (ncursor == es->linelen)
+ return -1;
+ bcount = 1;
+ do {
+ if (i > 0) {
+ if (++ncursor >= es->linelen)
+ return -1;
+ } else {
+ if (--ncursor < 0)
+ return -1;
+ }
+ t = bracktype(es->cbuf[ncursor]);
+ if (t == i)
+ bcount++;
+ else if (t == -i)
+ bcount--;
+ } while (bcount != 0);
+ if (sub && i > 0)
+ ncursor++;
+ break;
+
+ default:
+ return -1;
+ }
+ return ncursor;
+}
+
+static int
+redo_insert(int count)
+{
+ while (count-- > 0)
+ if (putbuf(ibuf, inslen, insert==REPLACE) != 0)
+ return -1;
+ if (es->cursor > 0)
+ es->cursor--;
+ insert = 0;
+ return 0;
+}
+
+static void
+yank_range(int a, int b)
+{
+ yanklen = b - a;
+ if (yanklen != 0)
+ memmove(ybuf, &es->cbuf[a], yanklen);
+}
+
+static int
+bracktype(int ch)
+{
+ switch (ch) {
+
+ case '(':
+ return 1;
+
+ case '[':
+ return 2;
+
+ case '{':
+ return 3;
+
+ case ')':
+ return -1;
+
+ case ']':
+ return -2;
+
+ case '}':
+ return -3;
+
+ default:
+ return 0;
+ }
+}
+
+/*
+ * Non user interface editor routines below here
+ */
+
+static int cur_col; /* current column on line */
+static int pwidth; /* width of prompt */
+static int prompt_trunc; /* how much of prompt to truncate */
+static int prompt_skip; /* how much of prompt to skip */
+static int winwidth; /* width of window */
+static char *wbuf[2]; /* window buffers */
+static int wbuf_len; /* length of window buffers (x_cols-3)*/
+static int win; /* window buffer in use */
+static char morec; /* more character at right of window */
+static int lastref; /* argument to last refresh() */
+static char holdbuf[CMDLEN]; /* place to hold last edit buffer */
+static int holdlen; /* length of holdbuf */
+
+static void
+save_cbuf(void)
+{
+ memmove(holdbuf, es->cbuf, es->linelen);
+ holdlen = es->linelen;
+ holdbuf[holdlen] = '\0';
+}
+
+static void
+restore_cbuf(void)
+{
+ es->cursor = 0;
+ es->linelen = holdlen;
+ memmove(es->cbuf, holdbuf, holdlen);
+}
+
+/* return a new edstate */
+static struct edstate *
+save_edstate(struct edstate *old)
+{
+ struct edstate *new;
+
+ new = (struct edstate *)alloc(sizeof(struct edstate), APERM);
+ new->cbuf = alloc(old->cbufsize, APERM);
+ memcpy(new->cbuf, old->cbuf, old->linelen);
+ new->cbufsize = old->cbufsize;
+ new->linelen = old->linelen;
+ new->cursor = old->cursor;
+ new->winleft = old->winleft;
+ return new;
+}
+
+static void
+restore_edstate(struct edstate *new, struct edstate *old)
+{
+ memcpy(new->cbuf, old->cbuf, old->linelen);
+ new->linelen = old->linelen;
+ new->cursor = old->cursor;
+ new->winleft = old->winleft;
+ free_edstate(old);
+}
+
+static void
+free_edstate(struct edstate *old)
+{
+ afree(old->cbuf, APERM);
+ afree((char *)old, APERM);
+}
+
+
+
+static void
+edit_reset(char *buf, size_t len)
+{
+ const char *p;
+
+ es = &ebuf;
+ es->cbuf = buf;
+ es->cbufsize = len;
+ undo = &undobuf;
+ undo->cbufsize = len;
+
+ es->linelen = undo->linelen = 0;
+ es->cursor = undo->cursor = 0;
+ es->winleft = undo->winleft = 0;
+
+ cur_col = pwidth = promptlen(prompt, &p);
+ prompt_skip = p - prompt;
+ if (pwidth > x_cols - 3 - MIN_EDIT_SPACE) {
+ cur_col = x_cols - 3 - MIN_EDIT_SPACE;
+ prompt_trunc = pwidth - cur_col;
+ pwidth -= prompt_trunc;
+ } else
+ prompt_trunc = 0;
+ if (!wbuf_len || wbuf_len != x_cols - 3) {
+ wbuf_len = x_cols - 3;
+ wbuf[0] = aresize(wbuf[0], wbuf_len, APERM);
+ wbuf[1] = aresize(wbuf[1], wbuf_len, APERM);
+ }
+ (void) memset(wbuf[0], ' ', wbuf_len);
+ (void) memset(wbuf[1], ' ', wbuf_len);
+ winwidth = x_cols - pwidth - 3;
+ win = 0;
+ morec = ' ';
+ lastref = 1;
+ holdlen = 0;
+}
+
+/*
+ * this is used for calling x_escape() in complete_word()
+ */
+static int
+x_vi_putbuf(const char *s, size_t len)
+{
+ return putbuf(s, len, 0);
+}
+
+static int
+putbuf(const char *buf, int len, int repl)
+{
+ if (len == 0)
+ return 0;
+ if (repl) {
+ if (es->cursor + len >= es->cbufsize)
+ return -1;
+ if (es->cursor + len > es->linelen)
+ es->linelen = es->cursor + len;
+ } else {
+ if (es->linelen + len >= es->cbufsize)
+ return -1;
+ memmove(&es->cbuf[es->cursor + len], &es->cbuf[es->cursor],
+ es->linelen - es->cursor);
+ es->linelen += len;
+ }
+ memmove(&es->cbuf[es->cursor], buf, len);
+ es->cursor += len;
+ return 0;
+}
+
+static void
+del_range(int a, int b)
+{
+ if (es->linelen != b)
+ memmove(&es->cbuf[a], &es->cbuf[b], es->linelen - b);
+ es->linelen -= b - a;
+}
+
+static int
+findch(int ch, int cnt, int forw, int incl)
+{
+ int ncursor;
+
+ if (es->linelen == 0)
+ return -1;
+ ncursor = es->cursor;
+ while (cnt--) {
+ do {
+ if (forw) {
+ if (++ncursor == es->linelen)
+ return -1;
+ } else {
+ if (--ncursor < 0)
+ return -1;
+ }
+ } while (es->cbuf[ncursor] != ch);
+ }
+ if (!incl) {
+ if (forw)
+ ncursor--;
+ else
+ ncursor++;
+ }
+ return ncursor;
+}
+
+static int
+forwword(int argcnt)
+{
+ int ncursor;
+
+ ncursor = es->cursor;
+ while (ncursor < es->linelen && argcnt--) {
+ if (is_wordch(es->cbuf[ncursor]))
+ while (is_wordch(es->cbuf[ncursor]) &&
+ ncursor < es->linelen)
+ ncursor++;
+ else if (!isspace((unsigned char)es->cbuf[ncursor]))
+ while (!is_wordch(es->cbuf[ncursor]) &&
+ !isspace((unsigned char)es->cbuf[ncursor]) &&
+ ncursor < es->linelen)
+ ncursor++;
+ while (isspace((unsigned char)es->cbuf[ncursor]) &&
+ ncursor < es->linelen)
+ ncursor++;
+ }
+ return ncursor;
+}
+
+static int
+backword(int argcnt)
+{
+ int ncursor;
+
+ ncursor = es->cursor;
+ while (ncursor > 0 && argcnt--) {
+ while (--ncursor > 0 && isspace((unsigned char)es->cbuf[ncursor]))
+ ;
+ if (ncursor > 0) {
+ if (is_wordch(es->cbuf[ncursor]))
+ while (--ncursor >= 0 &&
+ is_wordch(es->cbuf[ncursor]))
+ ;
+ else
+ while (--ncursor >= 0 &&
+ !is_wordch(es->cbuf[ncursor]) &&
+ !isspace((unsigned char)es->cbuf[ncursor]))
+ ;
+ ncursor++;
+ }
+ }
+ return ncursor;
+}
+
+static int
+endword(int argcnt)
+{
+ int ncursor;
+
+ ncursor = es->cursor;
+ while (ncursor < es->linelen && argcnt--) {
+ while (++ncursor < es->linelen - 1 &&
+ isspace((unsigned char)es->cbuf[ncursor]))
+ ;
+ if (ncursor < es->linelen - 1) {
+ if (is_wordch(es->cbuf[ncursor]))
+ while (++ncursor < es->linelen &&
+ is_wordch(es->cbuf[ncursor]))
+ ;
+ else
+ while (++ncursor < es->linelen &&
+ !is_wordch(es->cbuf[ncursor]) &&
+ !isspace((unsigned char)es->cbuf[ncursor]))
+ ;
+ ncursor--;
+ }
+ }
+ return ncursor;
+}
+
+static int
+Forwword(int argcnt)
+{
+ int ncursor;
+
+ ncursor = es->cursor;
+ while (ncursor < es->linelen && argcnt--) {
+ while (!isspace((unsigned char)es->cbuf[ncursor]) &&
+ ncursor < es->linelen)
+ ncursor++;
+ while (isspace((unsigned char)es->cbuf[ncursor]) &&
+ ncursor < es->linelen)
+ ncursor++;
+ }
+ return ncursor;
+}
+
+static int
+Backword(int argcnt)
+{
+ int ncursor;
+
+ ncursor = es->cursor;
+ while (ncursor > 0 && argcnt--) {
+ while (--ncursor >= 0 &&
+ isspace((unsigned char)es->cbuf[ncursor]))
+ ;
+ while (ncursor >= 0 &&
+ !isspace((unsigned char)es->cbuf[ncursor]))
+ ncursor--;
+ ncursor++;
+ }
+ return ncursor;
+}
+
+static int
+Endword(int argcnt)
+{
+ int ncursor;
+
+ ncursor = es->cursor;
+ while (ncursor < es->linelen - 1 && argcnt--) {
+ while (++ncursor < es->linelen - 1 &&
+ isspace((unsigned char)es->cbuf[ncursor]))
+ ;
+ if (ncursor < es->linelen - 1) {
+ while (++ncursor < es->linelen &&
+ !isspace((unsigned char)es->cbuf[ncursor]))
+ ;
+ ncursor--;
+ }
+ }
+ return ncursor;
+}
+
+static int
+grabhist(int save, int n)
+{
+ char *hptr;
+
+ if (n < 0 || n > hlast)
+ return -1;
+ if (n == hlast) {
+ restore_cbuf();
+ ohnum = n;
+ return 0;
+ }
+ (void) histnum(n);
+ if ((hptr = *histpos()) == NULL) {
+ internal_errorf(0, "grabhist: bad history array");
+ return -1;
+ }
+ if (save)
+ save_cbuf();
+ if ((es->linelen = strlen(hptr)) >= es->cbufsize)
+ es->linelen = es->cbufsize - 1;
+ memmove(es->cbuf, hptr, es->linelen);
+ es->cursor = 0;
+ ohnum = n;
+ return 0;
+}
+
+static int
+grabsearch(int save, int start, int fwd, char *pat)
+{
+ char *hptr;
+ int hist;
+ int anchored;
+
+ if ((start == 0 && fwd == 0) || (start >= hlast-1 && fwd == 1))
+ return -1;
+ if (fwd)
+ start++;
+ else
+ start--;
+ anchored = *pat == '^' ? (++pat, 1) : 0;
+ if ((hist = findhist(start, fwd, pat, anchored)) < 0) {
+ /* if (start != 0 && fwd && match(holdbuf, pat) >= 0) { */
+ /* XXX should strcmp be strncmp? */
+ if (start != 0 && fwd && strcmp(holdbuf, pat) >= 0) {
+ restore_cbuf();
+ return 0;
+ } else
+ return -1;
+ }
+ if (save)
+ save_cbuf();
+ histnum(hist);
+ hptr = *histpos();
+ if ((es->linelen = strlen(hptr)) >= es->cbufsize)
+ es->linelen = es->cbufsize - 1;
+ memmove(es->cbuf, hptr, es->linelen);
+ es->cursor = 0;
+ return hist;
+}
+
+static void
+redraw_line(int newline)
+{
+ (void) memset(wbuf[win], ' ', wbuf_len);
+ if (newline) {
+ x_putc('\r');
+ x_putc('\n');
+ }
+ vi_pprompt(0);
+ cur_col = pwidth;
+ morec = ' ';
+}
+
+static void
+refresh(int leftside)
+{
+ if (leftside < 0)
+ leftside = lastref;
+ else
+ lastref = leftside;
+ if (outofwin())
+ rewindow();
+ display(wbuf[1 - win], wbuf[win], leftside);
+ win = 1 - win;
+}
+
+static int
+outofwin(void)
+{
+ int cur, col;
+
+ if (es->cursor < es->winleft)
+ return 1;
+ col = 0;
+ cur = es->winleft;
+ while (cur < es->cursor)
+ col = newcol((unsigned char) es->cbuf[cur++], col);
+ if (col >= winwidth)
+ return 1;
+ return 0;
+}
+
+static void
+rewindow(void)
+{
+ int tcur, tcol;
+ int holdcur1, holdcol1;
+ int holdcur2, holdcol2;
+
+ holdcur1 = holdcur2 = tcur = 0;
+ holdcol1 = holdcol2 = tcol = 0;
+ while (tcur < es->cursor) {
+ if (tcol - holdcol2 > winwidth / 2) {
+ holdcur1 = holdcur2;
+ holdcol1 = holdcol2;
+ holdcur2 = tcur;
+ holdcol2 = tcol;
+ }
+ tcol = newcol((unsigned char) es->cbuf[tcur++], tcol);
+ }
+ while (tcol - holdcol1 > winwidth / 2)
+ holdcol1 = newcol((unsigned char) es->cbuf[holdcur1++],
+ holdcol1);
+ es->winleft = holdcur1;
+}
+
+static int
+newcol(int ch, int col)
+{
+ if (ch == '\t')
+ return (col | 7) + 1;
+ return col + char_len(ch);
+}
+
+static void
+display(char *wb1, char *wb2, int leftside)
+{
+ unsigned char ch;
+ char *twb1, *twb2, mc;
+ int cur, col, cnt;
+ int ncol = 0;
+ int moreright;
+
+ col = 0;
+ cur = es->winleft;
+ moreright = 0;
+ twb1 = wb1;
+ while (col < winwidth && cur < es->linelen) {
+ if (cur == es->cursor && leftside)
+ ncol = col + pwidth;
+ if ((ch = es->cbuf[cur]) == '\t') {
+ do {
+ *twb1++ = ' ';
+ } while (++col < winwidth && (col & 7) != 0);
+ } else {
+ if ((ch & 0x80) && Flag(FVISHOW8)) {
+ *twb1++ = 'M';
+ if (++col < winwidth) {
+ *twb1++ = '-';
+ col++;
+ }
+ ch &= 0x7f;
+ }
+ if (col < winwidth) {
+ if (ch < ' ' || ch == 0x7f) {
+ *twb1++ = '^';
+ if (++col < winwidth) {
+ *twb1++ = ch ^ '@';
+ col++;
+ }
+ } else {
+ *twb1++ = ch;
+ col++;
+ }
+ }
+ }
+ if (cur == es->cursor && !leftside)
+ ncol = col + pwidth - 1;
+ cur++;
+ }
+ if (cur == es->cursor)
+ ncol = col + pwidth;
+ if (col < winwidth) {
+ while (col < winwidth) {
+ *twb1++ = ' ';
+ col++;
+ }
+ } else
+ moreright++;
+ *twb1 = ' ';
+
+ col = pwidth;
+ cnt = winwidth;
+ twb1 = wb1;
+ twb2 = wb2;
+ while (cnt--) {
+ if (*twb1 != *twb2) {
+ if (cur_col != col)
+ ed_mov_opt(col, wb1);
+ x_putc(*twb1);
+ cur_col++;
+ }
+ twb1++;
+ twb2++;
+ col++;
+ }
+ if (es->winleft > 0 && moreright)
+ /* POSIX says to use * for this but that is a globbing
+ * character and may confuse people; + is more innocuous
+ */
+ mc = '+';
+ else if (es->winleft > 0)
+ mc = '<';
+ else if (moreright)
+ mc = '>';
+ else
+ mc = ' ';
+ if (mc != morec) {
+ ed_mov_opt(pwidth + winwidth + 1, wb1);
+ x_putc(mc);
+ cur_col++;
+ morec = mc;
+ }
+ if (cur_col != ncol)
+ ed_mov_opt(ncol, wb1);
+}
+
+static void
+ed_mov_opt(int col, char *wb)
+{
+ if (col < cur_col) {
+ if (col + 1 < cur_col - col) {
+ x_putc('\r');
+ vi_pprompt(0);
+ cur_col = pwidth;
+ while (cur_col++ < col)
+ x_putc(*wb++);
+ } else {
+ while (cur_col-- > col)
+ x_putc('\b');
+ }
+ } else {
+ wb = &wb[cur_col - pwidth];
+ while (cur_col++ < col)
+ x_putc(*wb++);
+ }
+ cur_col = col;
+}
+
+
+/* replace word with all expansions (ie, expand word*) */
+static int
+expand_word(int command)
+{
+ static struct edstate *buf;
+ int rval = 0;
+ int nwords;
+ int start, end;
+ char **words;
+ int i;
+
+ /* Undo previous expansion */
+ if (command == 0 && expanded == EXPAND && buf) {
+ restore_edstate(es, buf);
+ buf = 0;
+ expanded = NONE;
+ return 0;
+ }
+ if (buf) {
+ free_edstate(buf);
+ buf = 0;
+ }
+
+ nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
+ es->cbuf, es->linelen, es->cursor,
+ &start, &end, &words, (int *) 0);
+ if (nwords == 0) {
+ vi_error();
+ return -1;
+ }
+
+ buf = save_edstate(es);
+ expanded = EXPAND;
+ del_range(start, end);
+ es->cursor = start;
+ for (i = 0; i < nwords; ) {
+ if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
+ rval = -1;
+ break;
+ }
+ if (++i < nwords && putbuf(space, 1, 0) != 0) {
+ rval = -1;
+ break;
+ }
+ }
+ i = buf->cursor - end;
+ if (rval == 0 && i > 0)
+ es->cursor += i;
+ modified = 1; hnum = hlast;
+ insert = INSERT;
+ lastac = 0;
+ refresh(0);
+ return rval;
+}
+
+static int
+complete_word(int command, int count)
+{
+ static struct edstate *buf;
+ int rval = 0;
+ int nwords;
+ int start, end;
+ char **words;
+ char *match;
+ int match_len;
+ int is_unique;
+ int is_command;
+
+ /* Undo previous completion */
+ if (command == 0 && expanded == COMPLETE && buf) {
+ print_expansions(buf, 0);
+ expanded = PRINT;
+ return 0;
+ }
+ if (command == 0 && expanded == PRINT && buf) {
+ restore_edstate(es, buf);
+ buf = 0;
+ expanded = NONE;
+ return 0;
+ }
+ if (buf) {
+ free_edstate(buf);
+ buf = 0;
+ }
+
+ /* XCF_FULLPATH for count 'cause the menu printed by print_expansions()
+ * was done this way.
+ */
+ nwords = x_cf_glob(XCF_COMMAND_FILE | (count ? XCF_FULLPATH : 0),
+ es->cbuf, es->linelen, es->cursor,
+ &start, &end, &words, &is_command);
+ if (nwords == 0) {
+ vi_error();
+ return -1;
+ }
+ if (count) {
+ int i;
+
+ count--;
+ if (count >= nwords) {
+ vi_error();
+ x_print_expansions(nwords, words, is_command);
+ x_free_words(nwords, words);
+ redraw_line(0);
+ return -1;
+ }
+ /*
+ * Expand the count'th word to its basename
+ */
+ if (is_command) {
+ match = words[count] +
+ x_basename(words[count], (char *) 0);
+ /* If more than one possible match, use full path */
+ for (i = 0; i < nwords; i++)
+ if (i != count &&
+ strcmp(words[i] + x_basename(words[i],
+ (char *) 0), match) == 0) {
+ match = words[count];
+ break;
+ }
+ } else
+ match = words[count];
+ match_len = strlen(match);
+ is_unique = 1;
+ /* expanded = PRINT; next call undo */
+ } else {
+ match = words[0];
+ match_len = x_longest_prefix(nwords, words);
+ expanded = COMPLETE; /* next call will list completions */
+ is_unique = nwords == 1;
+ }
+
+ buf = save_edstate(es);
+ del_range(start, end);
+ es->cursor = start;
+
+ /* escape all shell-sensitive characters and put the result into
+ * command buffer */
+ rval = x_escape(match, match_len, x_vi_putbuf);
+
+ if (rval == 0 && is_unique) {
+ /* If exact match, don't undo. Allows directory completions
+ * to be used (ie, complete the next portion of the path).
+ */
+ expanded = NONE;
+
+ /* If not a directory, add a space to the end... */
+ if (match_len > 0 && match[match_len - 1] != '/')
+ rval = putbuf(space, 1, 0);
+ }
+ x_free_words(nwords, words);
+
+ modified = 1; hnum = hlast;
+ insert = INSERT;
+ lastac = 0; /* prevent this from being redone... */
+ refresh(0);
+
+ return rval;
+}
+
+static int
+print_expansions(struct edstate *e, int command)
+{
+ int nwords;
+ int start, end;
+ char **words;
+ int is_command;
+
+ nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
+ e->cbuf, e->linelen, e->cursor,
+ &start, &end, &words, &is_command);
+ if (nwords == 0) {
+ vi_error();
+ return -1;
+ }
+ x_print_expansions(nwords, words, is_command);
+ x_free_words(nwords, words);
+ redraw_line(0);
+ return 0;
+}
+
+/* How long is char when displayed (not counting tabs) */
+static int
+char_len(int c)
+{
+ int len = 1;
+
+ if ((c & 0x80) && Flag(FVISHOW8)) {
+ len += 2;
+ c &= 0x7f;
+ }
+ if (c < ' ' || c == 0x7f)
+ len++;
+ return len;
+}
+
+/* Similar to x_zotc(emacs.c), but no tab weirdness */
+static void
+x_vi_zotc(int c)
+{
+ if (Flag(FVISHOW8) && (c & 0x80)) {
+ x_puts("M-");
+ c &= 0x7f;
+ }
+ if (c < ' ' || c == 0x7f) {
+ x_putc('^');
+ c ^= '@';
+ }
+ x_putc(c);
+}
+
+static void
+vi_pprompt(int full)
+{
+ pprompt(prompt + (full ? 0 : prompt_skip), prompt_trunc);
+}
+
+static void
+vi_error(void)
+{
+ /* Beem out of any macros as soon as an error occurs */
+ vi_macro_reset();
+ x_putc(BEL);
+ x_flush();
+}
+
+static void
+vi_macro_reset(void)
+{
+ if (macro.p) {
+ afree(macro.buf, APERM);
+ memset((char *) ¯o, 0, sizeof(macro));
+ }
+}
+
+#endif /* VI */