From a34b05d2e6831deab1a088adfd2722d706a654c3 Mon Sep 17 00:00:00 2001 From: tg Date: Sat, 22 Mar 2003 17:35:03 +0000 Subject: [PATCH 1/2] Import OpenBSD 3.3 source repository from CTM 3132 the first time This opens an OpenBSD-mirabile (aka MirBSD) repository. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### MirBSD is: # Copyright (c) 1982-2003 by Thorsten "mirabile" Glaser # Copyright © 1968-2003 The authors of And contributors to UNIX®, the # C Language, BSD/Berkeley Unix; 386BSD, NetBSD 1.1 and OpenBSD. # # Anyone who obtained a copy of this work is hereby permitted to freely use, # distribute, modify, merge, sublicence, give away or sell it as long as the # authors are given due credit and the following notice is retained: # # This work is provided "as is", with no explicit or implicit warranty what- # soever. Use it only at your own risk. In no event may an author or contri- # butor be held liable for any damage, directly or indirectly, that origina- # ted through or is caused by creation or modification of this work. MirBSD is my private tree. MirBSD does not differ very much from OpenBSD and intentionally tracks OpenBSD. That's why it _is_ OpenBSD, just not the official one. It's like with DarrenBSD. At time of this writing, no advertising for MirBSD must be done, because the advertising clause has not yet been sorted out. http://templeofhate.com/tglaser/MirBSD/index.php --- BUG-REPORTS | 1398 +++++++++++++ CONTRIBUTORS | 129 ++ ChangeLog | 1600 ++++++++++++++ ChangeLog.0 | 3589 ++++++++++++++++++++++++++++++++ IAFA-PACKAGE | 18 + INSTALL | 151 ++ LEGAL | 36 + Makefile | 33 + NEWS | 662 ++++++ NOTES | 545 +++++ PROJECTS | 111 + README | 197 ++ alloc.c | 119 ++ c_ksh.c | 1475 +++++++++++++ c_sh.c | 906 ++++++++ c_test.c | 664 ++++++ c_test.h | 55 + c_ulimit.c | 273 +++ conf-end.h | 62 + config.h | 364 ++++ edit.c | 1081 ++++++++++ edit.h | 87 + emacs-gen.sh | 44 + emacs.c | 2199 ++++++++++++++++++++ eval.c | 1378 +++++++++++++ exec.c | 1729 ++++++++++++++++ expand.h | 107 + expr.c | 607 ++++++ history.c | 1192 +++++++++++ io.c | 560 +++++ jobs.c | 1851 +++++++++++++++++ ksh.1tbl | 5164 ++++++++++++++++++++++++++++++++++++++++++++++ ksh_dir.h | 26 + ksh_limval.h | 24 + ksh_stat.h | 59 + ksh_time.h | 26 + ksh_times.h | 20 + ksh_wait.h | 51 + lex.c | 1398 +++++++++++++ lex.h | 132 ++ mail.c | 205 ++ main.c | 863 ++++++++ misc.c | 1350 ++++++++++++ missing.c | 295 +++ path.c | 312 +++ proto.h | 303 +++ sh.1tbl | 3838 ++++++++++++++++++++++++++++++++++ sh.h | 747 +++++++ shf.c | 1295 ++++++++++++ shf.h | 86 + siglist.in | 56 + siglist.sh | 42 + syn.c | 947 +++++++++ table.c | 240 +++ table.h | 183 ++ tests/README | 22 + tests/alias.t | 112 + tests/arith.t | 79 + tests/bksl-nl.t | 341 +++ tests/brkcont.t | 195 ++ tests/cdhist.t | 162 ++ tests/eglob.t | 145 ++ tests/glob.t | 99 + tests/heredoc.t | 331 +++ tests/history.t | 559 +++++ tests/ifs.t | 162 ++ tests/integer.t | 218 ++ tests/lineno.t | 111 + tests/read.t | 58 + tests/regress.t | 1093 ++++++++++ tests/syntax.t | 10 + tests/th | 1206 +++++++++++ tests/th-sh | 30 + tests/th.sh | 30 + tests/unclass1.t | 99 + tests/unclass2.t | 163 ++ tests/version.t | 9 + trap.c | 451 ++++ tree.c | 760 +++++++ tree.h | 142 ++ tty.c | 179 ++ tty.h | 109 + var.c | 1236 +++++++++++ version.c | 10 + vi.c | 2190 ++++++++++++++++++++ 85 files changed, 51195 insertions(+) create mode 100644 BUG-REPORTS create mode 100644 CONTRIBUTORS create mode 100644 ChangeLog create mode 100644 ChangeLog.0 create mode 100644 IAFA-PACKAGE create mode 100644 INSTALL create mode 100644 LEGAL create mode 100644 Makefile create mode 100644 NEWS create mode 100644 NOTES create mode 100644 PROJECTS create mode 100644 README create mode 100644 alloc.c create mode 100644 c_ksh.c create mode 100644 c_sh.c create mode 100644 c_test.c create mode 100644 c_test.h create mode 100644 c_ulimit.c create mode 100644 conf-end.h create mode 100644 config.h create mode 100644 edit.c create mode 100644 edit.h create mode 100644 emacs-gen.sh create mode 100644 emacs.c create mode 100644 eval.c create mode 100644 exec.c create mode 100644 expand.h create mode 100644 expr.c create mode 100644 history.c create mode 100644 io.c create mode 100644 jobs.c create mode 100644 ksh.1tbl create mode 100644 ksh_dir.h create mode 100644 ksh_limval.h create mode 100644 ksh_stat.h create mode 100644 ksh_time.h create mode 100644 ksh_times.h create mode 100644 ksh_wait.h create mode 100644 lex.c create mode 100644 lex.h create mode 100644 mail.c create mode 100644 main.c create mode 100644 misc.c create mode 100644 missing.c create mode 100644 path.c create mode 100644 proto.h create mode 100644 sh.1tbl create mode 100644 sh.h create mode 100644 shf.c create mode 100644 shf.h create mode 100644 siglist.in create mode 100644 siglist.sh create mode 100644 syn.c create mode 100644 table.c create mode 100644 table.h create mode 100644 tests/README create mode 100644 tests/alias.t create mode 100644 tests/arith.t create mode 100644 tests/bksl-nl.t create mode 100644 tests/brkcont.t create mode 100644 tests/cdhist.t create mode 100644 tests/eglob.t create mode 100644 tests/glob.t create mode 100644 tests/heredoc.t create mode 100644 tests/history.t create mode 100644 tests/ifs.t create mode 100644 tests/integer.t create mode 100644 tests/lineno.t create mode 100644 tests/read.t create mode 100644 tests/regress.t create mode 100644 tests/syntax.t create mode 100644 tests/th create mode 100644 tests/th-sh create mode 100644 tests/th.sh create mode 100644 tests/unclass1.t create mode 100644 tests/unclass2.t create mode 100644 tests/version.t create mode 100644 trap.c create mode 100644 tree.c create mode 100644 tree.h create mode 100644 tty.c create mode 100644 tty.h create mode 100644 var.c create mode 100644 version.c create mode 100644 vi.c diff --git a/BUG-REPORTS b/BUG-REPORTS new file mode 100644 index 0000000..dfb126d --- /dev/null +++ b/BUG-REPORTS @@ -0,0 +1,1398 @@ +$OpenBSD: BUG-REPORTS,v 1.14 2003/02/28 09:45:09 jmc 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, 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 ): 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): scipts 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 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 fucntion 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): ^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 ): 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: * 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: = 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 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): / 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, = 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: 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: # 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, = on word with ~ + but no /, beeps (or prints final path comonent?). + [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 + not in . + [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 ; + or 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 ): + 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 ): + [ -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 ): + "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 ): 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 ): + 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 + contatenation. + [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 ) + 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 ) + ${..%..} 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 + ) + 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 + ) + 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 ): + 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 ): + 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 ): + 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: . 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 boardaries 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 ): + 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 ): + 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 ): 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 indicies 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 new file mode 100644 index 0000000..c6691a2 --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,129 @@ +$OpenBSD: CONTRIBUTORS,v 1.9 1999/07/14 13:37:23 millert 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 + (strut 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 (): added macros to vi (@char). + * Fritz Heinrichmeyer (): fixes + to allow compile under Linux 1.4.3. + * Gabor Zahemszky (): SVR3_PGRP vs SYSV_PGRP, many + bug reports and man page fixes. + * Dave Kinchlea (): DEFAULT_ENV patches. + * Paul Borman (): j_exit: send HUP, then CONT; HUP fg process. + * DaviD W. Sanderson (): patches to allow { .. } instead + of in .. esac in case statements. + * ? (): partial patches to handle SIGWINCH for command line + editing. + * Jason Tyler (): fixes for bugs in fc. + * Stefan Dalibor (): fix for + COLUMNS never being set in x_init(). + * Arnon Kanfi (): fix for prompt. + * Marc Olzheim (): patches to ifdef KSH the mail check + code and aliases; enum patches for old K&R compilers; handle missing dup2. + * Lars Hecking (): fixes so shell compiles as sh + again. + * Bill Kish (): added prompt delimiter hack for + hidden characters (eg, escape codes). + * Andrew S. Townley (): fixes for NeXT machines: + get a controlling if one needed, use correct profile. + * Eric J. Chet (): fix for core dump in . (quitenv() called + too soon). + * Greg A. Woods : fix to make ^[_ in emacs work + as in at&t ksh. + * George Robbins : fix for sh mode to + keep exec'd file descriptors open. + * George White : fix here-doc problem under OS/2 + (memory allocated incorrectly). + * David E. Wexelblat : fix to avoid memory overrun + in aresize(); fix to not print un-named options. + * Clifford Wolf (): fix memory overrun in aresize(); + fixed sys_siglist[] problem. + * Theo de Raadt (): allow ". /dev/null". + * Eric Youngdale (): flag field incorrectly changed + in exec.c(flushcom). + * Todd. C Miller (Todd C. Miller ): fix + for coredump in jobs. + * Kevin Schoedel : fix for word location in file + completion. + * Martin Lucina : fix for argument parsing in exit command, + fix for KSH_CHECK_H_TYPE. + * Mark Funkenhauser : added $LINENO support. + * Corinna Vinschen and Steven Hein : + port to cyngin environment on win95/winnt. + * Martin Dalecki : changes for 8 bit emacs mode. + * Dave Hillman : patch for bug in test -nt. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..d700b4a --- /dev/null +++ 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/fucntion. + * 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.14 2003/03/10 03:48:16 david 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 with / + 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 #. + * 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 *. + +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 new file mode 100644 index 0000000..3867056 --- /dev/null +++ b/ChangeLog.0 @@ -0,0 +1,3589 @@ +$OpenBSD: ChangeLog.0,v 1.4 2003/02/28 09:45:09 jmc 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 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 and only include + 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 . + * 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 @. + * vi.c(vi_hook,vi_cmd): added support for @ (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 , 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 . + + * 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 - 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 ) + + * emacs.c(x_transpose): changed behavior if FGMACS flag set + (fix from ). + +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 ). 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 ). + * 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 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 + ); 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 ). + + * ksh.1(DESCRIPTION): added missing P in \fP + (fix from Gordan Larson ). + +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 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 + with ). 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 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 new file mode 100644 index 0000000..dbd1af4 --- /dev/null +++ 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 new file mode 100644 index 0000000..ef960ac --- /dev/null +++ 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 new file mode 100644 index 0000000..d8325a3 --- /dev/null +++ b/LEGAL @@ -0,0 +1,36 @@ +$OpenBSD: LEGAL,v 1.1.1.1 1996/08/14 06:19:10 downsj 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 + These are covered by copyrighten by Simon J. Gerraty; + the copyright notice for these files is as follows: + This is free software. It comes with NO WARRANTY. + Permission to use, modify and distribute this source code + is granted subject to the following conditions. + 1/ that that the above copyright notice and this notice + are preserved in all copies and that due credit be given + to the author. + 2/ that any changes to this code are clearly commented + as such so that the author does get blamed for bugs + other than his own. + aclocal.m4 + This is covered by the GNU General Public Licence (GPL) + as it contains modified versions of macros that come with + GNU autoconf. As this is used solely for configuration, + the pdksh code itself is not covered by the GPL. + The following is taken from autoconf 2.x documentation + (info autoconf questions distributing) concerning use + of autoconf in programs: + + There are no restrictions on how the configuration + scripts that Autoconf produces may be distributed + or used. In Autoconf version 1, they were covered by + the GNU General Public License. We still encourage + software authors to distribute their work under terms + like those of the GPL, but doing so is not required + to use Autoconf. + +That's it. Short and simple. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ae58403 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +# $OpenBSD: Makefile,v 1.13 2002/04/22 21:44:58 miod 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 misc.c missing.c path.c shf.c syn.c table.c trap.c \ + tree.c tty.c var.c version.c vi.c + +DEFS= -DHAVE_CONFIG_H -Wall -Wno-unused +CFLAGS+=${DEFS} -I. -I${.CURDIR} -DKSH +MAN= ksh.1 sh.1 + +CLEANFILES+= siglist.out emacs.out + +LINKS= ${BINDIR}/ksh ${BINDIR}/rksh +LINKS+= ${BINDIR}/ksh ${BINDIR}/sh +MLINKS= ksh.1 rksh.1 ksh.1 ulimit.1 + +.depend trap.o: siglist.out +.depend emacs.o: emacs.out + +siglist.out: config.h sh.h siglist.in siglist.sh + /bin/sh ${.CURDIR}/siglist.sh \ + "${CPP} ${CPPFLAGS} ${DEFS} -I${.CURDIR}" \ + < ${.CURDIR}/siglist.in > siglist.out + +emacs.out: emacs.c + /bin/sh ${.CURDIR}/emacs-gen.sh ${.CURDIR}/emacs.c > emacs.out + +check test: + /bin/sh ${.CURDIR}/tests/th.sh ${.CURDIR}/tests/th -s ${.CURDIR}/tests -p ./ksh -C pdksh,sh,ksh,posix,posix-upu + +.include diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..51bc8ca --- /dev/null +++ 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: . 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.12 2003/02/28 09:45:09 jmc 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: # 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 " now edits , not 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: 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 || $(\) 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: @ 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 ^]). + +* 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 traped 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 new file mode 100644 index 0000000..49fc4b2 --- /dev/null +++ b/NOTES @@ -0,0 +1,545 @@ +$OpenBSD: NOTES,v 1.8 2003/02/26 03:53:35 david 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 occuring + 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+ - 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:-"*"}" + + $ + - (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: + $ + 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 new file mode 100644 index 0000000..07813a4 --- /dev/null +++ b/PROJECTS @@ -0,0 +1,111 @@ +$OpenBSD: PROJECTS,v 1.5 1999/07/14 13:37:23 millert 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 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. diff --git a/README b/README new file mode 100644 index 0000000..48e3fae --- /dev/null +++ b/README @@ -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, 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 new file mode 100644 index 0000000..19f9e86 --- /dev/null +++ b/alloc.c @@ -0,0 +1,119 @@ +/* $OpenBSD: alloc.c,v 1.5 2002/03/01 13:06:18 espie 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(size + sizeof(struct link)); + if (!l) + return NULL; + 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, size+sizeof(struct link)); + if (l2) { + 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; + + if (!ptr) + return; + + l = P2L(ptr); + + 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 new file mode 100644 index 0000000..818b3c2 --- /dev/null +++ b/c_ksh.c @@ -0,0 +1,1475 @@ +/* $OpenBSD: c_ksh.c,v 1.16 2003/02/28 09:45:09 jmc Exp $ */ + +/* + * built-in Korn commands: c_* + */ + +#include "sh.h" +#include "ksh_stat.h" +#include + +#ifdef __CYGWIN__ +#include +#endif /* __CYGWIN__ */ + +int +c_cd(wp) + 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; + + while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != EOF) + 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; + } + /* substitue 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; + 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); +#ifdef S_ISLNK + if (physical) + rval = chdir(try = Xstring(xs, xp) + phys_path); + else +#endif /* S_ISLNK */ + { + 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)); + 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 (!ISABSPATH(Xstring(xs, xp))) { +#ifdef OS2 + /* simplify_path() doesn't know about os/2's drive contexts, + * so it can't set current_wd when changing to a:foo. + * Handle this by calling getcwd()... + */ + pwd = ksh_get_wd((char *) 0, 0); +#else /* OS2 */ + pwd = (char *) 0; +#endif /* OS2 */ + } else +#ifdef S_ISLNK + if (!physical || !(pwd = get_phys_path(Xstring(xs, xp)))) +#endif /* S_ISLNK */ + pwd = Xstring(xs, xp); + + /* Set PWD */ + if (pwd) { +#ifdef __CYGWIN__ + char ptmp[PATH]; /* larger than MAX_PATH */ + cygwin_conv_to_full_posix_path(pwd, ptmp); +#else /* __CYGWIN__ */ + char *ptmp = pwd; +#endif /* __CYGWIN__ */ + 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); + + return 0; +} + +int +c_pwd(wp) + char **wp; +{ + int optc; + int physical = Flag(FPHYSICAL); + char *p; + + while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != EOF) + 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; + } +#ifdef S_ISLNK + p = current_wd[0] ? (physical ? get_phys_path(current_wd) : current_wd) + : (char *) 0; +#else /* S_ISLNK */ + p = current_wd[0] ? current_wd : (char *) 0; +#endif /* S_ISLNK */ + if (p && eaccess(p, R_OK) < 0) + p = (char *) 0; + if (!p) { + 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); + return 0; +} + +int +c_print(wp) + 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 */ +#define PO_FSLASH BIT(5) /* swap slash for backslash (for os2 ) */ + 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; + 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; +#if OS2 + const char *options = "Rnpfrsu,"; /* added f flag */ +#else + const char *options = "Rnprsu,"; +#endif + while ((optc = ksh_getopt(wp, &builtin_opt, options)) != EOF) + switch (optc) { + case 'R': /* fake BSD echo command */ + flags |= PO_PMINUSMINUS; + flags &= ~PO_EXPAND; + options = "ne"; + break; + case 'e': + flags |= PO_EXPAND; + break; +#ifdef OS2 + case 'f': + flags |= PO_FSLASH; + break; +#endif + case 'n': + flags &= ~PO_NL; + break; +#ifdef KSH + case 'p': + if ((fd = coproc_getfd(W_OK, &emsg)) < 0) { + bi_errorf("-p: %s", emsg); + return 1; + } + break; +#endif /* KSH */ + 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) { + register int c; + s = *wp; + while ((c = *s++) != '\0') { + Xcheck(xs, xp); +#ifdef OS2 + if ((flags & PO_FSLASH) && c == '\\') + if (*s == '\\') + *s++; + else + c = '/'; +#endif /* OS2 */ + 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 UNINITIALIZED(opipe); +#ifdef KSH + + /* 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(); + } +#endif /* KSH */ + for (s = Xstring(xs, xp); len > 0; ) { + n = write(fd, s, len); + if (n < 0) { +#ifdef KSH + if (flags & PO_COPROC) + restore_pipe(opipe); +#endif /* KSH */ + if (errno == EINTR) { + /* allow user to ^C out */ + intrcheck(); +#ifdef KSH + if (flags & PO_COPROC) + opipe = block_pipe(); +#endif /* KSH */ + continue; + } +#ifdef KSH + /* This doesn't really make sense - could + * break scripts (print -p generates + * error message). + *if (errno == EPIPE) + * coproc_write_close(fd); + */ +#endif /* KSH */ + return 1; + } + s += n; + len -= n; + } +#ifdef KSH + if (flags & PO_COPROC) + restore_pipe(opipe); +#endif /* KSH */ + } + + return 0; +} + +int +c_whence(wp) + 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)) != EOF) + 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 = tsearch(&keywords, id, hash(id)); + if (!tp && !pflag) { + tp = tsearch(&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(wp) + 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(wp) + 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)) != EOF) { + 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. + * typset -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 = tsort(&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 = tsort(&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(wp) + 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")) != EOF) { + 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 = tsort(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 = tsearch(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 = tenter(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(wp) + char **wp; +{ + register struct table *t = &aliases; + register struct tbl *ap; + int rv = 0, all = 0; + int optc; + + while ((optc = ksh_getopt(wp, &builtin_opt, "adt")) != EOF) + 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 = tsearch(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 (twalk(&ts, t); (ap = tnext(&ts)); ) { + if (ap->flag&ALLOC) { + ap->flag &= ~(ALLOC|ISSET); + afree((void*)ap->val.s, APERM); + } + ap->flag &= ~(DEFINED|ISSET|EXPORT); + } + } + + return rv; +} + +#ifdef KSH +int +c_let(wp) + 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)) { + rv = 2; /* distinguish error from zero result */ + break; + } else + rv = val == 0; + return rv; +} +#endif /* KSH */ + +int +c_jobs(wp) + char **wp; +{ + int optc; + int flag = 0; + int nflag = 0; + int rv = 0; + + while ((optc = ksh_getopt(wp, &builtin_opt, "lpnz")) != EOF) + 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(wp) + char **wp; +{ + int bg = strcmp(*wp, "bg") == 0; + int UNINITIALIZED(rv); + + 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 ARGS((void *arg, int i, char *buf, int buflen)); + +/* format a single kill item */ +static char * +kill_fmt_entry(arg, i, buf, buflen) + 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(wp) + 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(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:")) != EOF) + 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 ] {pid|job}...\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 + SIGNALS) + n -= 128; + if (n > 0 && n < SIGNALS && sigtraps[n].name) + shprintf("%s\n", sigtraps[n].name); + else + shprintf("%d\n", n); + } + } else if (Flag(FPOSIX)) { + p = null; + for (i = 1; i < SIGNALS; 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 = SIGNALS, ki.num_width = 1; i >= 10; i /= 10) + ki.num_width++; + ki.name_width = mess_width = 0; + for (i = 0; i < SIGNALS; 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, SIGNALS - 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(val) + 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(wp) + 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(wp) + char **wp; +{ + int rv = 0, macro = 0, list = 0; + register char *cp; + int optc; + + while ((optc = ksh_getopt(wp, &builtin_opt, "lm")) != EOF) + 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}, +#ifdef KSH + {"let", c_let}, +#endif /* KSH */ + {"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 new file mode 100644 index 0000000..50f9d9c --- /dev/null +++ b/c_sh.c @@ -0,0 +1,906 @@ +/* $OpenBSD: c_sh.c,v 1.17 2003/03/13 09:03:07 deraadt Exp $ */ + +/* + * built-in Bourne commands + */ + +#include "sh.h" +#include "ksh_stat.h" /* umask() */ +#include "ksh_time.h" +#include "ksh_times.h" + +static char *clocktos ARGS((clock_t t)); + + +/* :, false and true */ +int +c_label(wp) + char **wp; +{ + return wp[0][0] == 'f' ? 1 : 0; +} + +int +c_shift(wp) + char **wp; +{ + register struct block *l = e->loc; + register 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); + 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(wp) + char **wp; +{ + register int i; + register char *cp; + int symbolic = 0; + int old_umask; + int optc; + + while ((optc = ksh_getopt(wp, &builtin_opt, "S")) != EOF) + 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 { + int 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(wp) + 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(wp) + char **wp; +{ + int UNINITIALIZED(rv); + 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(wp) + char **wp; +{ + register int c = 0; + int expand = 1, history = 0; + int expanding; + int ecode = 0; + register char *cp; + int fd = 0; + struct shf *shf; + int optc; + const char *emsg; + XString cs, xs; + struct tbl *vp; + char UNINITIALIZED(*xp); + + while ((optc = ksh_getopt(wp, &builtin_opt, "prsu,")) != EOF) + switch (optc) { +#ifdef KSH + case 'p': + if ((fd = coproc_getfd(R_OK, &emsg)) < 0) { + bi_errorf("-p: %s", emsg); + return 1; + } + break; +#endif /* KSH */ + 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); + } + } + +#ifdef KSH + /* 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); + */ +#endif /* KSH */ + + 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' +#ifdef OS2 + || c == '\r' +#endif /* OS2 */ + ) + 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); + } +#ifdef KSH + /* 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); +#endif /* KSH */ + + return ecode ? ecode : c == EOF; +} + +int +c_eval(wp) + char **wp; +{ + register struct source *s; + + 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; + } + + return shell(s, FALSE); +} + +int +c_trap(wp) + char **wp; +{ + int i; + char *s; + register Trap *p; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return 1; + wp += builtin_opt.optind; + + if (*wp == NULL) { + int anydfl = 0; + + for (p = sigtraps, i = SIGNALS+1; --i >= 0; p++) { + if (p->trap == NULL) + anydfl = 1; + else { + shprintf("trap -- "); + print_value_quoted(p->trap); + shprintf(" %s\n", p->name); + } + } +#if 0 /* this is ugly and not clear POSIX needs it */ + /* POSIX may need this so output of trap can be saved and + * used to restore trap conditions + */ + if (anydfl) { + shprintf("trap -- -"); + for (p = sigtraps, i = SIGNALS+1; --i >= 0; p++) + if (p->trap == NULL && p->name) + shprintf(" %s", p->name); + shprintf(newline); + } +#endif + 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(wp) + 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(); /* get rid of any i/o redirections */ + unwind(how); + /*NOTREACHED*/ + return 0; +} + +int +c_brkcont(wp) + 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. + */ + 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(wp) + char **wp; +{ + int argi, setargs; + struct block *l = e->loc; + register 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(wp) + char **wp; +{ + register char *id; + int optc, unset_var = 1; + int ret = 0; + + while ((optc = ksh_getopt(wp, &builtin_opt, "fv")) != EOF) + 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 & ISSET)) + ret = 1; + if ((vp->flag&RDONLY)) { + bi_errorf("%s is read only", vp->name); + return 1; + } + unset(vp, strchr(id, '[') ? 1 : 0); + } else { /* unset function */ + if (define(id, (struct op *) NULL)) + ret = 1; + } + return ret; +} + +int +c_times(wp) + char **wp; +{ + struct tms all; + + (void) ksh_times(&all); + shprintf("Shell: %8ss user ", clocktos(all.tms_utime)); + shprintf("%8ss system\n", clocktos(all.tms_stime)); + shprintf("Kids: %8ss user ", clocktos(all.tms_cutime)); + shprintf("%8ss system\n", clocktos(all.tms_cstime)); + + return 0; +} + +/* + * time pipeline (really a statement, not a built-in command) + */ +int +timex(t, f) + struct op *t; + int f; +{ +#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 tms t0, t1, tms; + clock_t t0t, t1t = 0; + int tf = 0; + extern clock_t j_usrtime, j_systime; /* computed by j_wait */ + char opts[1]; + + t0t = ksh_times(&t0); + 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). + */ + j_usrtime = j_systime = 0; + if (t->left->type == TCOM) + t->left->str = opts; + opts[0] = 0; + rv = execute(t->left, f | XTIME); + tf |= opts[0]; + t1t = ksh_times(&t1); + } else + tf = TF_NOARGS; + + if (tf & TF_NOARGS) { /* ksh93 - report shell times (shell+kids) */ + tf |= TF_NOREAL; + tms.tms_utime = t0.tms_utime + t0.tms_cutime; + tms.tms_stime = t0.tms_stime + t0.tms_cstime; + } else { + tms.tms_utime = t1.tms_utime - t0.tms_utime + j_usrtime; + tms.tms_stime = t1.tms_stime - t0.tms_stime + j_systime; + } + + if (!(tf & TF_NOREAL)) + shf_fprintf(shl_out, + tf & TF_POSIX ? "real %8s\n" : "%8ss real ", + clocktos(t1t - t0t)); + shf_fprintf(shl_out, tf & TF_POSIX ? "user %8s\n" : "%8ss user ", + clocktos(tms.tms_utime)); + shf_fprintf(shl_out, tf & TF_POSIX ? "sys %8s\n" : "%8ss system\n", + clocktos(tms.tms_stime)); + shf_flush(shl_out); + + return rv; +} + +void +timex_hook(t, app) + 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")) != EOF) + 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; +} + +static char * +clocktos(t) + clock_t t; +{ + static char temp[22]; /* enough for 64 bit clock_t */ + register int i; + register char *cp = temp + sizeof(temp); + + /* note: posix says must use max precision, ie, if clk_tck is + * 1000, must print 3 places after decimal (if non-zero, else 1). + */ + if (CLK_TCK != 100) /* convert to 1/100'ths */ + t = (t < 1000000000/CLK_TCK) ? + (t * 100) / CLK_TCK : (t / CLK_TCK) * 100; + + *--cp = '\0'; + for (i = -2; i <= 0 || t > 0; i++) { + if (i == 0) + *--cp = '.'; + *--cp = '0' + (char)(t%10); + t /= 10; + } + return cp; +} + +/* exec with no args - args case is taken care of in comexec() */ +int +c_exec(wp) + 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). + */ +#ifdef KSH + if (!Flag(FSH) && i > 2 && e->savefd[i]) + fd_clexec(i); +#endif /* KSH */ + } + e->savefd = NULL; + } + return 0; +} + +/* dummy function, special case in comexec() */ +int +c_builtin(wp) + char ** wp; +{ + return 0; +} + +extern int c_test ARGS((char **wp)); /* in c_test.c */ +extern int c_ulimit ARGS((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}, +#ifdef OS2 + /* In OS2, the first line of a file can be "extproc name", which + * tells the command interpreter (cmd.exe) to use name to execute + * the file. For this to be useful, ksh must ignore commands + * starting with extproc and this does the trick... + */ + {"extproc", c_label}, +#endif /* OS2 */ + {NULL, NULL} +}; diff --git a/c_test.c b/c_test.c new file mode 100644 index 0000000..4ac5db5 --- /dev/null +++ b/c_test.c @@ -0,0 +1,664 @@ +/* $OpenBSD: c_test.c,v 1.9 2003/02/28 09:45:09 jmc 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 "ksh_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 ::= +*/ + +#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 }, +#ifdef KSH + {"==", TO_STEQL }, +#endif /* KSH */ + {"!=", 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 ARGS((const char *path, struct stat *statb)); +static int test_eaccess ARGS((const char *path, int mode)); +static int test_oexpr ARGS((Test_env *te, int do_eval)); +static int test_aexpr ARGS((Test_env *te, int do_eval)); +static int test_nexpr ARGS((Test_env *te, int do_eval)); +static int test_primary ARGS((Test_env *te, int do_eval)); +static int ptest_isa ARGS((Test_env *te, Test_meta meta)); +static const char *ptest_getopnd ARGS((Test_env *te, Test_op op, int do_eval)); +static int ptest_eval ARGS((Test_env *te, Test_op op, const char *opnd1, + const char *opnd2, int do_eval)); +static void ptest_error ARGS((Test_env *te, int offset, const char *msg)); + +int +c_test(wp) + 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(te, meta, s) + 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(te, op, opnd1, opnd2, do_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 */ +#ifdef S_ISCHR + return test_stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode); +#else + return 0; +#endif + case TO_FILBDEV: /* -b */ +#ifdef S_ISBLK + return test_stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode); +#else + return 0; +#endif + case TO_FILFIFO: /* -p */ +#ifdef S_ISFIFO + return test_stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode); +#else + return 0; +#endif + case TO_FILSYM: /* -h -L */ +#ifdef S_ISLNK + return lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode); +#else + return 0; +#endif + case TO_FILSOCK: /* -S */ +#ifdef S_ISSOCK + return test_stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode); +#else + return 0; +#endif + case TO_FILCDF:/* -H HP context dependent files (directories) */ +#ifdef S_ISCDF + { + /* Append a + to filename and check to see if result is a + * setuid directory. CDF stuff in general is hookey, since + * it breaks for the following sequence: echo hi > foo+; + * mkdir foo; echo bye > foo/default; chmod u+s foo + * (foo+ refers to the file with hi in it, there is no way + * to get at the file with bye in it - please correct me if + * I'm wrong about this). + */ + int len = strlen(opnd1); + char *p = str_nsave(opnd1, len + 1, ATEMP); + + p[len++] = '+'; + p[len] = '\0'; + return stat(p, &b1) == 0 && S_ISCDF(b1.st_mode); + } +#else + return 0; +#endif + case TO_FILSETU: /* -u */ +#ifdef S_ISUID + return test_stat(opnd1, &b1) == 0 + && (b1.st_mode & S_ISUID) == S_ISUID; +#else + return 0; +#endif + case TO_FILSETG: /* -g */ +#ifdef S_ISGID + return test_stat(opnd1, &b1) == 0 + && (b1.st_mode & S_ISGID) == S_ISGID; +#else + return 0; +#endif + 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) + || !evaluate(opnd2, &v2, KSH_RETURN_ERROR)) + { + /* 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(path, statb) + const char *path; + struct stat *statb; +{ +#if !defined(HAVE_DEV_FD) + int fd; + + if (strncmp(path, "/dev/fd/", 8) == 0 && getn(path + 8, &fd)) + return fstat(fd, statb); +#endif /* !HAVE_DEV_FD */ + + 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(path, mode) + const char *path; + int mode; +{ + int res; + +#if !defined(HAVE_DEV_FD) + int fd; + + /* Note: doesn't handle //dev/fd, etc.. (this is ok) */ + if (strncmp(path, "/dev/fd/", 8) == 0 && getn(path + 8, &fd)) { + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) < 0 + || (mode & X_OK) + || ((mode & W_OK) && (flags & O_ACCMODE) == O_RDONLY) + || ((mode & R_OK) && (flags & O_ACCMODE) == O_WRONLY)) + return -1; + return 0; + } +#endif /* !HAVE_DEV_FD */ + + /* On most (all?) unixes, access() says everything is executable for + * root - avoid this on files by using stat(). + */ + if ((mode & X_OK) && ksheuid == 0) { + 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; + /* Need to check other permissions? If so, use access() as + * this will deal with root on NFS. + */ + if (res == 0 && (mode & (R_OK|W_OK))) + res = eaccess(path, mode); + } else + res = eaccess(path, mode); + + return res; +} + +int +test_parse(te) + 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(te, do_eval) + 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(te, do_eval) + 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(te, do_eval) + 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(te, do_eval) + 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; + } + 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(te, meta) + 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(te, op, do_eval) + 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(te, op, opnd1, opnd2, do_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(te, offset, msg) + 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 new file mode 100644 index 0000000..f62495b --- /dev/null +++ b/c_test.h @@ -0,0 +1,55 @@ +/* $OpenBSD: c_test.h,v 1.1.1.1 1996/08/14 06:19:10 downsj Exp $ */ + +/* Various types of operations. Keeping things grouped nicely + * (unary,binary) makes switch() statements more efficeint. + */ +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) ARGS((Test_env *te, Test_meta meta)); + const char *(*getopnd) ARGS((Test_env *te, Test_op op, int do_eval)); + int (*eval) ARGS((Test_env *te, Test_op op, const char *opnd1, + const char *opnd2, int do_eval)); + void (*error) ARGS((Test_env *te, int offset, const char *msg)); +}; + +Test_op test_isop ARGS((Test_env *te, Test_meta meta, const char *s)); +int test_eval ARGS((Test_env *te, Test_op op, const char *opnd1, + const char *opnd2, int do_eval)); +int test_parse ARGS((Test_env *te)); diff --git a/c_ulimit.c b/c_ulimit.c new file mode 100644 index 0000000..a43a7f0 --- /dev/null +++ b/c_ulimit.c @@ -0,0 +1,273 @@ +/* $OpenBSD: c_ulimit.c,v 1.9 2002/06/09 05:47:27 todd Exp $ */ + +/* + ulimit -- handle "ulimit" builtin + + Reworked to use getrusage() and ulimit() at once (as needed on + some schizophenic 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 "ksh_time.h" +#ifdef HAVE_SYS_RESOURCE_H +# include +#endif /* HAVE_SYS_RESOURCE_H */ +#ifdef HAVE_ULIMIT_H +# include +#else /* HAVE_ULIMIT_H */ +# ifdef HAVE_ULIMIT +extern long ulimit(); +# endif /* HAVE_ULIMIT */ +#endif /* HAVE_ULIMIT_H */ + +#define SOFT 0x1 +#define HARD 0x2 + +#ifdef RLIM_INFINITY +# define KSH_RLIM_INFINITY RLIM_INFINITY +#else +# define KSH_RLIM_INFINITY ((rlim_t) 1 << (sizeof(rlim_t) * 8 - 1) - 1) +#endif /* RLIM_INFINITY */ + +int +c_ulimit(wp) + char **wp; +{ + static const struct limits { + const char *name; + enum { RLIMIT, ULIMIT } which; + int gcmd; /* get command */ + int scmd; /* set command (or -1, if no set command) */ + int factor; /* multiply by to get rlim_{cur,max} values */ + char option; + } limits[] = { + /* Do not use options -H, -S or -a */ +#ifdef RLIMIT_CPU + { "time(cpu-seconds)", RLIMIT, RLIMIT_CPU, RLIMIT_CPU, 1, 't' }, +#endif +#ifdef RLIMIT_FSIZE + { "file(blocks)", RLIMIT, RLIMIT_FSIZE, RLIMIT_FSIZE, 512, 'f' }, +#else /* RLIMIT_FSIZE */ +# ifdef UL_GETFSIZE /* x/open */ + { "file(blocks)", ULIMIT, UL_GETFSIZE, UL_SETFSIZE, 1, 'f' }, +# else /* UL_GETFSIZE */ +# ifdef UL_GFILLIM /* svr4/xenix */ + { "file(blocks)", ULIMIT, UL_GFILLIM, UL_SFILLIM, 1, 'f' }, +# else /* UL_GFILLIM */ + { "file(blocks)", ULIMIT, 1, 2, 1, 'f' }, +# endif /* UL_GFILLIM */ +# endif /* UL_GETFSIZE */ +#endif /* RLIMIT_FSIZE */ +#ifdef RLIMIT_CORE + { "coredump(blocks)", RLIMIT, RLIMIT_CORE, RLIMIT_CORE, 512, 'c' }, +#endif +#ifdef RLIMIT_DATA + { "data(kbytes)", RLIMIT, RLIMIT_DATA, RLIMIT_DATA, 1024, 'd' }, +#endif +#ifdef RLIMIT_STACK + { "stack(kbytes)", RLIMIT, RLIMIT_STACK, RLIMIT_STACK, 1024, 's' }, +#endif +#ifdef RLIMIT_MEMLOCK + { "lockedmem(kbytes)", RLIMIT, RLIMIT_MEMLOCK, RLIMIT_MEMLOCK, 1024, 'l' }, +#endif +#ifdef RLIMIT_RSS + { "memory(kbytes)", RLIMIT, RLIMIT_RSS, RLIMIT_RSS, 1024, 'm' }, +#endif +#ifdef RLIMIT_NOFILE + { "nofiles(descriptors)", RLIMIT, RLIMIT_NOFILE, RLIMIT_NOFILE, 1, 'n' }, +#else /* RLIMIT_NOFILE */ +# ifdef UL_GDESLIM /* svr4/xenix */ + { "nofiles(descriptors)", ULIMIT, UL_GDESLIM, -1, 1, 'n' }, +# endif /* UL_GDESLIM */ +#endif /* RLIMIT_NOFILE */ +#ifdef RLIMIT_NPROC + { "processes", RLIMIT, RLIMIT_NPROC, RLIMIT_NPROC, 1, 'p' }, +#endif +#ifdef RLIMIT_VMEM + { "vmemory(kbytes)", RLIMIT, RLIMIT_VMEM, RLIMIT_VMEM, 1024, 'v' }, +#else /* RLIMIT_VMEM */ + /* These are not quite right - really should subtract etext or something */ +# ifdef UL_GMEMLIM /* svr4/xenix */ + { "vmemory(maxaddr)", ULIMIT, UL_GMEMLIM, -1, 1, 'v' }, +# else /* UL_GMEMLIM */ +# ifdef UL_GETBREAK /* osf/1 */ + { "vmemory(maxaddr)", ULIMIT, UL_GETBREAK, -1, 1, 'v' }, +# else /* UL_GETBREAK */ +# ifdef UL_GETMAXBRK /* hpux */ + { "vmemory(maxaddr)", ULIMIT, UL_GETMAXBRK, -1, 1, 'v' }, +# endif /* UL_GETMAXBRK */ +# endif /* UL_GETBREAK */ +# endif /* UL_GMEMLIM */ +#endif /* RLIMIT_VMEM */ +#ifdef RLIMIT_SWAP + { "swap(kbytes)", RLIMIT_SWAP, RLIMIT_SWAP, 1024, 'w' }, +#endif + { (char *) 0 } + }; + static char options[3 + NELEM(limits)]; + rlim_t UNINITIALIZED(val); + int how = SOFT | HARD; + const struct limits *l; + int set, all = 0; + int optc, what; +#ifdef HAVE_SETRLIMIT + struct rlimit limit; +#endif /* HAVE_SETRLIMIT */ + + 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 = '\0'; + } + what = 'f'; + while ((optc = ksh_getopt(wp, &builtin_opt, options)) != EOF) + switch (optc) { + case 'H': + how = HARD; + break; + case 'S': + how = SOFT; + break; + case 'a': + all = 1; + break; + case '?': + return 1; + default: + what = optc; + } + + for (l = limits; l->name && l->option != what; l++) + ; + if (!l->name) { + internal_errorf(0, "ulimit: %c", what); + return 1; + } + + wp += builtin_opt.optind; + set = *wp ? 1 : 0; + if (set) { + if (all || wp[1]) { + bi_errorf("too many arguments"); + return 1; + } + if (strcmp(wp[0], "unlimited") == 0) + val = KSH_RLIM_INFINITY; + else { + long rval; + + if (!evaluate(wp[0], &rval, KSH_RETURN_ERROR)) + 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(wp[0][0])) { + bi_errorf("invalid limit: %s", wp[0]); + return 1; + } + val = rval * l->factor; + } + } + if (all) { + for (l = limits; l->name; l++) { +#ifdef HAVE_SETRLIMIT + if (l->which == RLIMIT) { + getrlimit(l->gcmd, &limit); + if (how & SOFT) + val = limit.rlim_cur; + else if (how & HARD) + val = limit.rlim_max; + } else +#endif /* HAVE_SETRLIMIT */ +#ifdef HAVE_ULIMIT + { + val = ulimit(l->gcmd, (rlim_t) 0); + } +#else /* HAVE_ULIMIT */ + ; +#endif /* HAVE_ULIMIT */ + shprintf("%-20s ", l->name); +#ifdef RLIM_INFINITY + if (val == RLIM_INFINITY) + shprintf("unlimited\n"); + else +#endif /* RLIM_INFINITY */ + { + val /= l->factor; + shprintf("%ld\n", (long) val); + } + } + return 0; + } +#ifdef HAVE_SETRLIMIT + if (l->which == RLIMIT) { + getrlimit(l->gcmd, &limit); + if (set) { + if (how & SOFT) + limit.rlim_cur = val; + if (how & HARD) + limit.rlim_max = val; + if (setrlimit(l->scmd, &limit) < 0) { + if (errno == EPERM) + bi_errorf("exceeds allowable limit"); + else + bi_errorf("bad limit: %s", + strerror(errno)); + return 1; + } + } else { + if (how & SOFT) + val = limit.rlim_cur; + else if (how & HARD) + val = limit.rlim_max; + } + } else +#endif /* HAVE_SETRLIMIT */ +#ifdef HAVE_ULIMIT + { + if (set) { + if (l->scmd == -1) { + bi_errorf("can't change limit"); + return 1; + } else if (ulimit(l->scmd, val) < 0) { + bi_errorf("bad limit: %s", strerror(errno)); + return 1; + } + } else + val = ulimit(l->gcmd, (rlim_t) 0); + } +#else /* HAVE_ULIMIT */ + ; +#endif /* HAVE_ULIMIT */ + if (!set) { +#ifdef RLIM_INFINITY + if (val == RLIM_INFINITY) + shprintf("unlimited\n"); + else +#endif /* RLIM_INFINITY */ + { + val /= l->factor; + shprintf("%ld\n", (long) val); + } + } + return 0; +} diff --git a/conf-end.h b/conf-end.h new file mode 100644 index 0000000..8b65cb7 --- /dev/null +++ b/conf-end.h @@ -0,0 +1,62 @@ +/* $OpenBSD: conf-end.h,v 1.2 1996/08/25 12:37:58 downsj Exp $ */ + +/* + * 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 */ + +/* + * if you don't have mmap() you can't use Peter Collinson's history + * mechanism. If that is the case, then define EASY_HISTORY + */ +#if defined(HISTORY) && (!defined(COMPLEX_HISTORY) || !defined(HAVE_MMAP) || !defined(HAVE_FLOCK)) +# undef COMPLEX_HISTORY +# define EASY_HISTORY /* sjg's trivial history file */ +#endif + +/* Can we safely catch sigchld and wait for processes? */ +#if (defined(HAVE_WAITPID) || defined(HAVE_WAIT3)) \ + && (defined(POSIX_SIGNALS) || defined(BSD42_SIGNALS)) +# define JOB_SIGS +#endif + +#if !defined(JOB_SIGS) || !(defined(POSIX_PGRP) || defined(BSD_PGRP)) +# undef JOBS /* if no JOB_SIGS, no job control support */ +#endif + +/* pdksh assumes system calls return EINTR if a signal happened (this so + * the signal handler doesn't have to longjmp()). I don't know if this + * happens (or can be made to happen) with sigset() et. al. (the bsd41 signal + * routines), so, the autoconf stuff checks what they do and defines + * SIGNALS_DONT_INTERRUPT if signals don't interrupt read(). + * If SIGNALS_DONT_INTERRUPT isn't defined and your compiler chokes on this, + * delete the hash in front of the error (and file a bug report). + */ +#ifdef SIGNALS_DONT_INTERRUPT + # error pdksh needs interruptable system calls. +#endif /* SIGNALS_DONT_INTERRUPT */ + +#ifdef HAVE_GCC_FUNC_ATTR +# define GCC_FUNC_ATTR(x) __attribute__((x)) +# define GCC_FUNC_ATTR2(x,y) __attribute__((x,y)) +#else +# define GCC_FUNC_ATTR(x) +# define GCC_FUNC_ATTR2(x,y) +#endif /* HAVE_GCC_FUNC_ATTR */ diff --git a/config.h b/config.h new file mode 100644 index 0000000..d484db7 --- /dev/null +++ b/config.h @@ -0,0 +1,364 @@ +/* $OpenBSD: config.h,v 1.7 2003/02/28 09:45:09 jmc Exp $ */ + +/* config.h. Generated automatically by configure. */ +/* config.h.in. Generated automatically from configure.in by autoheader. */ +/* + * This file, acconfig.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 + + +/* Define if on AIX 3. + System headers sometimes define this. + We just want to avoid a redefinition error message. */ +#ifndef _ALL_SOURCE +/* #undef _ALL_SOURCE */ +#endif + +/* Define if the closedir function returns void instead of int. */ +/* #undef CLOSEDIR_VOID */ + +/* Define to empty if the keyword does not work. */ +/* #undef const */ + + +/* Define to `int' if doesn't define. */ +/* #undef gid_t */ + +/* Define if you have a working `mmap' system call. */ +#define HAVE_MMAP 1 + +/* Define if your struct stat has st_rdev. */ +#define HAVE_ST_RDEV 1 + +/* Define if you have that is POSIX.1 compatible. */ +#define HAVE_SYS_WAIT_H 1 + +/* Define if you have . */ +#define HAVE_UNISTD_H 1 + +/* Define if on MINIX. */ +/* #undef _MINIX */ + +/* Define to `int' if doesn't define. */ +/* #undef mode_t */ + +/* Define to `long' if doesn't define. */ +/* #undef off_t */ + +/* Define to `int' if doesn't define. */ +/* #undef pid_t */ + +/* Define if the system does not provide POSIX.1 features except + with this defined. */ +/* #undef _POSIX_1_SOURCE */ + +/* Define if you need to in order for stat and other things to work. */ +/* #undef _POSIX_SOURCE */ + +/* Define as the return type of signal handlers (int or void). */ +#define RETSIGTYPE void + +/* Define if the `S_IS*' macros in do not work properly. */ +/* #undef STAT_MACROS_BROKEN */ + +/* Define if `sys_siglist' is declared by . */ +#define SYS_SIGLIST_DECLARED 1 + +/* Define if you can safely include both and . */ +#define TIME_WITH_SYS_TIME 1 + +/* Define to `int' if doesn't define. */ +/* #undef uid_t */ + +/* Define if the closedir function returns void instead of int. */ +/* #undef VOID_CLOSEDIR */ + +/* Define if your kernal doesn't handle scripts starting with #! */ +/* #undef SHARPBANG */ + +/* Define if dup2() preserves the close-on-exec flag (ultrix does this) */ +/* #undef DUP2_BROKEN */ + +/* Define as the return value of signal handlers (0 or ). */ +#define RETSIGVAL + +/* Define if you have posix signal routines (sigaction(), et. al.) */ +#define POSIX_SIGNALS 1 + +/* Define if you have BSD4.2 signal routines (sigsetmask(), et. al.) */ +/* #undef BSD42_SIGNALS */ + +/* Define if you have BSD4.1 signal routines (sigset(), et. al.) */ +/* #undef BSD41_SIGNALS */ + +/* Define if you have v7 signal routines (signal(), signal reset on delivery) */ +/* #undef V7_SIGNALS */ + +/* Define to use the fake posix signal routines (sigact.[ch]) */ +/* #undef USE_FAKE_SIGACT */ + +/* Define if signals don't interrupt read() */ +/* #undef SIGNALS_DONT_INTERRUPT */ + +/* Define if you have bsd versions of the setpgrp() and getpgrp() routines */ +/* #undef BSD_PGRP */ + +/* Define if you have POSIX versions of the setpgid() and getpgrp() routines */ +#define POSIX_PGRP 1 + +/* Define if you have sysV versions of the setpgrp() and getpgrp() routines */ +/* #undef SYSV_PGRP */ + +/* Define if you don't have setpgrp(), setpgid() or getpgrp() routines */ +/* #undef NO_PGRP */ + +/* Define to char if your compiler doesn't like the void keyword */ +/* #undef void */ + +/* Define to nothing if compiler doesn't like the volatile keyword */ +/* #undef volatile */ + +/* Define if C compiler groks function prototypes */ +#define HAVE_PROTOTYPES 1 + +/* Define if C compiler groks __attribute__((...)) (const, noreturn, format) */ +#define HAVE_GCC_FUNC_ATTR 1 + +/* Define to 32-bit signed integer type if doesn't define */ +/* #undef clock_t */ + +/* Define to the type of struct rlimit fields if the rlim_t type is missing */ +/* #undef rlim_t */ + +/* Define if time() is declared in */ +#define TIME_DECLARED 1 + +/* Define to `unsigned' if doesn't define */ +/* #undef sigset_t */ + +/* Define if sys_errlist[] and sys_nerr are in the C library */ +#define HAVE_SYS_ERRLIST 1 + +/* Define if sys_errlist[] and sys_nerr are defined in */ +#define SYS_ERRLIST_DECLARED 1 + +/* Define if sys_siglist[] is in the C library */ +#define HAVE_SYS_SIGLIST 1 + +/* Define if you have a sane header file */ +#define HAVE_TERMIOS_H 1 + +/* Define if you have a memset() function in your C library */ +#define HAVE_MEMSET 1 + +/* Define if you have a memmove() function in your C library */ +#define HAVE_MEMMOVE 1 + +/* Define if you have a bcopy() function in your C library */ +/* #undef HAVE_BCOPY */ + +/* Define if you have a lstat() function in your C library */ +#define HAVE_LSTAT 1 + +/* Define if you have a sane header file */ +/* #undef HAVE_TERMIO_H */ + +/* Define if you don't have times() or if it always returns 0 */ +/* #undef TIMES_BROKEN */ + +/* Define if opendir() will open non-directory files */ +/* #undef OPENDIR_DOES_NONDIR */ + +/* Define if the pgrp of setpgrp() can't be the pid of a zombie process */ +/* #undef NEED_PGRP_SYNC */ + +/* Define if you arg running SCO unix */ +/* #undef OS_SCO */ + +/* Define if you arg running ISC unix */ +/* #undef OS_ISC */ + +/* Define if you arg running OS2 with the EMX library */ +/* #undef OS2 */ + +/* Define if you have a POSIX.1 compatible */ +#define POSIX_SYS_WAIT 1 + +/* Define if your OS maps references to /dev/fd/n to file descriptor n */ +#define HAVE_DEV_FD 1 + +/* Default PATH (see comments in configure.in for more details) */ +#define DEFAULT_PATH "/bin:/usr/bin:/sbin:/usr/sbin" + +/* Define if your C library's getwd/getcwd function dumps core in unreadable + * directories. */ +/* #undef HPUX_GETWD_BUG */ + +/* Include ksh features? (see comments in configure.in for more details) */ +/* #define KSH 1 */ + +/* Include emacs editing? (see comments in configure.in for more details) */ +#define EMACS 1 + +/* Include vi editing? (see comments in configure.in for more details) */ +#define VI 1 + +/* Include job control? (see comments in configure.in for more details) */ +#define JOBS 1 + +/* Include brace-expansion? (see comments in configure.in for more details) */ +#define BRACE_EXPAND 1 + +/* Include any history? (see comments in configure.in for more details) */ +#define HISTORY 1 + +/* Include complex history? (see comments in configure.in for more details) */ +#define COMPLEX_HISTORY + +/* Strict POSIX behaviour? (see comments in configure.in for more details) */ +/* #undef POSIXLY_CORRECT */ + +/* Specify default $ENV? (see comments in configure.in for more details) */ +/* #undef DEFAULT_ENV */ + +/* Include shl(1) support? (see comments in configure.in for more details) */ +/* #undef SWTCH */ + +/* Include game-of-life? (see comments in configure.in for more details) */ +/* #undef SILLY */ + +/* The number of bytes in a int. */ +#define SIZEOF_INT 4 + +/* The number of bytes in a long. */ +#if defined(alpha) +#define SIZEOF_LONG 8 +#else +#define SIZEOF_LONG 4 +#endif + +/* Define if you have the _setjmp function. */ +/* #undef HAVE__SETJMP */ + +/* Define if you have the confstr function. */ +#define HAVE_CONFSTR 1 + +/* Define if you have the flock function. */ +#define HAVE_FLOCK 1 + +/* Define if you have the getcwd function. */ +#define HAVE_GETCWD 1 + +/* Define if you have the getgroups function. */ +/* #undef HAVE_GETGROUPS */ + +/* Define if you have the getpagesize function. */ +#define HAVE_GETPAGESIZE 1 + +/* Define if you have the getrusage function. */ +/* #undef HAVE_GETRUSAGE */ + +/* Define if you have the getwd function. */ +#define HAVE_GETWD 1 + +/* Define if you have the killpg function. */ +#define HAVE_KILLPG 1 + +/* Define if you have the nice function. */ +#define HAVE_NICE 1 + +/* Define if you have the setrlimit function. */ +#define HAVE_SETRLIMIT 1 + +/* Define if you have the sigsetjmp function. */ +#define HAVE_SIGSETJMP 1 + +/* Define if you have the strcasecmp function. */ +#define HAVE_STRCASECMP 1 + +/* Define if you have the strerror function. */ +#define HAVE_STRERROR 1 + +/* Define if you have the strstr function. */ +#define HAVE_STRSTR 1 + +/* Define if you have the sysconf function. */ +#define HAVE_SYSCONF 1 + +/* Define if you have the tcsetpgrp function. */ +#define HAVE_TCSETPGRP 1 + +/* Define if you have the ulimit function. */ +/* #undef HAVE_ULIMIT */ + +/* Define if you have the valloc function. */ +#define HAVE_VALLOC 1 + +/* Define if you have the wait3 function. */ +#define HAVE_WAIT3 1 + +/* Define if you have the waitpid function. */ +#define HAVE_WAITPID 1 + +/* Define if you have the header file. */ +#define HAVE_DIRENT_H 1 + +/* Define if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define if you have the header file. */ +#define HAVE_LIMITS_H 1 + +/* Define if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_NDIR_H */ + +/* Define if you have the header file. */ +#define HAVE_PATHS_H 1 + +/* Define if you have the header file. */ +#define HAVE_STDDEF_H 1 + +/* Define if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_DIR_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define if you have the header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_RESOURCE_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_WAIT_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_ULIMIT_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_VALUES_H */ + +/* Need to use a separate file to keep the configure script from commenting + * out the undefs.... + */ +#include "conf-end.h" + +#endif /* CONFIG_H */ diff --git a/edit.c b/edit.c new file mode 100644 index 0000000..02bd31c --- /dev/null +++ b/edit.c @@ -0,0 +1,1081 @@ +/* $OpenBSD: edit.c,v 1.15 2002/06/09 05:47:27 todd Exp $ */ + +/* + * Command line editing - common code + * + */ + +#include "config.h" +#ifdef EDIT + +#include "sh.h" +#include "tty.h" +#define EXTERN +#include "edit.h" +#undef EXTERN +#ifdef OS_SCO /* SCO Unix 3.2v4.1 */ +# include /* needed for */ +# include /* needed for struct winsize */ +#endif /* OS_SCO */ +#include +#include +#include "ksh_stat.h" + + +#if defined(TIOCGWINSZ) +static RETSIGTYPE x_sigwinch ARGS((int sig)); +static int got_sigwinch; +static void check_sigwinch ARGS((void)); +#endif /* TIOCGWINSZ */ + +static int x_file_glob ARGS((int flags, const char *str, int slen, + char ***wordsp)); +static int x_command_glob ARGS((int flags, const char *str, int slen, + char ***wordsp)); +static int x_locate_word ARGS((const char *buf, int buflen, int pos, + int *startp, int *is_command)); + +static char vdisable_c; + + +/* Called from main */ +void +x_init() +{ + /* 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 */ + +#ifdef TIOCGWINSZ +# ifdef SIGWINCH + if (setsig(&sigtraps[SIGWINCH], x_sigwinch, SS_RESTORE_ORIG|SS_SHTRAP)) + sigtraps[SIGWINCH].flags |= TF_SHELL_USES; +# endif /* SIGWINCH */ + got_sigwinch = 1; /* force initial check */ + check_sigwinch(); +#endif /* TIOCGWINSZ */ + +#ifdef EMACS + x_init_emacs(); +#endif /* EMACS */ + + /* Bizarreness to figure out how to disable + * a struct termios.c_cc[] char + */ +#ifdef _POSIX_VDISABLE + if (_POSIX_VDISABLE >= 0) + vdisable_c = (char) _POSIX_VDISABLE; + else + /* `feature not available' */ + vdisable_c = (char) 0377; +#else +# if defined(HAVE_PATHCONF) && defined(_PC_VDISABLE) + vdisable_c = fpathconf(tty_fd, _PC_VDISABLE); +# else + vdisable_c = (char) 0377; /* default to old BSD value */ +# endif +#endif /* _POSIX_VDISABLE */ +} + +#if defined(TIOCGWINSZ) +static RETSIGTYPE +x_sigwinch(sig) + int sig; +{ + got_sigwinch = 1; + return RETSIGVAL; +} + +static void +check_sigwinch ARGS((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); + } + } +} +#endif /* TIOCGWINSZ */ + +/* + * read an edited command line + */ +int +x_read(buf, len) + char *buf; + size_t len; +{ + int i; + +#if defined(TIOCGWINSZ) + if (got_sigwinch) + check_sigwinch(); +#endif /* TIOCGWINSZ */ + + 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); + return i; +} + +/* tty I/O */ + +int +x_getc() +{ +#ifdef OS2 + unsigned char c = _read_kbd(0, 1, 0); + return c == 0 ? 0xE0 : c; +#else /* OS2 */ + char c; + int n; + + while ((n = blocking_read(0, &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; +#endif /* OS2 */ +} + +void +x_flush() +{ + shf_flush(shl_out); +} + +void +x_putc(c) + int c; +{ + shf_putc(c, shl_out); +} + +void +x_puts(s) + const char *s; +{ + while (*s != 0) + shf_putc(*s++, shl_out); +} + +bool_t +x_mode(onoff) + bool_t onoff; +{ + static bool_t x_cur_mode; + bool_t prev; + + if (x_cur_mode == onoff) + return x_cur_mode; + prev = x_cur_mode; + x_cur_mode = onoff; + + if (onoff) { + TTY_state cb; + X_chars oldchars; + + oldchars = edchars; + cb = tty_state; + +#if defined(HAVE_TERMIOS_H) || defined(HAVE_TERMIO_H) + 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]; +# ifdef VWERASE + edchars.werase = cb.c_cc[VWERASE]; +# endif +# ifdef _CRAY2 /* brain-damaged terminal handler */ + cb.c_lflag &= ~(ICANON|ECHO); + /* rely on print routine to map '\n' to CR,LF */ +# else + cb.c_iflag &= ~(INLCR|ICRNL); +# ifdef _BSD_SYSV /* need to force CBREAK instead of RAW (need CRMOD on output) */ + cb.c_lflag &= ~(ICANON|ECHO); +# else +# ifdef SWTCH /* need CBREAK to handle swtch char */ + cb.c_lflag &= ~(ICANON|ECHO); + cb.c_lflag |= ISIG; + cb.c_cc[VINTR] = vdisable_c; + cb.c_cc[VQUIT] = vdisable_c; +# else + cb.c_lflag &= ~(ISIG|ICANON|ECHO); +# endif +# endif +# ifdef VLNEXT + /* osf/1 processes lnext when ~icanon */ + cb.c_cc[VLNEXT] = vdisable_c; +# endif /* VLNEXT */ +# ifdef VDISCARD + /* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */ + cb.c_cc[VDISCARD] = vdisable_c; +# endif /* VDISCARD */ + cb.c_cc[VTIME] = 0; + cb.c_cc[VMIN] = 1; +# endif /* _CRAY2 */ +#else + /* Assume BSD tty stuff. */ + edchars.erase = cb.sgttyb.sg_erase; + edchars.kill = cb.sgttyb.sg_kill; + cb.sgttyb.sg_flags &= ~ECHO; + cb.sgttyb.sg_flags |= CBREAK; +# ifdef TIOCGATC + edchars.intr = cb.lchars.tc_intrc; + edchars.quit = cb.lchars.tc_quitc; + edchars.eof = cb.lchars.tc_eofc; + edchars.werase = cb.lchars.tc_werasc; + cb.lchars.tc_suspc = -1; + cb.lchars.tc_dsuspc = -1; + cb.lchars.tc_lnextc = -1; + cb.lchars.tc_statc = -1; + cb.lchars.tc_intrc = -1; + cb.lchars.tc_quitc = -1; + cb.lchars.tc_rprntc = -1; +# else + edchars.intr = cb.tchars.t_intrc; + edchars.quit = cb.tchars.t_quitc; + edchars.eof = cb.tchars.t_eofc; + cb.tchars.t_intrc = -1; + cb.tchars.t_quitc = -1; +# ifdef TIOCGLTC + edchars.werase = cb.ltchars.t_werasc; + cb.ltchars.t_suspc = -1; + cb.ltchars.t_dsuspc = -1; + cb.ltchars.t_lnextc = -1; + cb.ltchars.t_rprntc = -1; +# endif +# endif /* TIOCGATC */ +#endif /* HAVE_TERMIOS_H || HAVE_TERMIO_H */ + + set_tty(tty_fd, &cb, TF_WAIT); + +#ifdef __CYGWIN__ + if (edchars.eof == '\0') + edchars.eof = '\4'; +#endif /* __CYGWIN__ */ + + /* Convert unset values to internal `unset' value */ + if (edchars.erase == vdisable_c) + edchars.erase = -1; + if (edchars.kill == vdisable_c) + edchars.kill = -1; + if (edchars.intr == vdisable_c) + edchars.intr = -1; + if (edchars.quit == vdisable_c) + edchars.quit = -1; + if (edchars.eof == vdisable_c) + edchars.eof = -1; + if (edchars.werase == vdisable_c) + edchars.werase = -1; + if (memcmp(&edchars, &oldchars, sizeof(edchars)) != 0) { +#ifdef EMACS + x_emacs_keys(&edchars); +#endif + } + } else { + /* TF_WAIT doesn't seem to be necessary when leaving xmode */ + set_tty(tty_fd, &tty_state, TF_NONE); + } + + return prev; +} + +/* NAME: + * promptlen - calculate the length of PS1 etc. + * + * DESCRIPTION: + * This function is based on a fix from guy@demon.co.uk + * It fixes a bug in that if PS1 contains '!', the length + * given by strlen() is probably wrong. + * + * RETURN VALUE: + * length + */ +int +promptlen(cp, spp) + const char *cp; + const char **spp; +{ + int count = 0; + const char *sp = cp; + char delimiter = 0; + int indelimit = 0; + + /* Undocumented AT&T ksh feature: + * If the second char in the prompt string is \r then the first char + * is taken to be a non-printing delimiter and any chars between two + * instances of the delimiter are not considered to be part of the + * prompt length + */ + if (*cp && cp[1] == '\r') { + delimiter = *cp; + cp += 2; + } + + for (; *cp; cp++) { + if (indelimit && *cp != delimiter) + ; + else if (*cp == '\n' || *cp == '\r') { + count = 0; + sp = cp + 1; + } else if (*cp == '\t') { + count = (count | 7) + 1; + } else if (*cp == '\b') { + if (count > 0) + count--; + } else if (*cp == delimiter) + indelimit = !indelimit; + else + count++; + } + if (spp) + *spp = sp; + return count; +} + +void +set_editmode(ed) + 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 = ksh_strrchr_dirsep(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(buf, bsize, lenp) + 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 ARGS((const char *str, int slen)); +static void glob_table ARGS((const char *pat, XPtrV *wp, struct table *tp)); +static void glob_path ARGS((int flags, const char *pat, XPtrV *wp, + const char *path)); + +#if 0 /* not used... */ +int x_complete_word ARGS((const char *str, int slen, int is_command, + int *multiple, char **ret)); +int +x_complete_word(str, slen, is_command, nwordsp, ret) + const char *str; + int slen; + int is_command; + int *nwordsp; + char **ret; +{ + int nwords; + int prefix_len; + char **words; + + nwords = (is_command ? x_command_glob : x_file_glob)(XCF_FULLPATH, + str, slen, &words); + *nwordsp = nwords; + if (nwords == 0) { + *ret = (char *) 0; + return -1; + } + + prefix_len = x_longest_prefix(nwords, words); + *ret = str_nsave(words[0], prefix_len, ATEMP); + x_free_words(nwords, words); + return prefix_len; +} +#endif /* 0 */ + +void +x_print_expansions(nwords, words, is_command) + 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 + && !ISDIRSEP(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(flags, str, slen, wordsp) + int flags; + const char *str; + int slen; + char ***wordsp; +{ + char *toglob; + char **words; + int nwords, i, idx, escaping; + XPtrV w; + struct source *s, *sold; + + if (slen < 0) + return 0; + + toglob = add_glob(str, slen); + + /* remove all escaping backward slashes */ + escaping = 0; + for(i = 0, idx = 0; toglob[i]; i++) { + if (toglob[i] == '\\' && !escaping) { + escaping = 1; + continue; + } + + toglob[idx] = toglob[i]; + idx++; + if (escaping) escaping = 0; + } + toglob[idx] = '\0'; + + /* + * 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) != 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 globbing failed (returned glob pattern), + * but be careful (E.g. toglob == "ab*" when the file + * "ab*" exists is not an error). + * 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 ((strcmp(words[0], toglob) == 0 + && stat(words[0], &statb) < 0) + || words[0][0] == '\0') + { + x_free_words(nwords, words); + nwords = 0; + } + } + afree(toglob, ATEMP); + + *wordsp = nwords ? words : (char **) 0; + + return nwords; +} + +/* Data structure used in x_command_glob() */ +struct path_order_info { + char *word; + int base; + int path_order; +}; + +/* Compare routine used in x_command_glob() */ +static int +path_order_cmp(aa, bb) + 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 = FILECMP(a->word + a->base, b->word + b->base); + return t ? t : a->path_order - b->path_order; +} + +static int +x_command_glob(flags, str, slen, wordsp) + 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 + || FILENCMP(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(buf, buflen, pos, startp, is_commandp) + 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 && buf[end+1] == ' ') + end++; + } + + if (is_commandp) { + int iscmd; + + /* Figure out if this is a command */ + for (p = start - 1; p >= 0 && isspace(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 (ISDIRSEP(buf[p])) + break; + iscmd = p == end; + } + *is_commandp = iscmd; + } + + *startp = start; + + return end - start; +} + +int +x_cf_glob(flags, buf, buflen, pos, startp, endp, wordsp, is_commandp) + 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(str, slen) + const char *str; + int slen; +{ + char *toglob; + char *s; + bool_t 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 (ISDIRSEP(*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(nwords, words) + 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 (FILECHCONV(p[j]) != FILECHCONV(words[0][j])) { + prefix_len = j; + break; + } + return prefix_len; +} + +void +x_free_words(nwords, 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(s, se) + 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 && ISDIRSEP(*p); p--) + ; + for (; p > s && !ISDIRSEP(*p); p--) + ; + if (ISDIRSEP(*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(pat, wp, tp) + const char *pat; + XPtrV *wp; + struct table *tp; +{ + struct tstate ts; + struct tbl *te; + + for (twalk(&ts, tp); (te = tnext(&ts)); ) { + if (gmatch(te->name, pat, FALSE)) + XPput(*wp, str_save(te->name, ATEMP)); + } +} + +static void +glob_path(flags, pat, wp, 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, PATHSEP))) + 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++ = DIRSEP; + 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(s, len, putbuf_func) + const char *s; + size_t len; + int putbuf_func ARGS((const char *s, size_t len)); +{ + 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 new file mode 100644 index 0000000..d8f3641 --- /dev/null +++ b/edit.h @@ -0,0 +1,87 @@ +/* $OpenBSD: edit.h,v 1.3 1999/11/14 22:04:02 d 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_fc_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 ARGS((void)); +void x_flush ARGS((void)); +void x_putc ARGS((int c)); +void x_puts ARGS((const char *s)); +bool_t x_mode ARGS((bool_t onoff)); +int promptlen ARGS((const char *cp, const char **spp)); +int x_do_comment ARGS((char *buf, int bsize, int *lenp)); +void x_print_expansions ARGS((int nwords, char *const *words, int is_command)); +int x_cf_glob ARGS((int flags, const char *buf, int buflen, int pos, int *startp, + int *endp, char ***wordsp, int *is_commandp)); +int x_longest_prefix ARGS((int nwords, char *const *words)); +int x_basename ARGS((const char *s, const char *se)); +void x_free_words ARGS((int nwords, char **words)); +int x_escape ARGS((const char *, size_t, int (*)(const char *s, size_t len))); +/* emacs.c */ +int x_emacs ARGS((char *buf, size_t len)); +void x_init_emacs ARGS((void)); +void x_emacs_keys ARGS((X_chars *ec)); +/* vi.c */ +int x_vi ARGS((char *buf, size_t len)); + + +#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-gen.sh b/emacs-gen.sh new file mode 100644 index 0000000..a1dee00 --- /dev/null +++ b/emacs-gen.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# $OpenBSD: emacs-gen.sh,v 1.1.1.1 1996/08/14 06:19:10 downsj Exp $ + +case $# in +1) file=$1;; +*) + echo "$0: Usage: $0 path-to-emacs.c" 1>&2 + exit 1 +esac; + +if [ ! -r "$file" ] ;then + echo "$0: can't read $file" 1>&2 + exit 1 +fi + +cat << E_O_F || exit 1 +/* + * NOTE: THIS FILE WAS GENERATED AUTOMATICALLY FROM $file + * + * DO NOT BOTHER EDITING THIS FILE + */ +E_O_F + +# Pass 1: print out lines before @START-FUNC-TAB@ +# and generate defines and function declarations, +sed -e '1,/@START-FUNC-TAB@/d' -e '/@END-FUNC-TAB@/,$d' < $file | + awk 'BEGIN { nfunc = 0; } + /^[ ]*#/ { + print $0; + next; + } + { + fname = $2; + c = substr(fname, length(fname), 1); + if (c == ",") + fname = substr(fname, 1, length(fname) - 1); + if (fname != "0") { + printf "#define XFUNC_%s %d\n", substr(fname, 3, length(fname) - 2), nfunc; + printf "static int %s ARGS((int c));\n", fname; + nfunc++; + } + }' || exit 1 + +exit 0 diff --git a/emacs.c b/emacs.c new file mode 100644 index 0000000..feaeb7f --- /dev/null +++ b/emacs.c @@ -0,0 +1,2199 @@ +/* $OpenBSD: emacs.c,v 1.18 2003/02/28 09:45:09 jmc 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 + */ + +#include "config.h" +#ifdef EMACS + +#include "sh.h" +#include "ksh_stat.h" +#include "ksh_dir.h" +#include +#include +#include "edit.h" + +static Area aedit; +#define AEDIT &aedit /* area for kill ring and macro defns */ + +#undef CTRL /* _BSD brain damage */ +#define CTRL(x) ((x) == '?' ? 0x7F : (x) & 0x1F) /* ASCII */ +#define UNCTRL(x) ((x) == 0x7F ? '?' : (x) | 0x40) /* ASCII */ +#define META(x) ((x) & 0x7f) +#define ISMETA(x) (x_usemeta && ((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) ARGS((int c)); + const char *xf_name; + short xf_flags; +}; + +/* index into struct x_ftab x_ftab[] - small is good */ +typedef unsigned char Findex; + +struct x_defbindings { + Findex xdb_func; /* XFUNC_* */ + unsigned char xdb_tab; + unsigned char xdb_char; +}; + +#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 == '\'') +#define is_mfs(c) (!(isalnum(c) || c == '_' || c == '$')) /* Separator for motion */ + +#ifdef OS2 + /* Deal with 8 bit chars & an extra prefix for function key (these two + * changes increase memory usage from 9,216 bytes to 24,416 bytes...) + */ +# define CHARMASK 0xFF /* 8-bit ASCII character mask */ +# define X_NTABS 4 /* normal, meta1, meta2, meta3 */ +static int x_prefix3 = 0xE0; +#else /* OS2 */ +# define CHARMASK 0xFF /* 8-bit character mask */ +# define X_NTABS 3 /* normal, meta1, meta2 */ +#endif /* OS2 */ +#define X_TABSZ (CHARMASK+1) /* size of keydef tables etc */ + +/* 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; + +/* { 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 x_usemeta; /* no 8-bit ascii, meta = ESC */ + +static int xlp_valid; +/* end from 4.9 edit.h } */ + +static int x_prefix1 = CTRL('['), x_prefix2 = CTRL('X'); +static char **x_histp; /* history position */ +static int x_nextcmd; /* for newline-and-next */ +static char *xmp; /* mark pointer */ +static Findex x_last_command; +static Findex (*x_tab)[X_TABSZ]; /* key definition */ +static char *(*x_atab)[X_TABSZ]; /* macro definitions */ +static unsigned char x_bound[(X_TABSZ * X_NTABS + 7) / 8]; +#define KILLSIZE 20 +static char *killstack[KILLSIZE]; +static int killsp, killtp; +static int x_curprefix; +static char *macroptr; +static int prompt_skip; + +static int x_ins ARGS((char *cp)); +static void x_delete ARGS((int nc, int force_push)); +static int x_bword ARGS((void)); +static int x_fword ARGS((void)); +static void x_goto ARGS((char *cp)); +static void x_bs ARGS((int c)); +static int x_size_str ARGS((char *cp)); +static int x_size ARGS((int c)); +static void x_zots ARGS((char *str)); +static void x_zotc ARGS((int c)); +static void x_load_hist ARGS((char **hp)); +static int x_search ARGS((char *pat, int sameline, int offset)); +static int x_match ARGS((char *str, char *pat)); +static void x_redraw ARGS((int limit)); +static void x_push ARGS((int nchars)); +static char * x_mapin ARGS((const char *cp)); +static char * x_mapout ARGS((int c)); +static void x_print ARGS((int prefix, int key)); +static void x_adjust ARGS((void)); +static void x_e_ungetc ARGS((int c)); +static int x_e_getc ARGS((void)); +static void x_e_putc ARGS((int c)); +static void x_e_puts ARGS((const char *s)); +static int x_comment ARGS((int c)); +static int x_fold_case ARGS((int c)); +static char *x_lastcp ARGS((void)); +static void do_complete ARGS((int flags, Comp_type type)); +static int x_emacs_putbuf ARGS((const char *s, size_t len)); + + +/* The lines between START-FUNC-TAB .. END-FUNC-TAB are run through a + * script (emacs-gen.sh) that generates emacs.out which contains: + * - function declarations for x_* functions + * - defines of the form XFUNC_ where is function + * name, sans leading x_. + * Note that the script treats #ifdef and { 0, 0, 0} specially - use with + * caution. + */ +#include "emacs.out" +static const struct x_ftab x_ftab[] = { +/* @START-FUNC-TAB@ */ + { 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_meta1, "prefix-1", XF_PREFIX }, + { x_meta2, "prefix-2", XF_PREFIX }, + { 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_capitialize, "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 }, +#ifdef SILLY + { x_game_of_life, "play-game-of-life", 0 }, +#else + { 0, 0, 0 }, +#endif +#ifdef DEBUG + { x_debug_info, "debug-info", 0 }, +#else + { 0, 0, 0 }, +#endif +#ifdef OS2 + { x_meta3, "prefix-3", XF_PREFIX }, +#else + { 0, 0, 0 }, +#endif +/* @END-FUNC-TAB@ */ + }; + +static struct x_defbindings const x_defbindings[] = { + { XFUNC_del_back, 0, CTRL('?') }, + { XFUNC_del_bword, 1, CTRL('?') }, + { XFUNC_eot_del, 0, CTRL('D') }, + { XFUNC_del_back, 0, CTRL('H') }, + { XFUNC_del_bword, 1, CTRL('H') }, + { XFUNC_del_bword, 1, 'h' }, + { XFUNC_mv_bword, 1, 'b' }, + { XFUNC_mv_fword, 1, 'f' }, + { XFUNC_del_fword, 1, 'd' }, + { XFUNC_mv_back, 0, CTRL('B') }, + { XFUNC_mv_forw, 0, CTRL('F') }, + { XFUNC_search_char_forw, 0, CTRL(']') }, + { XFUNC_search_char_back, 1, CTRL(']') }, + { XFUNC_newline, 0, CTRL('M') }, + { XFUNC_newline, 0, CTRL('J') }, + { XFUNC_end_of_text, 0, CTRL('_') }, + { XFUNC_abort, 0, CTRL('G') }, + { XFUNC_prev_com, 0, CTRL('P') }, + { XFUNC_next_com, 0, CTRL('N') }, + { XFUNC_nl_next_com, 0, CTRL('O') }, + { XFUNC_search_hist, 0, CTRL('R') }, + { XFUNC_beg_hist, 1, '<' }, + { XFUNC_end_hist, 1, '>' }, + { XFUNC_goto_hist, 1, 'g' }, + { XFUNC_mv_end, 0, CTRL('E') }, + { XFUNC_mv_begin, 0, CTRL('A') }, + { XFUNC_draw_line, 0, CTRL('L') }, + { XFUNC_meta1, 0, CTRL('[') }, + { XFUNC_meta2, 0, CTRL('X') }, + { XFUNC_kill, 0, CTRL('K') }, + { XFUNC_yank, 0, CTRL('Y') }, + { XFUNC_meta_yank, 1, 'y' }, + { XFUNC_literal, 0, CTRL('^') }, + { XFUNC_comment, 1, '#' }, +#if defined(BRL) && defined(TIOCSTI) + { XFUNC_stuff, 0, CTRL('T') }, +#else + { XFUNC_transpose, 0, CTRL('T') }, +#endif + { XFUNC_complete, 1, CTRL('[') }, + { XFUNC_comp_list, 0, CTRL('I') }, + { XFUNC_comp_list, 1, '=' }, + { XFUNC_enumerate, 1, '?' }, + { XFUNC_expand, 1, '*' }, + { XFUNC_comp_file, 1, CTRL('X') }, + { XFUNC_comp_comm, 2, CTRL('[') }, + { XFUNC_list_comm, 2, '?' }, + { XFUNC_list_file, 2, CTRL('Y') }, + { XFUNC_set_mark, 1, ' ' }, + { XFUNC_kill_region, 0, CTRL('W') }, + { XFUNC_xchg_point_mark, 2, CTRL('X') }, + { XFUNC_version, 0, CTRL('V') }, +#ifdef DEBUG + { XFUNC_debug_info, 1, CTRL('H') }, +#endif + { XFUNC_prev_histword, 1, '.' }, + { XFUNC_prev_histword, 1, '_' }, + { XFUNC_set_arg, 1, '0' }, + { XFUNC_set_arg, 1, '1' }, + { XFUNC_set_arg, 1, '2' }, + { XFUNC_set_arg, 1, '3' }, + { XFUNC_set_arg, 1, '4' }, + { XFUNC_set_arg, 1, '5' }, + { XFUNC_set_arg, 1, '6' }, + { XFUNC_set_arg, 1, '7' }, + { XFUNC_set_arg, 1, '8' }, + { XFUNC_set_arg, 1, '9' }, + { XFUNC_fold_upper, 1, 'U' }, + { XFUNC_fold_upper, 1, 'u' }, + { XFUNC_fold_lower, 1, 'L' }, + { XFUNC_fold_lower, 1, 'l' }, + { XFUNC_fold_capitialize, 1, 'C' }, + { XFUNC_fold_capitialize, 1, 'c' }, +#ifdef OS2 + { XFUNC_meta3, 0, 0xE0 }, + { XFUNC_mv_back, 3, 'K' }, + { XFUNC_mv_forw, 3, 'M' }, + { XFUNC_next_com, 3, 'P' }, + { XFUNC_prev_com, 3, 'H' }, +#endif /* OS2 */ + /* These for ansi arrow keys: arguablely shouldn't be here by + * default, but its simpler/faster/smaller than using termcap + * entries. + */ + { XFUNC_meta2, 1, '[' }, + { XFUNC_meta2, 1, 'O' }, + { XFUNC_prev_com, 2, 'A' }, + { XFUNC_next_com, 2, 'B' }, + { XFUNC_mv_forw, 2, 'C' }, + { XFUNC_mv_back, 2, 'D' }, +}; + +int +x_emacs(buf, len) + char *buf; + size_t len; +{ + int c; + const char *p; + int i; + Findex f; + + xbp = xbuf = buf; xend = buf + len; + xlp = xcp = xep = buf; + *xcp = 0; + xlp_valid = TRUE; + xmp = NULL; + x_curprefix = 0; + macroptr = (char *) 0; + x_histp = histptr + 1; + x_last_command = XFUNC_error; + + xx_cols = x_cols; + x_col = promptlen(prompt, &p); + prompt_skip = p - prompt; + x_adj_ok = 1; + x_displen = xx_cols - 2 - x_col; + x_adj_done = 0; + + pprompt(prompt, 0); + + if (x_nextcmd >= 0) { + int off = source->line - x_nextcmd; + if (histptr - history >= off) + x_load_hist(histptr - off); + x_nextcmd = -1; + } + + while (1) { + x_flush(); + if ((c = x_e_getc()) < 0) + return 0; + + if (ISMETA(c)) { + c = META(c); + x_curprefix = 1; + } + + f = x_curprefix == -1 ? XFUNC_insert + : x_tab[x_curprefix][c&CHARMASK]; + + if (!(x_ftab[f].xf_flags & XF_PREFIX) + && x_last_command != XFUNC_set_arg) + { + x_arg = 1; + x_arg_defaulted = 1; + } + i = c | (x_curprefix << 8); + x_curprefix = 0; + switch (i = (*x_ftab[f].xf_func)(i)) { + case KSTD: + if (!(x_ftab[f].xf_flags & XF_PREFIX)) + x_last_command = f; + break; + case KEOL: + i = xep - xbuf; + return i; + case KINTR: /* special case for interrupt */ + trapsig(SIGINT); + x_mode(FALSE); + unwind(LSHELL); + } + } +} + +static int +x_insert(c) + 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(c) + int c; +{ + if (macroptr) { + x_e_putc(BEL); + return KSTD; + } + macroptr = x_atab[c>>8][c & CHARMASK]; + if (macroptr && !*macroptr) { + /* XXX bell? */ + macroptr = (char *) 0; + } + return KSTD; +} + +static int +x_do_ins(cp, len) + 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(s) + char *s; +{ + char *cp = xcp; + register 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(s, len) + 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(c) + 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(c) + 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(nc, force_push) + int nc; + int force_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 (nc > 1 || force_push) + x_push(nc); + + xep -= nc; + cp = xcp; + j = 0; + i = nc; + while (i--) { + j += x_size(*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(c) + int c; +{ + x_delete(x_bword(), FALSE); + return KSTD; +} + +static int +x_mv_bword(c) + int c; +{ + (void)x_bword(); + return KSTD; +} + +static int +x_mv_fword(c) + int c; +{ + x_goto(xcp + x_fword()); + return KSTD; +} + +static int +x_del_fword(c) + int c; +{ + x_delete(x_fword(), FALSE); + return KSTD; +} + +static int +x_bword() +{ + int nc = 0; + register 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() +{ + int nc = 0; + register 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(cp) + register 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(*--xcp); + } + else + { + if (cp > xcp) /* move forward */ + { + while (cp > xcp) + x_zotc(*xcp++); + } + } + } +} + +static void +x_bs(c) + int c; +{ + register int i; + i = x_size(c); + while (i--) + x_e_putc('\b'); +} + +static int +x_size_str(cp) + register char *cp; +{ + register int size = 0; + while (*cp) + size += x_size(*cp++); + return size; +} + +static int +x_size(c) + 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(str) + register char *str; +{ + register int adj = x_adj_done; + + x_lastcp(); + while (*str && str < xlp && adj == x_adj_done) + x_zotc(*str++); +} + +static void +x_zotc(c) + 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(c) + 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(c) + 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(c) + 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(c) + 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(c) + int c; +{ + x_e_putc('\r'); + x_e_putc('\n'); + x_flush(); + *xep++ = '\n'; + return KEOL; +} + +static int +x_end_of_text(c) + int c; +{ + return KEOL; +} + +static int x_beg_hist(c) int c; { x_load_hist(history); return KSTD;} + +static int x_end_hist(c) int c; { x_load_hist(histptr); return KSTD;} + +static int x_prev_com(c) int c; { x_load_hist(x_histp - x_arg); return KSTD;} + +static int x_next_com(c) 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(c) + 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(hp) + register char **hp; +{ + int oldsize; + + if (hp < history || hp > histptr) { + x_e_putc(BEL); + return; + } + x_histp = hp; + oldsize = x_size_str(xbuf); + (void)strcpy(xbuf, *hp); + xbp = xbuf; + xep = xcp = xbuf + strlen(*hp); + xlp_valid = FALSE; + if (xep > x_lastcp()) + x_goto(xep); + else + x_redraw(oldsize); +} + +static int +x_nl_next_com(c) + int c; +{ + x_nextcmd = source->line - (histptr - x_histp) + 1; + return (x_newline(c)); +} + +static int +x_eot_del(c) + int c; +{ + if (xep == xbuf && x_arg_defaulted) + return (x_end_of_text(c)); + else + return (x_del_char(c)); +} + +/* reverse incremental history search */ +static int +x_search_hist(c) + int c; +{ + int offset = -1; /* offset of match in xbuf, else -1 */ + char pat [256+1]; /* pattern buffer */ + register char *p = pat; + Findex f; + + *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 = x_tab[0][c&CHARMASK]; + if (c == CTRL('[')) + break; + else if (f == XFUNC_search_hist) + offset = x_search(pat, 0, offset); + else if (f == XFUNC_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 == XFUNC_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(pat, sameline, offset) + char *pat; + int sameline; + int offset; +{ + register 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(str, pat) + char *str, *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(c) + 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(c) + int c; +{ + x_goto(xep); + return KSTD; +} + +static int +x_mv_begin(c) + int c; +{ + x_goto(xbuf); + return KSTD; +} + +static int +x_draw_line(c) + 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(limit) + int limit; +{ + int i, j; + char *cp; + + x_adj_ok = 0; + if (limit == -1) + x_e_putc('\n'); + else + x_e_putc('\r'); + x_flush(); + if (xbp == xbuf) + { + pprompt(prompt + prompt_skip, 0); + x_col = promptlen(prompt, (const char **) 0); + } + x_displen = xx_cols - 2 - x_col; + 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(c) + 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 mdoe. + * 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(c) + int c; +{ + x_curprefix = -1; + return KSTD; +} + +static int +x_meta1(c) + int c; +{ + x_curprefix = 1; + return KSTD; +} + +static int +x_meta2(c) + int c; +{ + x_curprefix = 2; + return KSTD; +} + +#ifdef OS2 +static int +x_meta3(c) + int c; +{ + x_curprefix = 3; + return KSTD; +} +#endif /* OS2 */ + +static int +x_kill(c) + 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(nchars) + 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(c) + 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(c) + int c; +{ + int len; + if (x_last_command != XFUNC_yank && x_last_command != XFUNC_meta_yank) { + 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(c) + int c; +{ + /* x_zotc(c); */ + xlp = xep = xcp = xbp = xbuf; + xlp_valid = TRUE; + *xcp = 0; + return KINTR; +} + +static int +x_error(c) + int c; +{ + x_e_putc(BEL); + return KSTD; +} + +static int +x_stuffreset(c) + 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(c) + int c; +{ +#if 0 || defined TIOCSTI + char ch = c; + bool_t savmode = x_mode(FALSE); + + (void)ioctl(TTY, TIOCSTI, &ch); + (void)x_mode(savmode); + x_redraw(-1); +#endif + return KSTD; +} + +static char * +x_mapin(cp) + const char *cp; +{ + char *new, *op; + + op = new = str_save(cp, ATEMP); + while (*cp) { + /* XXX -- should handle \^ escape? */ + if (*cp == '^') { + cp++; +#ifdef OS2 + if (*cp == '0') /* To define function keys */ + *op++ = 0xE0; + else +#endif /* OS2 */ + if (*cp >= '?') /* includes '?'; ASCII */ + *op++ = CTRL(*cp); + else { + *op++ = '^'; + cp--; + } + } else + *op++ = *cp; + cp++; + } + *op = '\0'; + + return new; +} + +static char * +x_mapout(c) + int c; +{ + static char buf[8]; + register char *p = buf; + +#ifdef OS2 + if (c == 0xE0) { + *p++ = '^'; + *p++ = '0'; + } else +#endif /* OS2 */ + if (iscntrl(c)) { + *p++ = '^'; + *p++ = UNCTRL(c); + } else + *p++ = c; + *p = 0; + return buf; +} + +static void +x_print(prefix, key) + int prefix, key; +{ + if (prefix == 1) + shprintf("%s", x_mapout(x_prefix1)); + if (prefix == 2) + shprintf("%s", x_mapout(x_prefix2)); +#ifdef OS2 + if (prefix == 3) + shprintf("%s", x_mapout(x_prefix3)); +#endif /* OS2 */ + shprintf("%s = ", x_mapout(key)); + if (x_tab[prefix][key] != XFUNC_ins_string) + shprintf("%s\n", x_ftab[x_tab[prefix][key]].xf_name); + else + shprintf("'%s'\n", x_atab[prefix][key]); +} + +int +x_bind(a1, a2, macro, list) + const char *a1, *a2; + int macro; /* bind -m */ + int list; /* bind -l */ +{ + Findex f; + int prefix, key; + char *sp = NULL; + char *m1, *m2; + + if (x_tab == NULL) { + bi_errorf("cannot bind, not a tty"); + return 1; + } + + /* List function names */ + if (list) { + for (f = 0; f < NELEM(x_ftab); f++) + if (x_ftab[f].xf_name + && !(x_ftab[f].xf_flags & XF_NOBIND)) + shprintf("%s\n", x_ftab[f].xf_name); + return 0; + } + + if (a1 == NULL) { + for (prefix = 0; prefix < X_NTABS; prefix++) + for (key = 0; key < X_TABSZ; key++) { + f = x_tab[prefix][key]; + if (f == XFUNC_insert || f == XFUNC_error + || (macro && f != XFUNC_ins_string)) + continue; + x_print(prefix, key); + } + return 0; + } + + m1 = x_mapin(a1); + prefix = key = 0; + for (;; m1++) { + key = *m1 & CHARMASK; + if (x_tab[prefix][key] == XFUNC_meta1) + prefix = 1; + else if (x_tab[prefix][key] == XFUNC_meta2) + prefix = 2; +#ifdef OS2 + else if (x_tab[prefix][key] == XFUNC_meta3) + prefix = 3; +#endif /* OS2 */ + else + break; + } + + if (a2 == NULL) { + x_print(prefix, key); + return 0; + } + + if (*a2 == 0) + f = XFUNC_insert; + else if (!macro) { + for (f = 0; f < NELEM(x_ftab); f++) + if (x_ftab[f].xf_name + && strcmp(x_ftab[f].xf_name, a2) == 0) + break; + if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) { + bi_errorf("%s: no such function", a2); + return 1; + } +#if 0 /* This breaks the bind commands that map arrow keys */ + if (f == XFUNC_meta1) + x_prefix1 = key; + if (f == XFUNC_meta2) + x_prefix2 = key; +#endif /* 0 */ + } else { + f = XFUNC_ins_string; + m2 = x_mapin(a2); + sp = str_save(m2, AEDIT); + } + + if (x_tab[prefix][key] == XFUNC_ins_string && x_atab[prefix][key]) + afree((void *)x_atab[prefix][key], AEDIT); + x_tab[prefix][key] = f; + x_atab[prefix][key] = sp; + + /* Track what the user has bound so x_emacs_keys() won't toast things */ + if (f == XFUNC_insert) + x_bound[(prefix * X_TABSZ + key) / 8] &= + ~(1 << ((prefix * X_TABSZ + key) % 8)); + else + x_bound[(prefix * X_TABSZ + key) / 8] |= + (1 << ((prefix * X_TABSZ + key) % 8)); + + return 0; +} + +void +x_init_emacs() +{ + register int i, j; + char *locale; + + ainit(AEDIT); + x_nextcmd = -1; + + x_tab = (Findex (*)[X_TABSZ]) alloc(sizeofN(*x_tab, X_NTABS), AEDIT); + for (j = 0; j < X_TABSZ; j++) + x_tab[0][j] = XFUNC_insert; + for (i = 1; i < X_NTABS; i++) + for (j = 0; j < X_TABSZ; j++) + x_tab[i][j] = XFUNC_error; + for (i = 0; i < NELEM(x_defbindings); i++) + x_tab[(unsigned char)x_defbindings[i].xdb_tab][x_defbindings[i].xdb_char] + = x_defbindings[i].xdb_func; + + x_atab = (char *(*)[X_TABSZ]) alloc(sizeofN(*x_atab, X_NTABS), AEDIT); + for (i = 1; i < X_NTABS; i++) + for (j = 0; j < X_TABSZ; j++) + x_atab[i][j] = NULL; + + /* 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")) + x_usemeta = 1; +} + +static void +bind_if_not_bound(p, k, func) + int p, k; + int func; +{ + /* Has user already bound this key? If so, don't override it */ + if (x_bound[((p) * X_TABSZ + (k)) / 8] + & (1 << (((p) * X_TABSZ + (k)) % 8))) + return; + + x_tab[p][k] = func; +} + +void +x_emacs_keys(ec) + X_chars *ec; +{ + if (ec->erase >= 0) { + bind_if_not_bound(0, ec->erase, XFUNC_del_back); + bind_if_not_bound(1, ec->erase, XFUNC_del_bword); + } + if (ec->kill >= 0) + bind_if_not_bound(0, ec->kill, XFUNC_del_line); + if (ec->werase >= 0) + bind_if_not_bound(0, ec->werase, XFUNC_del_bword); + if (ec->intr >= 0) + bind_if_not_bound(0, ec->intr, XFUNC_abort); + if (ec->quit >= 0) + bind_if_not_bound(0, ec->quit, XFUNC_noop); +} + +static int +x_set_mark(c) + int c; +{ + xmp = xcp; + return KSTD; +} + +static int +x_kill_region(c) + 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(c) + 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(c) + 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(c) + int c; +{ + return KSTD; +} + +#ifdef SILLY +static int +x_game_of_life(c) + int c; +{ + char newbuf [256+1]; + register char *ip, *op; + int i, len; + + i = xep - xbuf; + *xep = 0; + len = x_size_str(xbuf); + xcp = xbp = xbuf; + memmove(newbuf+1, xbuf, i); + newbuf[0] = 'A'; + newbuf[i] = 'A'; + for (ip = newbuf+1, op = xbuf; --i >= 0; ip++, op++) { + /* Empty space */ + if (*ip < '@' || *ip == '_' || *ip == 0x7F) { + /* Two adults, make whoopee */ + if (ip[-1] < '_' && ip[1] < '_') { + /* Make kid look like parents. */ + *op = '`' + ((ip[-1] + ip[1])/2)%32; + if (*op == 0x7F) /* Birth defect */ + *op = '`'; + } + else + *op = ' '; /* nothing happens */ + continue; + } + /* Child */ + if (*ip > '`') { + /* All alone, dies */ + if (ip[-1] == ' ' && ip[1] == ' ') + *op = ' '; + else /* Gets older */ + *op = *ip-'`'+'@'; + continue; + } + /* Adult */ + /* Overcrowded, dies */ + if (ip[-1] >= '@' && ip[1] >= '@') { + *op = ' '; + continue; + } + *op = *ip; + } + *op = 0; + x_redraw(len); + return KSTD; +} +#endif + +/* + * File/command name completion routines + */ + + +static int +x_comp_comm(c) + int c; +{ + do_complete(XCF_COMMAND, CT_COMPLETE); + return KSTD; +} +static int +x_list_comm(c) + int c; +{ + do_complete(XCF_COMMAND, CT_LIST); + return KSTD; +} +static int +x_complete(c) + int c; +{ + do_complete(XCF_COMMAND_FILE, CT_COMPLETE); + return KSTD; +} +static int +x_enumerate(c) + int c; +{ + do_complete(XCF_COMMAND_FILE, CT_LIST); + return KSTD; +} +static int +x_comp_file(c) + int c; +{ + do_complete(XCF_FILE, CT_COMPLETE); + return KSTD; +} +static int +x_list_file(c) + int c; +{ + do_complete(XCF_FILE, CT_LIST); + return KSTD; +} +static int +x_comp_list(c) + int c; +{ + do_complete(XCF_COMMAND_FILE, CT_COMPLIST); + return KSTD; +} +static int +x_expand(c) + 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; i++) + if (x_ins(words[i]) < 0 || (i < nwords - 1 && x_ins(space) < 0)) + { + x_e_putc(BEL); + return KSTD; + } + + return KSTD; +} + +/* type == 0 for list, 1 for complete and 2 for complete-list */ +static void +do_complete(flags, type) + 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 (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) && (!ISDIRSEP(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() +{ + 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(c) + int c; +{ + unget_char = c; +} + +static int +x_e_getc() +{ + int c; + + if (unget_char >= 0) { + c = unget_char; + unget_char = -1; + } else { + if (macroptr) { + c = *macroptr++; + if (!*macroptr) + macroptr = (char *) 0; + } else + c = x_getc(); + } + + return c <= CHARMASK ? c : (c & CHARMASK); +} + +static void +x_e_putc(c) + 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(c) + 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(s) + const char *s; +{ + register 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(c) + int c; +{ + int n = 0; + int first = 1; + + c &= CHARMASK; /* strip command prefix */ + 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; + } + return KSTD; +} + + +/* Comment or uncomment the current line. */ +static int +x_comment(c) + 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(c) + int c; +{ + register 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(c) + int c; +{ + return x_fold_case('U'); +} + +/* Lowercase N(1) words */ +static int +x_fold_lower(c) + int c; +{ + return x_fold_case('L'); +} + +/* Lowercase N(1) words */ +static int +x_fold_capitialize(c) + 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(c) + int c; +{ + char *cp = xcp; + + if (cp == xep) { + x_e_putc(BEL); + return KSTD; + } + while (x_arg--) { + /* + * fisrt 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(*cp)) + *cp = tolower(*cp); + } else { /* uppercase, capitialize */ + if (islower(*cp)) + *cp = toupper(*cp); + } + cp++; + } + /* + * now for the rest of the word + */ + while (cp != xep && !is_mfs(*cp)) { + if (c == 'U') { /* uppercase */ + if (islower(*cp)) + *cp = toupper(*cp); + } else { /* lowercase, capitialize */ + if (isupper(*cp)) + *cp = tolower(*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() +{ + register char *rcp; + register int i; + + if (!xlp_valid) + { + for (i = 0, rcp = xbp; rcp < xep && i < x_displen; rcp++) + i += x_size(*rcp); + xlp = rcp; + } + xlp_valid = TRUE; + return (xlp); +} + +#endif /* EDIT */ diff --git a/eval.c b/eval.c new file mode 100644 index 0000000..2a0990a --- /dev/null +++ b/eval.c @@ -0,0 +1,1378 @@ +/* $OpenBSD: eval.c,v 1.12 2003/03/10 03:48:16 david Exp $ */ + +/* + * Expansion - quoting, separation, substitution, globbing + */ + +#include "sh.h" +#include +#include "ksh_dir.h" +#include "ksh_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) */ + +/* 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 ARGS((Expand *xp, char *sp, char *word, int *stypep, int *slenp)); +static int comsub ARGS((Expand *xp, char *cp)); +static char *trimsub ARGS((char *str, char *pat, int how)); +static void glob ARGS((char *cp, XPtrV *wp, int markdirs)); +static void globit ARGS((XString *xs, char **xpp, char *sp, XPtrV *wp, + int check)); +static char *maybe_expand_tilde ARGS((char *p, XString *dsp, char **dpp, + int isassign)); +static char *tilde ARGS((char *acp)); +static char *homedir ARGS((char *name)); +#ifdef BRACE_EXPAND +static void alt_expand ARGS((XPtrV *wp, char *start, char *exp_start, + char *end, int fdo)); +#endif + +/* compile and expand word */ +char * +substitute(cp, f) + 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(ap, f) + register char **ap; + int f; +{ + XPtrV w; + + if (*ap == NULL) + return ap; + XPinit(w, 32); + XPput(w, NULL); /* space for shell name */ +#ifdef SHARPBANG + XPput(w, NULL); /* and space for one arg */ +#endif + while (*ap != NULL) + expand(*ap++, &w, f); + XPput(w, NULL); +#ifdef SHARPBANG + return (char **) XPclose(w) + 2; +#else + return (char **) XPclose(w) + 1; +#endif +} + +/* + * expand string + */ +char * +evalstr(cp, f) + 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(cp, f) + register 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(cp, wp, f) + char *cp; /* input word */ + register XPtrV *wp; /* output words */ + int f; /* DO* flags */ +{ + register int UNINITIALIZED(c); + register int type; /* expansion type */ + register int quote = 0; /* quoted */ + XString ds; /* destination string */ + register 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 */ + SubType st_head, *st; + int UNINITIALIZED(newlines); /* For trailing newlines in COMSUB */ + int saw_eq, tilde_ok; + int make_magic; + + 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); + 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 were all syntax checking gets done... + */ + { + char *varname = ++sp; /* skip the { or x (}) */ + int stype; + int slen; + + sp = strchr(sp, '\0') + 1; /* skip variable */ + type = varsub(&x, varname, sp, &stype, &slen); + if (type < 0) { + char endc; + char *str, *end; + + end = (char *) wdscan(sp, CSUBST); + /* ({) the } or x is already skipped */ + endc = *end; + *end = EOS; + str = snptreef((char *) 0, 64, "%S", + varname - 1); + *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_; + /* fall through */ + 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); + type = XSUB; + 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). + */ + setstr(st->var, debunk( + (char *) alloc(strlen(dp) + 1, + ATEMP), dp), + 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), 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: + 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 (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; + shf_close(x.u.shf); + if (x.split) + subst_exstat = waitlast(); + 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)); + 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 { + /* 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 PATHSEP: /* : */ + /* 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(xp, sp, word, stypep, slenp) + 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; + int max = 0; + vp = global(arrayname(sp)); + if (vp->flag & (ISSET|ARRAY)) + zero_ok = 1; + for (; vp; vp = vp->u.array) + if (vp->flag & ISSET) { + max = vp->index + 1; + 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; + 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 '#': + 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(xp, cp) + register Expand *xp; + char *cp; +{ + Source *s, *sold; + register struct op *t; + struct shf *shf; + + s = pushs(SSTRING, ATEMP); + s->start = s->str = cp; + sold = source; + t = compile(s); + source = sold; + + if (t == NULL) + return XBASE; + + if (t != NULL && t->type == TCOM && /* $(args == NULL && *t->vars == NULL && t->ioact != NULL) { + register 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) + errorf("%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, 0); /* fd 1 may be closed... */ + ksh_dup2(pv[1], 1, FALSE); + close(pv[1]); + execute(t, XFORK|XXCOM|XPIPEO); + restfd(1, ofd1); + startlast(); + xp->split = 1; /* waitlast() */ + } + + xp->u.shf = shf; + return XCOM; +} + +/* + * perform #pattern and %pattern substitution in ${} + */ + +static char * +trimsub(str, pat, how) + register char *str; + char *pat; + int how; +{ + register char *end = strchr(str, 0); + register 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(cp, wp, markdirs) + char *cp; + register XPtrV *wp; + int markdirs; +{ + int oldsize = XPsize(*wp); + + if (glob_str(cp, wp, markdirs) == 0) + XPput(*wp, debunk(cp, cp)); + 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(cp, wp, markdirs) + 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(xs, xpp, sp, wp, check) + XString *xs; /* dest string */ + char **xpp; /* ptr to dest end */ + char *sp; /* source path */ + register XPtrV *wp; /* output list */ + int check; /* GF_* flags */ +{ + register 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) + && ISDIRSEP(xp[-1]) && !S_ISDIR(lstatb.st_mode) +#ifdef S_ISLNK + && (!S_ISLNK(lstatb.st_mode) + || stat_check() < 0 + || !S_ISDIR(statb.st_mode)) +#endif /* S_ISLNK */ + ) + 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) && !ISDIRSEP(xp[-1]) + && (S_ISDIR(lstatb.st_mode) +#ifdef S_ISLNK + || (S_ISLNK(lstatb.st_mode) + && stat_check() > 0 + && S_ISDIR(statb.st_mode)) +#endif /* S_ISLNK */ + )) + { + *xp++ = DIRSEP; + *xp = '\0'; + } + } +#ifdef OS2 /* Done this way to avoid bug in gcc 2.7.2... */ + /* Ugly kludge required for command + * completion - see how search_access() + * is implemented for OS/2... + */ +# define KLUDGE_VAL 4 +#else /* OS2 */ +# define KLUDGE_VAL 0 +#endif /* OS2 */ + XPput(*wp, str_nsave(Xstring(*xs, xp), Xlength(*xs, xp) + + KLUDGE_VAL, ATEMP)); + return; + } + + if (xp > Xstring(*xs, xp)) + *xp++ = DIRSEP; + while (ISDIRSEP(*sp)) { + Xcheck(*xs, xp); + *xp++ = *sp++; + } + np = ksh_strchr_dirsep(sp); + if (np != NULL) { + se = np; + odirsep = *np; /* don't assume DIRSEP, 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); + 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 = ksh_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 = NLENGTH(d) + 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 ARGS((XString *xs, char **xpp, char *p)); +static int +copy_non_glob(xs, xpp, p) + 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(dp, sp) + char *dp; + const char *sp; +{ + char *d, *s; + + if ((s = strchr(sp, MAGIC))) { + memcpy(dp, sp, s - sp); + for (d = dp + (s - sp); *s; s++) + if (!ISMAGIC(*s) || !(*++s & 0x80) + || !strchr("*+?@! ", *s & 0x7f)) + *d++ = *s; + else { + /* extended pattern operators: *+?@! */ + if ((*s & 0x7f) != ' ') + *d++ = *s & 0x7f; + *d++ = '('; + } + *d = '\0'; + } else if (dp != sp) + strcpy(dp, sp); + 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(p, dsp, dpp, isassign) + 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 && !ISDIRSEP(p[1]) + && (!isassign || p[1] != PATHSEP)) + { + 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(cp) + 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(name) + char *name; +{ + register struct tbl *ap; + + ap = tenter(&homedirs, name, hash(name)); + if (!(ap->flag & ISSET)) { +#ifdef OS2 + /* No usernames in OS2 - punt */ + return NULL; +#else /* OS2 */ + 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; +#endif /* OS2 */ + } + return ap->val.s; +} + +#ifdef BRACE_EXPAND +static void +alt_expand(wp, start, exp_start, end, fdo) + XPtrV *wp; + char *start, *exp_start; + char *end; + int fdo; +{ + int UNINITIALIZED(count); + char *brace_start, *brace_end, *UNINITIALIZED(comma); + 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)); + 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 new file mode 100644 index 0000000..de722b3 --- /dev/null +++ b/exec.c @@ -0,0 +1,1729 @@ +/* $OpenBSD: exec.c,v 1.27 2003/02/28 09:45:09 jmc Exp $ */ + +/* + * execute command tree + */ + +#include "sh.h" +#include "c_test.h" +#include +#include "ksh_stat.h" + +/* Does ps4 get parameter substitutions done? */ +#ifdef KSH +# define PS4_SUBSTITUTE(s) substitute((s), 0) +#else +# define PS4_SUBSTITUTE(s) (s) +#endif /* KSH */ + +static int comexec ARGS((struct op *t, struct tbl *volatile tp, char **ap, + int volatile flags)); +static void scriptexec ARGS((struct op *tp, char **ap)); +static int call_builtin ARGS((struct tbl *tp, char **wp)); +static int iosetup ARGS((struct ioword *iop, struct tbl *tp)); +static int herein ARGS((const char *content, int sub)); +#ifdef KSH +static char *do_selectargs ARGS((char **ap, bool_t print_menu)); +#endif /* KSH */ +#ifdef KSH +static int dbteste_isa ARGS((Test_env *te, Test_meta meta)); +static const char *dbteste_getopnd ARGS((Test_env *te, Test_op op, + int do_eval)); +static int dbteste_eval ARGS((Test_env *te, Test_op op, const char *opnd1, + const char *opnd2, int do_eval)); +static void dbteste_error ARGS((Test_env *te, int offset, const char *msg)); +#endif /* KSH */ +#ifdef OS2 +static int search_access1 ARGS((const char *path, int mode, int *errnop)); +#endif /* OS2 */ + + +/* + * handle systems that don't have F_SETFD + */ +#ifndef F_SETFD +# ifndef MAXFD +# define MAXFD 64 +# endif +/* a bit field would be smaller, but this will work */ +static char clexec_tab[MAXFD+1]; +#endif + +/* + * we now use this function always. + */ +int +fd_clexec(fd) + int fd; +{ +#ifndef F_SETFD + if (fd >= 0 && fd < sizeof(clexec_tab)) { + clexec_tab[fd] = 1; + return 0; + } + return -1; +#else + return fcntl(fd, F_SETFD, 1); +#endif +} + + +/* + * execute command tree + */ +int +execute(t, flags) + struct op * volatile t; + volatile int flags; /* if XEXEC don't fork */ +{ + int i; + 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; + + /* Is this the end of a pipeline? If so, we want to evaluate the + * command arguments + bool_t 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, -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); + break; + + case TPAREN: + rv = execute(t->left, flags|XFORK); + break; + + case TPIPE: + flags |= XFORK; + flags &= ~XEXEC; + e->savefd[0] = savefd(0, 0); + (void) ksh_dup2(e->savefd[0], 0, FALSE); /* stdin of first */ + e->savefd[1] = savefd(1, 0); + 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, 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, 0); + if (!(flags&XBGND) && !(flags&XXCOM)) + rv = i; + break; + + case TLIST: + while (t->type == TLIST) { + execute(t->left, flags & XERROK); + t = t->right; + } + rv = execute(t, flags & XERROK); + break; + +#ifdef KSH + case TCOPROC: + { +# ifdef JOB_SIGS + sigset_t omask; +# endif /* JOB_SIGS */ + +# ifdef JOB_SIGS + /* Block sigchild as we are using things changed in the + * signal handler + */ + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + e->type = E_ERRH; + i = ksh_sigsetjmp(e->jbuf, 0); + if (i) { + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); + quitenv(); + unwind(i); + /*NOTREACHED*/ + } +# endif /* JOB_SIGS */ + /* 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, 0); + e->savefd[1] = savefd(1, 0); + + openpipe(pv); + 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; + } +# ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); + e->type = E_EXEC; /* no more need for error handler */ +# endif /* JOB_SIGS */ + + /* 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, + coproc.readw); + break; + } +#endif /* KSH */ + + 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); + break; + + case TOR: + case TAND: + rv = execute(t->left, XERROK); + if (t->right != NULL && (rv == 0) == (t->type == TAND)) + rv = execute(t->right, flags & XERROK); + else + flags |= XERROK; + break; + + case TBANG: + rv = !execute(t->right, XERROK); + break; + +#ifdef KSH + 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; + } +#endif /* KSH */ + + case TFOR: +#ifdef KSH + case TSELECT: + { + volatile bool_t is_first = TRUE; +#endif /* KSH */ + ap = (t->vars != NULL) ? + eval(t->vars, DOBLANK|DOGLOB|DOTILDE) + : e->loc->argv + 1; + e->type = E_LOOP; + while (1) { + i = ksh_sigsetjmp(e->jbuf, 0); + if (!i) + break; + if ((e->flags&EF_BRKCONT_PASS) + || (i != LBREAK && i != LCONTIN)) + { + quitenv(); + 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); + } + } +#ifdef KSH + 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); + } + } + } +#endif /* KSH */ + break; + + case TWHILE: + case TUNTIL: + e->type = E_LOOP; + while (1) { + i = ksh_sigsetjmp(e->jbuf, 0); + if (!i) + break; + if ((e->flags&EF_BRKCONT_PASS) + || (i != LBREAK && i != LCONTIN)) + { + quitenv(); + unwind(i); + } else if (i == LBREAK) { + rv = 0; + goto Break; + } + } + rv = 0; /* in case of a continue */ + while ((execute(t->left, XERROK) == 0) == (t->type == TWHILE)) + rv = execute(t->right, flags & XERROK); + break; + + case TIF: + case TELIF: + if (t->right == NULL) + break; /* should be error */ + rv = execute(t->left, XERROK) == 0 ? + execute(t->right->left, flags & XERROK) : + execute(t->right->right, flags & 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); + break; + + case TBRACE: + rv = execute(t->left, flags & 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); + break; + + case TEXEC: /* an eval'd TCOM */ + s = t->args[0]; + ap = makenv(); +#ifndef F_SETFD + for (i = 0; i < sizeof(clexec_tab); i++) + if (clexec_tab[i]) { + close(i); + clexec_tab[i] = 0; + } +#endif + restoresigs(); + cleanup_proc_env(); + /* XINTACT bit is for OS2 */ + ksh_execve(t->str, t->args, ap, (flags & XINTACT) ? 1 : 0); + if (errno == ENOEXEC) + scriptexec(t, ap); + else + errorf("%s: %s", s, strerror(errno)); + } + Break: + exstat = rv; + + quitenv(); /* restores IO */ + if ((flags&XEXEC)) + unwind(LEXIT); /* exit child */ + if (rv != 0 && !(flags & XERROK)) { + if (Flag(FERREXIT)) + unwind(LERROR); + trapsig(SIGERR_); + } + return rv; +} + +/* + * execute simple command + */ + +static int +comexec(t, tp, ap, flags) + struct op *t; + struct tbl *volatile tp; + register char **ap; + int volatile flags; +{ + int i; + volatile int rv = 0; + register char *cp; + register 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; + +#ifdef KSH + /* 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 scipt) - + * 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); + } +#endif /* KSH */ + + /* 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)) + 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 ((cp = *ap) == NULL) { + rv = subst_exstat; + goto Leave; + } else if (!tp) { + if (Flag(FRESTRICTED) && ksh_strchr_dirsep(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 = ksh_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); + 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(); + unwind(i); + /*NOTREACHED*/ + default: + quitenv(); + 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; + } + +#ifdef KSH + 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); + } +#endif /* KSH */ + + 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, -1); + break; + } + Leave: + if (flags & XEXEC) { + exstat = rv; + unwind(LLEAVE); + } + return rv; +} + +static void +scriptexec(tp, ap) + register struct op *tp; + register 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; +#ifdef SHARPBANG + { + char buf[LINE]; + register char *cp; + register int fd, n; + + buf[0] = '\0'; + if ((fd = open(tp->str, O_RDONLY)) >= 0) { + if ((n = read(fd, buf, LINE - 1)) > 0) + buf[n] = '\0'; + (void) close(fd); + } + if ((buf[0] == '#' && buf[1] == '!' && (cp = &buf[2])) +# ifdef OS2 + || (strncmp(buf, "extproc", 7) == 0 && isspace(buf[7]) + && (cp = &buf[7])) +# endif /* OS2 */ + ) + { + while (*cp && (*cp == ' ' || *cp == '\t')) + cp++; + if (*cp && *cp != '\n') { + char *a0 = cp, *a1 = (char *) 0; +# ifdef OS2 + char *a2 = cp; +# endif /* OS2 */ + + while (*cp && *cp != '\n' && *cp != ' ' + && *cp != '\t') + { +# ifdef OS2 + /* Allow shell search without prepended path + * if shell with / in pathname cannot be found. + * Use / explicitly so \ can be used if explicit + * needs to be forced. + */ + if (*cp == '/') + a2 = cp + 1; +# endif /* OS2 */ + cp++; + } + if (*cp && *cp != '\n') { + *cp++ = '\0'; + while (*cp + && (*cp == ' ' || *cp == '\t')) + cp++; + if (*cp && *cp != '\n') { + a1 = cp; + /* all one argument */ + while (*cp && *cp != '\n') + cp++; + } + } + if (*cp == '\n') { + *cp = '\0'; + if (a1) + *tp->args-- = a1; +# ifdef OS2 + if (a0 != a2) { + char *tmp_a0 = str_nsave(a0, + strlen(a0) + 5, ATEMP); + if (search_access(tmp_a0, X_OK, + (int *) 0)) + a0 = a2; + afree(tmp_a0, ATEMP); + } +# endif /* OS2 */ + shell = a0; + } + } +# ifdef OS2 + } else { + /* Use ksh documented shell default if present + * else use OS2_SHELL which is assumed to need + * the /c option and '\' as dir separator. + */ + char *p = shell; + + shell = str_val(global("EXECSHELL")); + if (shell && *shell) + shell = search(shell, path, X_OK, (int *) 0); + if (!shell || !*shell) { + shell = p; + *tp->args-- = "/c"; + for (p = tp->str; *p; p++) + if (*p == '/') + *p = '\\'; + } +# endif /* OS2 */ + } + } +#endif /* SHARPBANG */ + *tp->args = shell; + + ksh_execve(tp->args[0], tp->args, ap, 0); + + /* report both the program that was run and the bogus shell */ + errorf("%s: %s: %s", tp->str, shell, strerror(errno)); +} + +int +shcomexec(wp) + register char **wp; +{ + register struct tbl *tp; + + tp = tsearch(&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(name, h, create) + 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 = tsearch(&l->funs, name, h); + if (tp) + break; + if (!l->next && create) { + tp = tenter(&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(name, t) + 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 */ + tdelete(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(name, func) + const char *name; + int (*func) ARGS((char **)); +{ + register 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 = tenter(&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(name, flags) + const char *name; + int flags; /* FC_* */ +{ + 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 (ksh_strchr_dirsep(name) != NULL) { + insert = 0; + /* prevent FPATH search below */ + flags &= ~FC_FUNC; + goto Search; + } + tbi = (flags & FC_BI) ? tsearch(&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 = tsearch(&taliases, name, h); + if (tp && (tp->flag & ISSET) && eaccess(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 = tenter(&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) { + tp->val.s = tp == &temp ? npath : str_save(npath, APERM); + 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(all) + int all; /* just relative or all */ +{ + struct tbl *tp; + struct tstate ts; + + for (twalk(&ts, &taliases); (tp = tnext(&ts)) != NULL; ) + if ((tp->flag&ISSET) && (all || !ISDIRSEP(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(path, mode, errnop) + const char *path; + int mode; + int *errnop; /* set if candidate found, but not suitable */ +{ +#ifndef OS2 + int ret, err = 0; + struct stat statb; + + if (stat(path, &statb) < 0) + return -1; + ret = eaccess(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) + /* This 'cause access() says root can execute everything */ + || !(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) + { + ret = -1; + err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES; + } + if (err && errnop && !*errnop) + *errnop = err; + return ret; +#else /* !OS2 */ + /* + * NOTE: ASSUMES path can be modified and has enough room at the + * end of the string for a suffix (ie, 4 extra characters). + * Certain code knows this (eg, eval.c(globit()), + * exec.c(search())). + */ + static char *xsuffixes[] = { ".ksh", ".exe", ".", ".sh", ".cmd", + ".com", ".bat", (char *) 0 + }; + static char *rsuffixes[] = { ".ksh", ".", ".sh", ".cmd", ".bat", + (char *) 0 + }; + int i; + char *mpath = (char *) path; + char *tp = mpath + strlen(mpath); + char *p; + char **sfx; + + /* If a suffix has been specified, check if it is one of the + * suffixes that indicate the file is executable - if so, change + * the access test to R_OK... + * This code assumes OS/2 files can have only one suffix... + */ + if ((p = strrchr((p = ksh_strrchr_dirsep(mpath)) ? p : mpath, '.'))) { + if (mode == X_OK) + mode = R_OK; + return search_access1(mpath, mode, errnop); + } + /* Try appending the various suffixes. Different suffixes for + * read and execute 'cause we don't want to read an executable... + */ + sfx = mode == R_OK ? rsuffixes : xsuffixes; + for (i = 0; sfx[i]; i++) { + strcpy(tp, p = sfx[i]); + if (search_access1(mpath, R_OK, errnop) == 0) + return 0; + *tp = '\0'; + } + return -1; +#endif /* !OS2 */ +} + +#ifdef OS2 +static int +search_access1(path, mode, errnop) + 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 = eaccess(path, mode); + if (ret < 0) + err = errno; /* File exists, but we can't access it */ + else if (!S_ISREG(statb.st_mode)) { + ret = -1; + err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES; + } + if (err && errnop && !*errnop) + *errnop = err; + return ret; +} +#endif /* OS2 */ + +/* + * search for command with PATH + */ +char * +search(name, path, mode, errnop) + 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; +#ifdef OS2 + /* Xinit() allocates 8 additional bytes, so appended suffixes won't + * overflow the memory. + */ + namelen = strlen(name) + 1; + Xinit(xs, xp, namelen, ATEMP); + memcpy(Xstring(xs, xp), name, namelen); + + if (ksh_strchr_dirsep(name)) { + if (search_access(Xstring(xs, xp), mode, errnop) >= 0) + return Xstring(xs, xp); /* not Xclose() - see above */ + Xfree(xs, xp); + return NULL; + } + + /* Look in current context always. (os2 style) */ + if (search_access(Xstring(xs, xp), mode, errnop) == 0) + return Xstring(xs, xp); /* not Xclose() - xp may be wrong */ +#else /* OS2 */ + if (ksh_strchr_dirsep(name)) { + if (search_access(name, mode, errnop) == 0) + return (char *) name; + return NULL; + } + + namelen = strlen(name) + 1; + Xinit(xs, xp, 128, ATEMP); +#endif /* OS2 */ + + sp = path; + while (sp != NULL) { + xp = Xstring(xs, xp); + if (!(p = strchr(sp, PATHSEP))) + p = sp + strlen(sp); + if (p != sp) { + XcheckN(xs, xp, p - sp); + memcpy(xp, sp, p - sp); + xp += p - sp; + *xp++ = DIRSEP; + } + sp = p; + XcheckN(xs, xp, namelen); + memcpy(xp, name, namelen); + if (search_access(Xstring(xs, xp), mode, errnop) == 0) +#ifdef OS2 + return Xstring(xs, xp); /* Not Xclose() - see above */ +#else /* OS2 */ + return Xclose(xs, xp + namelen); +#endif /* OS2 */ + if (*sp++ == '\0') + sp = NULL; + } + Xfree(xs, xp); + return NULL; +} + +static int +call_builtin(tp, wp) + 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(iop, tp) + register struct ioword *iop; + struct tbl *tp; +{ + register int u = -1; + char *cp = iop->name; + int iotype = iop->flag & IOTYPE; + int do_open = 1, do_close = 0, UNINITIALIZED(flags); + 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; + } + break; + } + } + if (do_open) { + if (Flag(FRESTRICTED) && (flags & O_CREAT)) { + warningf(TRUE, "%s: restricted", cp); + return -1; + } + u = open(cp, flags, 0666); +#ifdef OS2 + if (u < 0 && strcmp(cp, "/dev/null") == 0) + u = open("nul", flags, 0666); +#endif /* OS2 */ + } + 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, 1); + } + + 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); +#ifdef KSH + /* 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); + } +#endif /* KSH */ + } + 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(content, sub) + 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 = ksh_sigsetjmp(e->jbuf, 0); + if (i) { + source = osource; + quitenv(); + shf_close(shf); /* after quitenv */ + 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) != LWORD) + internal_errorf(1, "herein: yylex"); + source = osource; + shf_puts(evalstr(yylval.cp, 0), shf); + } else + shf_puts(content, shf); + + quitenv(); + + 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; +} + +#if defined(KSH) || defined(EDIT) +/* + * ksh special - the select command processing section + * print the args in column form - assuming that we can + */ +static char * +do_selectargs(ap, print_menu) + register char **ap; + bool_t 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; +} info; + +static char *select_fmt_entry ARGS((void *arg, int i, char *buf, int buflen)); + +/* format a single select menu item */ +static char * +select_fmt_entry(arg, i, buf, buflen) + 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(ap) + 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 ARGS((void *arg, int i, char *buf, int buflen)); + +static char * +plain_fmt_entry(arg, i, buf, buflen) + void *arg; + int i; + char *buf; + int buflen; +{ + shf_snprintf(buf, buflen, "%s", ((char *const *)arg)[i]); + return buf; +} + +int +pr_list(ap) + 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 /* KSH || EDIT */ +#ifdef KSH + +/* + * [[ ... ]] 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(te, meta) + 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(te, op, do_eval) + 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(te, op, opnd1, opnd2, do_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(te, offset, msg) + Test_env *te; + int offset; + const char *msg; +{ + te->flags |= TEF_ERROR; + internal_errorf(0, "dbteste_error: %s (offset %d)", msg, offset); +} +#endif /* KSH */ diff --git a/expand.h b/expand.h new file mode 100644 index 0000000..ed1aeb7 --- /dev/null +++ b/expand.h @@ -0,0 +1,107 @@ +/* $OpenBSD: expand.h,v 1.3 2001/03/26 16:19:45 todd 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_ ARGS((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 { \ + register 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 new file mode 100644 index 0000000..3ef1fc6 --- /dev/null +++ b/expr.c @@ -0,0 +1,607 @@ +/* $OpenBSD: expr.c,v 1.8 2003/02/28 09:45:09 jmc Exp $ */ + +/* + * Korn expression evaluation + */ +/* + * todo: better error handling: if in builtin, should be builtin error, etc. + */ + +#include "sh.h" +#include + + +/* 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 ?:,&&,||) */ + 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 ARGS((Expr_state *es, enum error_type type, + const char *str)) GCC_FUNC_ATTR(noreturn); +static struct tbl *evalexpr ARGS((Expr_state *es, enum prec prec)); +static void token ARGS((Expr_state *es)); +static struct tbl *do_ppmm ARGS((Expr_state *es, enum token op, + struct tbl *vasn, bool_t is_prefix)); +static void assign_check ARGS((Expr_state *es, enum token op, + struct tbl *vasn)); +static struct tbl *tempvar ARGS((void)); +static struct tbl *intvar ARGS((Expr_state *es, struct tbl *vp)); + +/* + * parse and evalute expression + */ +int +evaluate(expr, rval, error_ok) + const char *expr; + long *rval; + int error_ok; +{ + struct tbl v; + int ret; + + v.flag = DEFINED|INTEGER; + v.type = 0; + ret = v_evaluate(&v, expr, error_ok); + *rval = v.val.i; + return ret; +} + +/* + * parse and evalute expression, storing result in vp. + */ +int +v_evaluate(vp, expr, error_ok) + struct tbl *vp; + const char *expr; + volatile int error_ok; +{ + 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.evaling = (struct tbl *) 0; + + newenv(E_ERRH); + i = ksh_sigsetjmp(e->jbuf, 0); + if (i) { + /* Clear EXPRINEVAL in of any variables we were playing with */ + if (curstate.evaling) + curstate.evaling->flag &= ~EXPRINEVAL; + quitenv(); + 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); + else + /* can fail if readony */ + setstr(vp, str_val(v), error_ok); + + quitenv(); + + return 1; +} + +static void +evalerr(es, type, str) + Expr_state *es; + enum error_type type; + const char *str; +{ + char tbuf[2]; + const char *s; + + 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(es, prec) + Expr_state *es; + enum prec prec; +{ + struct tbl *vl, UNINITIALIZED(*vr), *vasn; + enum token op; + long UNINITIALIZED(res); + + 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: + res = vl->val.i / vr->val.i; + break; + case O_MOD: + case O_MODASN: + 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); + else + setint(vasn, res); + vl = vr; + } else if (op != O_TERN) + vl->val.i = res; + } + return vl; +} + +static void +token(es) + Expr_state *es; +{ + const char *cp; + int c; + char *tvar; + + /* skip white space */ + for (cp = es->tokp; (c = *cp), isspace(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; + } +#ifdef KSH + 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 + */ + ; + } +#endif /* KSH */ + 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) == 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(es, op, vasn, is_prefix) + Expr_state *es; + enum token op; + struct tbl *vasn; + bool_t 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); + else + setint(vasn, vl->val.i); + if (!is_prefix) /* undo the inc/dec */ + vl->val.i = oval; + + return vl; +} + +static void +assign_check(es, op, vasn) + Expr_state *es; + enum token op; + struct tbl *vasn; +{ + if (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() +{ + register 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(es, vp) + 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) == 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); + vp->flag &= ~EXPRINEVAL; + es->evaling = (struct tbl *) 0; + } + return vq; +} diff --git a/history.c b/history.c new file mode 100644 index 0000000..4ec7d6b --- /dev/null +++ b/history.c @@ -0,0 +1,1192 @@ +/* $OpenBSD: history.c,v 1.17 2003/02/28 09:45:09 jmc Exp $ */ + +/* + * command history + * + * only implements in-memory history. + */ + +/* + * This file contains + * a) the original in-memory history mechanism + * b) a simple file saving history mechanism done by sjg@zen + * define EASY_HISTORY to get this + * c) 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 "ksh_stat.h" + +#ifdef HISTORY +# ifdef EASY_HISTORY + +# ifndef HISTFILE +# ifdef OS2 +# define HISTFILE "history.ksh" +# else /* OS2 */ +# define HISTFILE ".pdksh_history" +# endif /* OS2 */ +# endif + +# else +/* Defines and includes for the complicated case */ + +# include +# include + +/* + * variables for handling the data file + */ +static int histfd; +static int hsize; + +static int hist_count_lines ARGS((unsigned char *, int)); +static int hist_shrink ARGS((unsigned char *, int)); +static unsigned char *hist_skip_back ARGS((unsigned char *,int *,int)); +static void histload ARGS((Source *, unsigned char *, int)); +static void histinsert ARGS((Source *, int, unsigned char *)); +static void writehistfile ARGS((int, char *)); +static int sprinkle ARGS((int)); + +# ifdef MAP_FILE +# define MAP_FLAGS (MAP_FILE|MAP_PRIVATE) +# else +# define MAP_FLAGS MAP_PRIVATE +# endif + +# endif /* of EASY_HISTORY */ + +static int hist_execute ARGS((char *cmd)); +static int hist_replace ARGS((char **hp, const char *pat, const char *rep, + int global)); +static char **hist_get ARGS((const char *str, int approx, int allow_cur)); +static char **hist_get_newest ARGS((int allow_cur)); +static char **hist_get_oldest ARGS(()); +static void histbackup ARGS((void)); + +static char **current; /* current position in history[] */ +static int curpos; /* current index 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(wp) + char **wp; +{ + struct shf *shf; + struct temp UNINITIALIZED(*tf); + 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; + + while ((optc = ksh_getopt(wp, &builtin_opt, "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != EOF) + switch (optc) { + case 'e': + p = builtin_opt.optarg; + if (strcmp(p, "-") == 0) + sflag++; + else { + editor = str_nsave(p, strlen(p) + 4, ATEMP); + strcat(editor, " $_"); + } + 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} $_"); + 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(cmd) + 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; + } +#ifdef EASY_HISTORY + if (p != cmd) + histappend(p, TRUE); + else +#endif /* EASY_HISTORY */ + 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); + source = sold; + return ret; +} + +static int +hist_replace(hp, pat, rep, global) + 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(str, approx, allow_cur) + 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 (hp < 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 */ +static char ** +hist_get_newest(allow_cur) + 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 newest command in the history */ +static char ** +hist_get_oldest() +{ + if (histptr <= history) { + bi_errorf("no history (yet)"); + return (char **) 0; + } + return history; +} + +/******************************/ +/* Back up over last histsave */ +/******************************/ +static void +histbackup() +{ + 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() +{ + return current; +} + +int +histN() +{ + return curpos; +} + +int +histnum(n) + int n; +{ + int last = histptr - history; + + if (n < 0 || n >= last) { + current = histptr; + curpos = last; + return last; + } else { + current = &history[n]; + curpos = 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(start, fwd, str, anchored) + 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; +} + +/* + * set history + * this means reallocating the dataspace + */ +void +sethistsize(n) + 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(name) + 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 + */ +# ifdef EASY_HISTORY + if (hname) { + afree(hname, APERM); + hname = NULL; + } +# else + 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; + } +# endif + + hist_init(hist_source); +} + +/* + * initialise the history vector + */ +void +init_histvec() +{ + if (history == (char **)NULL) { + histsize = HISTORYSIZE; + history = (char **)alloc(histsize*sizeof (char *), APERM); + histptr = history - 1; + } +} + +# ifdef EASY_HISTORY +/* + * save command in history + */ +void +histsave(lno, cmd, dowrite) + int lno; /* ignored (compatibility with COMPLEX_HISTORY) */ + const char *cmd; + int dowrite; /* ignored (compatibility with COMPLEX_HISTORY) */ +{ + register char **hp = histptr; + char *cp; + + if (++hp >= history + histsize) { /* remove oldest command */ + afree((void*)history[0], APERM); + memmove(history, history + 1, + sizeof(history[0]) * (histsize - 1)); + hp = &history[histsize - 1]; + } + *hp = str_save(cmd, APERM); + /* trash trailing newline but allow imbedded newlines */ + cp = *hp + strlen(*hp); + if (cp > *hp && cp[-1] == '\n') + cp[-1] = '\0'; + histptr = hp; +} + +/* + * Append an entry to the last saved command. Used for multiline + * commands + */ +void +histappend(cmd, nl_separate) + const char *cmd; + int nl_separate; +{ + int hlen, clen; + char *p; + + hlen = strlen(*histptr); + clen = strlen(cmd); + if (clen > 0 && cmd[clen-1] == '\n') + clen--; + p = *histptr = (char *) aresize(*histptr, hlen + clen + 2, APERM); + p += hlen; + if (nl_separate) + *p++ = '\n'; + memcpy(p, cmd, clen); + p[clen] = '\0'; +} + +/* + * 92-04-25 + * A simple history file implementation. + * At present we only save the history when we exit. + * This can cause problems when there are multiple shells are + * running under the same user-id. The last shell to exit gets + * to save its history. + */ +void +hist_init(s) + Source *s; +{ + char *f; + FILE *fh; + + if (Flag(FTALKING) == 0) + return; + + hstarted = 1; + + hist_source = s; + + if ((f = str_val(global("HISTFILE"))) == NULL || *f == '\0') { +# if 1 /* Don't use history file unless the user asks for it */ + hname = NULL; + return; +# else + char *home = str_val(global("HOME")); + int len; + + if (home == NULL) + home = null; + f = HISTFILE; + hname = alloc(len = strlen(home) + strlen(f) + 2, APERM); + shf_snprintf(hname, len, "%s/%s", home, f); +# endif + } else + hname = str_save(f, APERM); + + if ((fh = fopen(hname, "r"))) { + int pos = 0, nread = 0; + int contin = 0; /* continuation of previous command */ + char *end; + char hline[LINE + 1]; + + while (1) { + if (pos >= nread) { + pos = 0; + nread = fread(hline, 1, LINE, fh); + if (nread <= 0) + break; + hline[nread] = '\0'; + } + end = strchr(hline + pos, 0); /* will always succeed */ + if (contin) + histappend(hline + pos, 0); + else { + hist_source->line++; + histsave(0, hline + pos, 0); + } + pos = end - hline + 1; + contin = end == &hline[nread]; + } + fclose(fh); + } +} + +/* + * save our history. + * We check that we do not have more than we are allowed. + * If the history file is read-only we do nothing. + * Handy for having all shells start with a useful history set. + */ + +void +hist_finish() +{ + static int once; + FILE *fh; + register int i; + register char **hp; + + if (once++) + return; + /* check how many we have */ + i = histptr - history; + if (i >= histsize) + hp = &histptr[-histsize]; + else + hp = history; + if (hname && (fh = fopen(hname, "w"))) + { + for (i = 0; hp + i <= histptr && hp[i]; i++) + fprintf(fh, "%s%c", hp[i], '\0'); + fclose(fh); + } +} + +# else /* EASY_HISTORY */ + +/* + * 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(lno, cmd, dowrite) + int lno; + const char *cmd; + int dowrite; +{ + register 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 + * + */ +# define HMAGIC1 0xab +# define HMAGIC2 0xcd +# define COMMAND 0xff + +void +hist_init(s) + 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, 0); + + (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_FLAGS, histfd, 0); + /* + * check on its validity + */ + if (base == MAP_FAILED || *base != HMAGIC1 || base[1] != HMAGIC2) { + if (base != (unsigned char *)-1) + munmap((caddr_t)base, hsize); + hist_finish(); + unlink(hname); + 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)) + unlink(hname); + 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(base, bytes) + register unsigned char *base; + register 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(oldbase, oldbytes) + 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 = creat(nfile, 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(base, bytes, no) + unsigned char *base; + int *bytes; + int no; +{ + register int lines = 0; + register 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(s, base, bytes) + Source *s; + register unsigned char *base; + register int bytes; +{ + State state; + int lno; + unsigned char *line; + + 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; + histsave(lno, (char *)line, 0); + } + state = shdr; + } + } + } +} + +/* + * Insert a line into the history at a specified number + */ +static void +histinsert(s, lno, line) + Source *s; + int lno; + unsigned char *line; +{ + register 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(lno, cmd) + int lno; + char *cmd; +{ + int sizenow; + unsigned char *base; + unsigned char *new; + int bytes; + 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_FLAGS, 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) flock(histfd, LOCK_UN); + (void) close(histfd); + histfd = 0; +} + +/* + * add magic to the history file + */ +static int +sprinkle(fd) + int fd; +{ + static char mag[] = { HMAGIC1, HMAGIC2 }; + + return(write(fd, mag, 2) != 2); +} + +# endif +#else /* HISTORY */ + +/* No history to be compiled in: dummy routines to avoid lots more ifdefs */ +void +init_histvec() +{ +} +void +hist_init(s) + Source *s; +{ +} +void +hist_finish() +{ +} +void +histsave(lno, cmd, dowrite) + int lno; + const char *cmd; + int dowrite; +{ + errorf("history not enabled"); +} +#endif /* HISTORY */ diff --git a/io.c b/io.c new file mode 100644 index 0000000..2834886 --- /dev/null +++ b/io.c @@ -0,0 +1,560 @@ +/* $OpenBSD: io.c,v 1.12 2003/03/10 03:48:16 david Exp $ */ + +/* + * shell buffered IO and formatted output + */ + +#include +#include "sh.h" +#include "ksh_stat.h" + +static int initio_done; + +/* + * formatted output functions + */ + + +/* A shell error occurred (eg, syntax error, etc.) */ +void +#ifdef HAVE_PROTOTYPES +errorf(const char *fmt, ...) +#else +errorf(fmt, va_alist) + const char *fmt; + va_dcl +#endif +{ + va_list va; + + shl_stdout_ok = 0; /* debugging: note that stdout not valid */ + exstat = 1; + if (*fmt) { + error_prefix(TRUE); + SH_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 +#ifdef HAVE_PROTOTYPES +warningf(int fileline, const char *fmt, ...) +#else +warningf(fileline, fmt, va_alist) + int fileline; + const char *fmt; + va_dcl +#endif +{ + va_list va; + + error_prefix(fileline); + SH_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 +#ifdef HAVE_PROTOTYPES +bi_errorf(const char *fmt, ...) +#else +bi_errorf(fmt, va_alist) + const char *fmt; + va_dcl +#endif +{ + 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); + SH_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 +#ifdef HAVE_PROTOTYPES +internal_errorf(int jump, const char *fmt, ...) +#else +internal_errorf(jump, fmt, va_alist) + int jump; + const char *fmt; + va_dcl +#endif +{ + va_list va; + + error_prefix(TRUE); + shf_fprintf(shl_out, "internal error: "); + SH_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(fileline) + 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 +#ifdef HAVE_PROTOTYPES +shellf(const char *fmt, ...) +#else +shellf(fmt, va_alist) + const char *fmt; + va_dcl +#endif +{ + va_list va; + + if (!initio_done) /* shl_out may not be set up yet... */ + return; + SH_VA_START(va, fmt); + shf_vfprintf(shl_out, fmt, va); + va_end(va); + shf_flush(shl_out); +} + +/* printf to shl_stdout (stdout) */ +void +#ifdef HAVE_PROTOTYPES +shprintf(const char *fmt, ...) +#else +shprintf(fmt, va_alist) + const char *fmt; + va_dcl +#endif +{ + va_list va; + + if (!shl_stdout_ok) + internal_errorf(1, "shl_stdout not valid"); + SH_VA_START(va, fmt); + shf_vfprintf(shl_stdout, fmt, va); + va_end(va); +} + +#ifdef KSH_DEBUG +static struct shf *kshdebug_shf; + +void +kshdebug_init_() +{ + 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 +# ifdef HAVE_PROTOTYPES +kshdebug_printf_(const char *fmt, ...) +# else +kshdebug_printf_(fmt, va_alist) + const char *fmt; + va_dcl +# endif +{ + va_list va; + + if (!kshdebug_shf) + return; + SH_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_(str, mem, nbytes) + 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(fd) + 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() +{ + 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(ofd, nfd, errok) + int ofd; + int nfd; + int errok; +{ + int ret = dup2(ofd, nfd); + + if (ret < 0 && errno != EBADF && !errok) + errorf("too many files open in shell"); + +#ifdef DUP2_BROKEN + /* Ultrix systems like to preserve the close-on-exec flag */ + if (ret >= 0) + (void) fcntl(nfd, F_SETFD, 0); +#endif /* DUP2_BROKEN */ + + return ret; +} + +/* + * move fd from user space (0<=fd<10) to shell space (fd>=10), + * set close-on-exec flag. + */ +int +savefd(fd, noclose) + int fd; + int noclose; +{ + int nfd; + + if (fd < FDBASE) { + nfd = ksh_dupbase(fd, FDBASE); + if (nfd < 0) { + if (errno == EBADF) + return -1; + else + errorf("too many files open in shell"); + } + if (!noclose) + close(fd); + } else + nfd = fd; + fd_clexec(nfd); + return nfd; +} + +void +restfd(fd, ofd) + int fd, ofd; +{ + if (fd == 2) + shf_flush(&shf_iob[fd]); + if (ofd < 0) /* original fd closed */ + close(fd); + else { + ksh_dup2(ofd, fd, TRUE); /* XXX: what to do if this fails? */ + close(ofd); + } +} + +void +openpipe(pv) + register int *pv; +{ + if (pipe(pv) < 0) + errorf("can't create pipe - try again"); + pv[0] = savefd(pv[0], 0); + pv[1] = savefd(pv[1], 0); +} + +void +closepipe(pv) + register 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(name, mode, emsgp) + char *name; + int mode; + const char **emsgp; +{ + int fd, fl; + + if (isdigit(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; +#ifdef OS2 + if (mode == W_OK ) { + if (setmode(fd, O_TEXT) == -1) { + if (emsgp) + *emsgp = "couldn't set write mode"; + return -1; + } + } else if (mode == R_OK) + if (setmode(fd, O_BINARY) == -1) { + if (emsgp) + *emsgp = "couldn't set read mode"; + return -1; + } +#else /* OS2 */ + /* 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; + } +#endif /* OS2 */ + return fd; + } +#ifdef KSH + else if (name[0] == 'p' && !name[1]) + return coproc_getfd(mode, emsgp); +#endif /* KSH */ + if (emsgp) + *emsgp = "illegal file descriptor name"; + return -1; +} + +#ifdef KSH +/* Called once from main */ +void +coproc_init() +{ + 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(fd) + 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(fd) + 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(fd) + 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(mode, emsgp) + 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(reuse) + 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; + } +} +#endif /* KSH */ + + +/* + * temporary files + */ + +struct temp * +maketemp(ap, type, tlist) + Area *ap; + Temp_type type; + struct temp **tlist; +{ + static unsigned int inc; + 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; +#ifdef __OpenBSD__ + shf_snprintf(path, len, "%s/shXXXXXXXX", dir); + fd = mkstemp(path); + if (fd >= 0) + tp->shf = shf_fdopen(fd, SHF_WR, (struct shf *) 0); +#else + while (1) { + /* Note that temp files need to fit 8.3 DOS limits */ + shf_snprintf(path, len, "%s/sh%05u.%03x", + dir, (unsigned) procpid, inc++); + /* Mode 0600 to be paranoid, O_TRUNC in case O_EXCL isn't + * really there. + */ + fd = open(path, O_RDWR|O_CREAT|O_EXCL|O_TRUNC, 0600); + if (fd >= 0) { + tp->shf = shf_fdopen(fd, SHF_WR, (struct shf *) 0); + break; + } + if (errno != EINTR +#ifdef EEXIST + && errno != EEXIST +#endif /* EEXIST */ +#ifdef EISDIR + && errno != EISDIR +#endif /* EISDIR */ + ) + /* Error must be printed by caller: don't know here if + * errorf() or bi_errorf() should be used. + */ + break; + } +#endif /* __OpenBSD__ */ + tp->pid = procpid; + + tp->next = *tlist; + *tlist = tp; + + return tp; +} diff --git a/jobs.c b/jobs.c new file mode 100644 index 0000000..2013674 --- /dev/null +++ b/jobs.c @@ -0,0 +1,1851 @@ +/* $OpenBSD: jobs.c,v 1.19 2003/03/13 09:03:07 deraadt 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 :) + * + * Notes regarding the copious ifdefs: + * - JOB_SIGS is independent of JOBS - it is defined if there are modern + * signal and wait routines available. This is prefered, even when + * JOBS is not defined, since the shell will not otherwise notice when + * background jobs die until the shell waits for a foreground process + * to die. + * - TTY_PGRP defined iff JOBS is defined - defined if there are tty + * process groups + * - NEED_PGRP_SYNC defined iff JOBS is defined - see comment below + */ + +#include "sh.h" +#include "ksh_stat.h" +#include "ksh_wait.h" +#include "ksh_times.h" +#include "tty.h" + +/* Start of system configuration stuff */ + +/* We keep CHILD_MAX zombie processes around (exact value isn't critical) */ +#ifndef CHILD_MAX +# if defined(HAVE_SYSCONF) && defined(_SC_CHILD_MAX) +# define CHILD_MAX sysconf(_SC_CHILD_MAX) +# else /* _SC_CHILD_MAX */ +# ifdef _POSIX_CHILD_MAX +# define CHILD_MAX ((_POSIX_CHILD_MAX) * 2) +# else /* _POSIX_CHILD_MAX */ +# define CHILD_MAX 20 +# endif /* _POSIX_CHILD_MAX */ +# endif /* _SC_CHILD_MAX */ +#endif /* !CHILD_MAX */ + +#ifdef JOBS +# if defined(HAVE_TCSETPGRP) || defined(TIOCSPGRP) +# define TTY_PGRP +# endif +# ifdef BSD_PGRP +# define setpgid setpgrp +# define getpgID() getpgrp(0) +# else +# define getpgID() getpgrp() +# endif +# if defined(TTY_PGRP) && !defined(HAVE_TCSETPGRP) +int tcsetpgrp ARGS((int fd, pid_t grp)); +int tcgetpgrp ARGS((int fd)); + +int +tcsetpgrp(fd, grp) + int fd; + pid_t grp; +{ + return ioctl(fd, TIOCSPGRP, &grp); +} + +int +tcgetpgrp(fd) + int fd; +{ + int r, grp; + + if ((r = ioctl(fd, TIOCGPGRP, &grp)) < 0) + return r; + return grp; +} +# endif /* !HAVE_TCSETPGRP && TIOCSPGRP */ +#else /* JOBS */ +/* These so we can use ifdef xxx instead of if defined(JOBS) && defined(xxx) */ +# undef TTY_PGRP +# undef NEED_PGRP_SYNC +#endif /* JOBS */ + +/* End of system configuration stuff */ + + +/* 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; + WAIT_T 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 /* flaged 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 */ + clock_t systime; /* system time used by job */ + clock_t usrtime; /* user time used by job */ + Proc *proc_list; /* process list */ + Proc *last_proc; /* last process in list */ +#ifdef KSH + Coproc_id coproc_id; /* 0 or id of coprocess output pipe */ +#endif /* KSH */ +#ifdef TTY_PGRP + TTY_state ttystate; /* saved tty state for stopped jobs */ + pid_t saved_ttypgrp; /* saved tty process group for stopped jobs */ +#endif /* TTY_PGRP */ +}; + +/* 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 + }; +clock_t 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 */ +static INT32 njobs; /* # of jobs started */ +static int child_max; /* CHILD_MAX */ + + +#ifdef JOB_SIGS +/* held_sigchld is set if sigchld occurs before a job is completely started */ +static int held_sigchld; +#endif /* JOB_SIGS */ + +#ifdef JOBS +static struct shf *shl_j; +#endif /* JOBS */ + +#ifdef NEED_PGRP_SYNC +/* On some systems, the kernel doesn't count zombie processes when checking + * if a process group is valid, which can cause problems in creating the + * pipeline "cmd1 | cmd2": if cmd1 can die (and go into the zombie state) + * before cmd2 is started, the kernel doesn't allow the setpgid() for cmd2 + * to succeed. Solution is to create a pipe between the parent and the first + * process; the first process doesn't do anything until the pipe is closed + * and the parent doesn't close the pipe until all the processes are started. + */ +static int j_sync_pipe[2]; +static int j_sync_open; +#endif /* NEED_PGRP_SYNC */ + +#ifdef TTY_PGRP +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 /* TTY_PGRP */ + +static void j_set_async ARGS((Job *j)); +static void j_startjob ARGS((Job *j)); +static int j_waitj ARGS((Job *j, int flags, const char *where)); +static RETSIGTYPE j_sigchld ARGS((int sig)); +static void j_print ARGS((Job *j, int how, struct shf *shf)); +static Job *j_lookup ARGS((const char *cp, int *ecodep)); +static Job *new_job ARGS((void)); +static Proc *new_proc ARGS((void)); +static void check_job ARGS((Job *j)); +static void put_job ARGS((Job *j, int where)); +static void remove_job ARGS((Job *j, const char *where)); +static int kill_job ARGS((Job *j, int sig)); + +/* initialize job control */ +void +j_init(mflagset) + int mflagset; +{ + child_max = CHILD_MAX; /* so syscon() isn't always being called */ + +#ifdef JOB_SIGS + 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); +#else /* JOB_SIGS */ + /* Make sure SIGCHLD isn't ignored - can do odd things under SYSV */ + setsig(&sigtraps[SIGCHLD], SIG_DFL, SS_RESTORE_ORIG|SS_FORCE); +#endif /* JOB_SIGS */ + +#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); + +# ifdef TTY_PGRP + 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); + } + } +# endif /* TTY_PGRP */ + + /* j_change() calls tty_init() */ + if (Flag(FMONITOR)) + j_change(); + else +#endif /* JOBS */ + if (Flag(FTALKING)) + tty_init(TRUE); +} + +/* job cleanup before shell exit */ +void +j_exit() +{ + /* 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 +# ifdef TTY_PGRP + 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); + } +# endif /* TTY_PGRP */ + 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() +{ + int i; + + if (Flag(FMONITOR)) { + /* Don't call get_tty() 'til we own the tty process group */ + tty_init(FALSE); + +# ifdef TTY_PGRP + /* no controlling tty, no SIGT* */ + ttypgrp_ok = tty_fd >= 0 && tty_devtty; + + if (ttypgrp_ok && (our_pgrp = getpgID()) < 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 defined(NTTYDISC) && defined(TIOCSETD) && !defined(HAVE_TERMIOS_H) && !defined(HAVE_TERMIO_H) + if (ttypgrp_ok) { + int ldisc = NTTYDISC; + + if (ioctl(tty_fd, TIOCSETD, &ldisc) < 0) + warningf(FALSE, + "j_init: can't set new line discipline: %s", + strerror(errno)); + } +# endif /* NTTYDISC && TIOCSETD */ + if (!ttypgrp_ok) + warningf(FALSE, "warning: won't have full job control"); +# endif /* TTY_PGRP */ + if (tty_fd >= 0) + get_tty(tty_fd, &tty_state); + } else { +# ifdef TTY_PGRP + 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); + } +# endif /* TTY_PGRP */ + if (!Flag(FTALKING)) + tty_close(); + } +} +#endif /* JOBS */ + +/* execute tree in child subprocess */ +int +exchild(t, flags, close_fd) + struct op *t; + int flags; + int close_fd; /* used if XPCLOSE or XCCLOSE */ +{ + static Proc *last_proc; /* for pipelines */ + + int i; +#ifdef JOB_SIGS + sigset_t omask; +#endif /* JOB_SIGS */ + 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)); + +#ifdef JOB_SIGS + /* no SIGCHLD's while messing with job and process lists */ + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif /* JOB_SIGS */ + + p = new_proc(); + p->next = (Proc *) 0; + p->state = PRUNNING; + WSTATUS(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 { +#ifdef NEED_PGRP_SYNC + if (j_sync_open) { /* should never happen */ + j_sync_open = 0; + closepipe(j_sync_pipe); + } + /* don't do the sync pipe business if there is no pipeline */ + if (flags & XPIPEO) { + openpipe(j_sync_pipe); + j_sync_open = 1; + } +#endif /* NEED_PGRP_SYNC */ + 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)); + j->usrtime = j->systime = 0; + j->state = PRUNNING; + j->pgrp = 0; + j->ppid = procpid; + j->age = ++njobs; + j->proc_list = p; +#ifdef KSH + j->coproc_id = 0; +#endif /* KSH */ + 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"); +#ifdef NEED_PGRP_SYNC + if (j_sync_open) { + closepipe(j_sync_pipe); + j_sync_open = 0; + } +#endif /* NEED_PGRP_SYNC */ +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + 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; +# ifdef NEED_PGRP_SYNC + int first_child_sync = 0; +# endif /* NEED_PGRP_SYNC */ + +# ifdef NEED_PGRP_SYNC + if (j_sync_open) { + /* + * The Parent closes 0, keeps 1 open 'til the whole + * pipeline is started. The First child closes 1, + * keeps 0 open (reads from it). The remaining + * children just have to close 1 (parent has already + * closeed 0). + */ + if (j->pgrp == 0) { /* First process */ + close(j_sync_pipe[ischild]); + j_sync_pipe[ischild] = -1; + first_child_sync = ischild; + } else if (ischild) { + j_sync_open = 0; + closepipe(j_sync_pipe); + } + } +# endif /* NEED_PGRP_SYNC */ + 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); +# ifdef TTY_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 /* TTY_PGRP */ +# ifdef NEED_PGRP_SYNC + if (first_child_sync) { + char c; + while (read(j_sync_pipe[0], &c, 1) == -1 + && errno == EINTR) + ; + close(j_sync_pipe[0]); + j_sync_open = 0; + } +# endif /* NEED_PGRP_SYNC */ + } +#endif /* JOBS */ + + /* used to close pipe input fd */ + if (close_fd >= 0 && (((flags & XPCLOSE) && !ischild) + || ((flags & XCCLOSE) && ischild))) + close(close_fd); + if (ischild) { /* child */ +#ifdef KSH + /* Do this before restoring signal */ + if (flags & XCOPROC) + coproc_cleanup(FALSE); +#endif /* KSH */ +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + cleanup_parents_env(); +#ifdef TTY_PGRP + /* 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 /* TTY_PGRP */ +#ifdef HAVE_NICE + if (Flag(FBGNICE) && (flags & XBGND)) + nice(4); +#endif /* HAVE_NICE */ + 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); + (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; +#ifdef OS2 + if (tty_fd >= 0) + flags |= XINTACT; +#endif /* OS2 */ + tty_close(); + cleartraps(); + execute(t, (flags & XERROK) | XEXEC); /* 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 TTY_PGRP + /* YYY: Is this needed? (see also YYY above) + if (Flag(FMONITOR) && !(flags&(XXCOM|XBGND))) + tcsetpgrp(tty_fd, j->pgrp); + */ +#endif /* TTY_PGRP */ + j_startjob(j); +#ifdef KSH + 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 */ + } +#endif /* KSH */ + 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"); + } + +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + + return rv; +} + +/* start the last job: only used for `command` jobs */ +void +startlast() +{ +#ifdef JOB_SIGS + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif /* JOB_SIGS */ + + 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); + } +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ +} + +/* wait for last job: only used for `command` jobs */ +int +waitlast() +{ + int rv; + Job *j; +#ifdef JOB_SIGS + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif /* JOB_SIGS */ + + j = last_job; + if (!j || !(j->flags & JF_STARTED)) { + if (!j) + warningf(TRUE, "waitlast: no last job"); + else + internal_errorf(0, "waitlast: not started"); +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + return 125; /* not so arbitrary, non-zero value */ + } + + rv = j_waitj(j, JW_NONE, "jw:waitlast"); + +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + + return rv; +} + +/* wait for child, interruptable. */ +int +waitfor(cp, sigp) + const char *cp; + int *sigp; +{ + int rv; + Job *j; + int ecode; + int flags = JW_INTERRUPT|JW_ASYNCNOTIFY; +#ifdef JOB_SIGS + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif /* JOB_SIGS */ + + *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) { +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + return -1; + } + } else if ((j = j_lookup(cp, &ecode))) { + /* don't report normal job completion */ + flags &= ~JW_ASYNCNOTIFY; + if (j->ppid != procpid) { +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + return -1; + } + } else { +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + 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"); + +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + + if (rv < 0) /* we were interrupted */ + *sigp = 128 + -rv; + + return rv; +} + +/* kill (built-in) a job */ +int +j_kill(cp, sig) + const char *cp; + int sig; +{ + Job *j; + Proc *p; + int rv = 0; + int ecode; +#ifdef JOB_SIGS + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif /* JOB_SIGS */ + + if ((j = j_lookup(cp, &ecode)) == (Job *) 0) { +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + 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; + } + } + +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + + return rv; +} + +#ifdef JOBS +/* fg and bg built-ins: called only if Flag(FMONITOR) set */ +int +j_resume(cp, bg) + 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; + WSTATUS(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 TTY_PGRP + /* attach tty to job */ + if (j->state == PRUNNING) { + if (ttypgrp_ok && (j->flags & JF_SAVEDTTY)) { + set_tty(tty_fd, &j->ttystate, TF_NONE); + } + /* 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) { + set_tty(tty_fd, &tty_state, TF_NONE); + } + 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 /* TTY_PGRP */ + 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 TTY_PGRP + if (ttypgrp_ok && (j->flags & JF_SAVEDTTY)) { + set_tty(tty_fd, &tty_state, TF_NONE); + } + 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 /* TTY_PGRP */ + } + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); + bi_errorf("cannot continue job %s: %s", + cp, strerror(err)); + return 1; + } + if (!bg) { +# ifdef TTY_PGRP + if (ttypgrp_ok) { + j->flags &= ~(JF_SAVEDTTY | JF_SAVEDTTYPGRP); + } +# endif /* TTY_PGRP */ + 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() +{ + 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; +} + +/* list jobs for jobs built-in */ +int +j_jobs(cp, slp, nflag) + const char *cp; + int slp; /* 0: short, 1: long, 2: pgrp */ + int nflag; +{ + Job *j, *tmp; + int how; + int zflag = 0; +#ifdef JOB_SIGS + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif /* JOB_SIGS */ + + if (nflag < 0) { /* kludge: print zombies */ + nflag = 0; + zflag = 1; + } + if (cp) { + int ecode; + + if ((j = j_lookup(cp, &ecode)) == (Job *) 0) { +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + 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"); + } +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + return 0; +} + +/* list jobs for top-level notification */ +void +j_notify() +{ + Job *j, *tmp; +#ifdef JOB_SIGS + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif /* JOB_SIGS */ + 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); +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ +} + +/* Return pid of last process in last asynchronous job */ +pid_t +j_async() +{ +#ifdef JOB_SIGS + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); +#endif /* JOB_SIGS */ + + if (async_job) + async_job->flags |= JF_KNOWN; + +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + + 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(j) + 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(j) + Job *j; +{ + Proc *p; + + j->flags |= JF_STARTED; + for (p = j->proc_list; p->next; p = p->next) + ; + j->last_proc = p; + +#ifdef NEED_PGRP_SYNC + if (j_sync_open) { + j_sync_open = 0; + closepipe(j_sync_pipe); + } +#endif /* NEED_PGRP_SYNC */ +#ifdef JOB_SIGS + if (held_sigchld) { + held_sigchld = 0; + /* Don't call j_sigchld() as it may remove job... */ + kill(procpid, SIGCHLD); + } +#endif /* JOB_SIGS */ +} + +/* + * 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(j, flags, where) + 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)) + { +#ifdef JOB_SIGS + sigsuspend(&sm_default); +#else /* JOB_SIGS */ + j_sigchld(SIGCHLD); +#endif /* JOB_SIGS */ + 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) { + WAIT_T status; + + j->flags &= ~JF_FG; +#ifdef TTY_PGRP + 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; + get_tty(tty_fd, &j->ttystate); + } + } +#endif /* TTY_PGRP */ + 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)) + { + get_tty(tty_fd, &tty_state); + } else { + set_tty(tty_fd, &tty_state, + (j->state == PEXITED) ? 0 : TF_MIPSKLUDGE); + /* 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. + */ +static RETSIGTYPE +j_sigchld(sig) + int sig; +{ + int errno_ = errno; + Job *j; + Proc UNINITIALIZED(*p); + int pid; + WAIT_T status; + struct tms t0, t1; + +#ifdef JOB_SIGS + /* 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; + return RETSIGVAL; + } +#endif /* JOB_SIGS */ + + ksh_times(&t0); + do { +#ifdef JOB_SIGS + pid = ksh_waitpid(-1, &status, (WNOHANG|WUNTRACED)); +#else /* JOB_SIGS */ + pid = wait(&status); +#endif /* JOB_SIGS */ + + if (pid <= 0) /* return if would block (0) ... */ + break; /* ... or no children or interrupted (-1) */ + + ksh_times(&t1); + + /* 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); + */ + t0 = t1; + continue; + } + + j->usrtime += t1.tms_cutime - t0.tms_cutime; + j->systime += t1.tms_cstime - t0.tms_cstime; + t0 = t1; + 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 */ + } +#ifdef JOB_SIGS + while (1); +#else /* JOB_SIGS */ + while (0); +#endif /* JOB_SIGS */ + + errno = errno_; + + return RETSIGVAL; +} + +/* + * 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(j) + 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; + } + +#ifdef KSH + /* 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 leasst, 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); + } +#endif /* KSH */ + + 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(j, how, shf) + Job *j; + int how; + struct shf *shf; +{ + Proc *p; + int state; + WAIT_T 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 (WIFCORED(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 + && WSTATUS(p->status) == WSTATUS(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(cp, ecodep) + 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() +{ + 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 strut + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static Proc * +new_proc() +{ + 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(j, where) + 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(j, where) + 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(j, sig) + 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.1tbl b/ksh.1tbl new file mode 100644 index 0000000..4ad1206 --- /dev/null +++ b/ksh.1tbl @@ -0,0 +1,5164 @@ +.\" $OpenBSD: ksh.1tbl,v 1.50 2003/03/20 07:30:37 jmc Exp $ +.\" +.\" Copyright (c) 1980, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the University of +.\" California, Berkeley and its contributors. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)ksh.1tbl 8.2 (Berkeley) 8/19/96 +.\" +.Dd August 19, 1996 +.Dt KSH 1 +.Os +.Sh NAME +.Nm ksh +.Nd public domain Korn shell +.Sh SYNOPSIS +.Nm ksh +.Op Fl +abCefhiklmnprsuvxX +.Op Fl +o Ar option +.Oo [ Fl c Ar command-string [ +.Xo Ar command-name ] No \&| Fl s No \&| +.Ar file No ]\ +.Xc +.Op Ar argument ... Oc +.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. +.Ss Shell startup +The following options can be specified only on the command line: +.Bl -tag -width Ds +.It Fl c Ar command-string +.Nm +will execute the command(s) contained in +.Ar command-string . +.It Fl i +Interactive mode; see below. +.It Fl l +Login shell; see below. +.It Fl s +The shell reads commands from standard input; all non-option arguments +are positional parameters. +.It Fl r +Restricted mode; see below. +.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. +.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 name the shell was called with (i.e., argv[0]) is used. +.Pp +A shell is +.Dq interactive +if the +.Fl i +option is used or if both standard input and standard error are attached +to a tty. +An interactive shell has job control enabled (if available), ignores the +.Dv SIGINT , +.Dv SIGQUIT , +and +.Dv SIGTERM +signals, and prints prompts before reading input (see +.Ev PS1 +and +.Ev PS2 +parameters). +For non-interactive shells, the +.Ic trackall +option is on by default (see +.Ic set +command below). +.Pp +A shell is +.Dq restricted +if the +.Fl r +option is used or if either the basename of the name the shell was invoked +with or the +.Ev SHELL +parameter match the pattern +.Dq \&*r\&*sh +(e.g., +.Dq rsh , +.Dq rksh , +.Dq rpdksh , +etc.). +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 > , +.Ql >| , +.Ql >> , +.Ql <> ) . +.El +.Pp +A shell is +.Dq privileged +if the +.Fl p +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). +.Pp +If the basename of the name the shell is called with (i.e., argv[0]) +starts with +.Ql - +or if the +.Fl l +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. +.Pp +If the +.Ev ENV +parameter is set when the 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. +If the +.Ev ENV +parameter is not set (and not +.Dv NULL ) +and +.Nm pdksh +was compiled with the +.Dv DEFAULT_ENV +macro defined, the file named in that macro is included (after the above +mentioned substitutions have been performed). +.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 shells 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 < , +.Ql > , +.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 tokens: +.Ql < , +.Ql <& , +.Ql << , +.Ql > , +.Ql >& , +.Ql >> , +etc. are used to specify redirections (see +.Sx Input/output redirection +below); +.Ql | +is used to create pipelines; +.Ql |& +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 -- 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 +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 is 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 ! +reversed 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 -- +.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. +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): +.Pp +.TS +center; +lfB lfB lfB lfB lfB . +case else function then ! +do esac if time [[ +done fi in until { +elif for select while } +.TE +.\" +.\".Ic case , do , done , elif , +.\".Ic else , esac , fi , for , +.\".Ic function , if , in , select , +.\".Ic then , time , until , while , +.\".Ic \&! , \&[\&[ , \&{ , \&} +.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, +.Pp +.Bl -inset -indent -compact +.It Ic { echo foo; echo bar; } +.It Ic { echo foo; echo bar } +.It Ic { { echo foo; echo bar; } } +.El +.Pp +are all valid, but +.Pp +.Bl -inset -indent -compact +.It Ic { echo foo; echo bar } +.El +.Pp +is not. +.Bl -tag -width Ds +.It Ic \&( Ar list Ic \&) +Execute +.Ar list +in a subshell. +There is no implicit way to pass environment changes from a +subshell back to its parent. +.It Ic \&{ Ar list Ic \&} +Compound construct; +.Ar list +is executed, but not in a subshell. +Note that +.Ic \&{ +and +.Ic \&} +are reserved words, not meta-characters. +.It Xo Ic case Ar word Ic in [ +.Ns [ Ic \&( ] Ar pattern [ +.Ns Ic \&| Ar pattern ] ... Ic \&) +.Ar list Ic \&;\&; +.Ns ] Ar ... +.Ic esac +.Xc +The +.Ic case +statement attempts to match +.Ar word +against the specified +.Ar pattern Ns s ; +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 Ic for Ar name No [ +.Ic in Ar word Ar ... term Ns No ] +.Ic do Ar list Ic 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. +.Ar term +is either a newline or a +.Ql \&; . +.It Xo Ic if Ar list Ic then +.Ar list [ Ic elif Ar list Ic then +.Ar list ] Ar ... [ Ic else +.Ar list ] Ic 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 Ic select Ar name No [ +.Ic in Ar word Ar ... term Ns No ] +.Ic do Ar list Ic 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 +.Dq #?\ \& +.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 . +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 +.Ic in Ar word Ar ... +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 Ic until Ar list Ic do Ar list +.Ic 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 Ic while Ar list Ic do Ar list +.Ic 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 Ic function Ar name Ic \&{ +.Ar list Ic \&} +.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 Ic () 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 Ic (( Ar expression Ic )) +The arithmetic expression +.Ar expression +is evaluated; equivalent to +.Ic let Ar expression +(see +.Sx Arithmetic expressions +and the +.Ic let +command below). +.It Ic [[ Ar expression Ic ]] +Similar to the +.Ic test +and +.Ic \&[ Ar ... Ic \&] +commands (described later), with the following exceptions: +.Pp +.Bl -bullet -offset indent +.It +Field splitting and file name generation are not performed on arguments. +.It +The +.Fl a +.Pq Tn AND +and +.Fl o +.Pq Tn OR +operators are replaced with +.Ql && +and +.Ql || , +respectively. +.It +Operators (e.g., +.Ql Fl f , +.Ql = , +.Ql ! , +etc.) must be unquoted. +.It +The second operand of the +.Ql != +and +.Ql = +expressions are patterns (e.g., the comparison +.Ic [[ foobar = f*r ]] +succeeds). +.It +There are two additional binary operators: +.Ql < +and +.Ql > +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 +.Ic \&[ Ar str Ic \&] +use +.Ic \&[[ Fl n Ar str Ic \&]] ) . +.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 statement +.Pp +.Ic \&[[ -r foo && $(< foo) = b*r ]] +.Pp +the +.Ic $(< foo) +is evaluated if and only if the file +.Pa foo +exists and is readable. +.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 sequences of the form +.Ic \&"...`...\e\&"...`..\&" . +.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: +.Pp +.Bl -item -offset indent -compact +.It +.Ic autoload='typeset -fu' +.It +.Ic functions='typeset -f' +.It +.Ic hash='alias -t' +.It +.Ic history='fc -l' +.It +.Ic integer='typeset -i' +.It +.Ic local='typeset' +.It +.Ic login='exec login' +.It +.Ic nohup='nohup ' +.It +.Ic r='fc -e -' +.It +.Ic stop='kill -STOP' +.It +.Ic suspend='kill -STOP $$' +.It +.Ic type='whence -v' +.El +.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 Fl o Ic trackall +or +.Ic set Fl 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: +.Ic cat , cc , chmod , cp , +.Ic date , ed , emacs , grep , +.Ic ls , mail , make , mv , +.Ic pr , rm , sed , sh , +.Ic vi , +and +.Ic who . +.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 +.Ic $ Ns Ar name +or +.Ic ${ Ns Ar ... Ns Ic \&} ; +command substitutions take the form +.Ic $( Ns Ar command Ns Ic \&) +or +.Ic ` Ns Ar command Ns Ic ` ; +and arithmetic substitutions take the form +.Ic $(( Ns Ar expression Ns Ic )) . +.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 no +.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 or trailing +.Pf non- Ev IFS +whitespace does create an empty field. +.Pp +Example: If +.Ev IFS +is set to +.Dq : , +the sequence of characters +.Dq A:B::D +contains four fields: +.Dq A , +.Dq B , +.Dq , +and +.Dq 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 +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 +.Ic $( Ns Ar command Ns Ic \&) +substitutions, normal quoting rules are used when +.Ar command +is parsed; however, for the +.Ic ` Ns Ar command Ns Ic ` +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 +.Ic \&< Ar file +is interpreted to mean substitute the contents of +.Ar file +(note that +.Ic $(< foo) +has the same effect as +.Ic $(cat foo) , +but it is carried out more efficiently because no process is started). +.Pp +.Sy Note: +.Ic $( Ns Ar command Ns Ic \&) +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 later form can be treated as arrays by appending an array index of the +form +.Op Ar expr +where +.Ar expr +is an arithmetic expression. +Array indices are currently limited to the range 0 through 1023, inclusive. +Parameter substitutions take the form +.Ic $ Ns Ar name , +.Ic ${ Ns Ar name Ns Ic \&} , +or +.Sm off +.Ic ${ Ar name Oo Ar expr Oc Ic \&} , +.Sm on +where +.Ar name +is a parameter name. +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 +.Ic # , PWD , +etc.; 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 +.Ev 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 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 Fl A +commands. +Lastly, parameters can be assigned values using assignment operators +inside arithmetic expressions (see +.Sx Arithmetic expressions +below) or using the +.Xo Ic ${ Ns Ar name Ns No = +.Ns Ar value Ns Ic \&} +.Xc +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 No = 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 +.Ic ${ Ns Ar name Ns Ic \&} +form of parameter substitution: +.Bl -tag -width Ds +.It Xo Ic ${ Ns Ar name Ns +.Ic \&:\&- Ns Ar word Ns Ic \&} +.Xc +If +.Ar name +is set and not +.Dv NULL , +it is substituted; otherwise, +.Ar word +is substituted. +.It Xo Ic ${ Ns Ar name Ns +.Ic \&:\&+ Ns Ar word Ns Ic \&} +.Xc +If +.Ar name +is set and not +.Dv NULL , +.Ar word +is substituted; otherwise, nothing is substituted. +.It Xo Ic ${ Ns Ar name Ns +.Ic \&:\&= Ns Ar word Ns Ic \&} +.Xc +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. +.It Xo Ic ${ Ns Ar name Ns +.Ic \&:\&? Ns Ar word Ns Ic \&} +.Xc +If +.Ar name +is set and not +.Dv NULL , +it is substituted; otherwise, +.Ar word +is printed on standard error (preceded by +.Ar name Ns No \&: ) +and an error occurs (normally causing termination of a shell script, function +or .-script). +If 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: +.Bl -tag -width Ds +.It Ic ${# Ns Ar name Ns Ic \&} +The number of positional parameters if +.Ar name +is +.Ql * , +.Ql @ , +not specified, or the length of the string value of parameter +.Ar name . +.It Xo Ic ${# Ns Ar name Ns +.Ic [*\&]} , ${# Ns Ar name Ns Ic [@\&]} +.Xc +The number of elements in the array +.Ar name . +.Sm off +.It Xo +.Ic ${ Ar name Ic # Ar pattern +.Sm on +.Ic } , +.Sm off +.Ic ${ Ar name Ic ## Ar pattern Ic \&} +.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. +.Sm off +.It Xo +.Ic ${ Ar name Ic % Ar pattern +.Sm on +.Ic } , +.Sm off +.Ic ${ Ar name Ic %% Ar pattern Ic } +.Xc +.Sm on +Like +.Ic ${..#..} +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 (i.e., $1, $2, etc.). +.It Ev \&$ +The process ID of the shell, or the +.Tn 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 +.Ic set +command below for 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 the shell was invoked with (i.e., +.Ic argv[0] ) , +or the +.Ar command-name +if it was invoked with the +.Fl c +option and the +.Ar command-name +was supplied, or the +.Ar file +argument, if it was supplied. +If the +.Ic posix +option is not set, +.Ic \&$0 +is the name of the current function or script. +.It Ev 1 ... Ev 9 +The first nine positional parameters that were supplied to the shell, function +or .-script. +Further positional parameters may be accessed using +.Ic ${ Ns Ar number Ns Ic \&} . +.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 +.Ev MAILPATH +parameter below). +.It Ev CDPATH +Search path for the +.Ic cd +built-in command. +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 +.Dq \&. +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 +.Ic select , +.Ic set Fl 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 +.Ev VISUAL +parameter below for how this works. +.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 +.Fn 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 future, +.Nm pdksh +may also use a default history file. +.It Ev HISTSIZE +The number of commands normally stored for history. +The default is 128. +.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). +See also the version commands in +.Sx Emacs editing mode +and +.Sx Vi editing mode +sections, below. +.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. +Not yet implemented. +.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 +.Ev $_ +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 last argument 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 .'d files. +An empty string resulting from a leading or trailing +colon, or two adjacent colons, is treated as a +.Dq \&. , +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 +.Ql ! +is replaced with the current command number (see +.Ic fc +command below). +A literal +.Ql ! +can be put in the prompt by placing +.Ql !! +in +.Ev PS1 . +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 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. +If you don't have any non-printing characters, you're out of luck. +By the way, don't blame me for +this hack; it's in the original +.Xr ksh . +Default is +.Dq \&$\ \& +for non-root users, +.Dq \&#\ \& +for root. +.It Ev PS2 +Secondary prompt string, by default +.Dq \&>\ \& , +used when more input is needed to complete a command. +.It Ev PS3 +Prompt used by +.Ic select +statement when reading a menu selection. +Default is +.Dq \&#\&?\ \& . +.It Ev PS4 +Used to prefix commands that are printed during execution tracing (see +.Ic set Fl x +command below). +Parameter, command, and arithmetic substitutions are performed +before it is printed. +Default is +.Dq \&+\ \& . +.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 simple random number generator. +Every time +.Ev RANDOM +is referenced, it is assigned the next number in a random number series. +The point in the series can be set by assigning a number to +.Ev RANDOM +(see +.Xr rand 3 ) . +.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 shell temporary 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 , +.Xr emacs +or +.Xr gmacs +(Gosling emacs) editing mode is enabled, respectively. +.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 (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 unquoted colon +.Pq Sq \&: , +and 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, which take the form +.Pp +.Sm off +.D1 Xo Ar prefix Ic { Ar str No 1,..., +.Ar str No N Ic } Ar suffix +.Xc +.Sm on +.Pp +are expanded to 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 (i.e., +.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 ? +or +.Ql * +characters or +.Dq [..] +sequences. +Once brace expansion has been performed, the shell replaces file +name patterns with the sorted named 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 Ic \&? +Matches any single character. +.It Ic \&* +Matches any sequence of characters. +.It Ic \&[ Ns No .. Ns Ic \&] +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 +.Dq 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. +.It Ic \&[\&! Ns No .. Ns Ic \&] +Like +.Ic \&[ Ns No .. Ns Ic \&] , +except it matches any character not inside the brackets. +.Sm off +.It Xo Ic \&*( Ar pattern Ic \&| No \ ...\ +.Ic \&| Ar pattern Ic \&) +.Xc +.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 Xo Ic \&+( Ar pattern Ic \&| No \ ...\ +.Ic \&| Ar pattern Ic \&) +.Xc +.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 Xo Ic \&?( Ar pattern Ic \&| No \ ...\ +.Ic \&| Ar pattern Ic \&) +.Xc +.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 Xo Ic \&@( Ar pattern Ic \&| No \ ...\ +.Ic \&| Ar pattern Ic \&) +.Xc +.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 Xo Ic \&!( Ar pattern Ic \&| No \ ...\ +.Ic \&| Ar pattern Ic \&) +.Xc +.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 +.Dq \&. +and +.Dq \&.\&. , +but the original +.Xr ksh , +Bourne +.Xr sh +and +.Xr 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 +.Ic \&[ Ns No .. Ns Ic \&] +sequence; also, the names +.Dq \&. +and +.Dq \&.\&. +are never matched, even by the pattern +.Dq \&.\&* . +.Pp +If the +.Ic markdirs +option is set, any directories that result from file name generation are marked +with a trailing +.Ql / . +.Pp +The +.Tn POSIX +character classes (i.e., +.Ic \&[\&: Ns Ar class-name Ns Ic \&:\&] +inside a +.Ic \&[ Ns No .. Ns Ic \&] +expression) are not yet implemented. +.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 Ic \&> 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 < foo > 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 Ic \&>\&| Ar file +Same as +.Ic \&> , +except the file is truncated, even if the +.Ic noclobber +option is set. +.It Ic \&>\&> Ar file +Same as +.Ic \&> , +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 +.Fn open 2 ) . +.It Ic \&< Ar file +Standard input is redirected from +.Ar file , +which is opened for reading. +.It Ic \&<\&> Ar file +Same as +.Ic \&< , +except the file is opened for reading and writing. +.It Ic \&<\&< 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 Ic \&<\&<\&- Ar marker +Same as +.Ic \&<\&< , +except leading tabs are stripped from lines in the here document. +.It Ic \&<\&& 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 Ic \&>\&& Ar fd +Same as +.Ic \&<\&& , +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 +.Pp +.Ic cat /foo/bar 2\&>&1 \&> /dev/null \&| cat -n +.Pp +will print an error with a line number prepended to it. +.Ss Arithmetic expressions +Integer arithmetic expressions can be used with the +.Ic let +command, inside +.Ic $(( Ns No .. Ns Ic )) +expressions, inside array references (e.g., +.Sm off +.Ar name Ic \&[ Ar expr Ic \&] ) , +.Sm on +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: +.Bl -item -offset indent -compact +.It +.Ic \&+ \&- \&! \&~ \&+\&+ \&-\&- +.El +.Pp +Binary operators: +.Bl -item -offset indent -compact +.It +.Ic \&, +.It +.Ic = \&*= /= %= \&+= \&-= \&<\&<= +.Ic \&>\&>= \&&= ^= \&|= +.It +.Ic \&|\&| +.It +.Ic \&&\&& +.It +.Ic \&| +.It +.Ic ^ +.It +.Ic \&& +.It +.Ic == \&!= +.It +.Ic \&< \&<= \&>= \&> +.It +.Ic \&<\&< \&>\&> +.It +.Ic \&+ \&- +.It +.Ic \&* / % +.El +.Pp +Ternary operators: +.Bl -item -offset indent -compact +.It +.Ic \&?\&: +(precedence is immediately higher than assignment) +.El +.Pp +Grouping operators: +.Bl -item -offset indent -compact +.It +.Ic \&( \&) +.El +.Pp +Integer constants may be specified with arbitrary bases using the notation +.Ar base Ns Ic \&# Ns Ar number , +where +.Ar base +is a decimal integer specifying the base, and +.Ar number +is a number in the specified base. +.Pp +The operators are evaluated as follows: +.Pp +.Bl -tag -width Ds -offset indent +.It unary Ic \&+ +Result is the argument (included for completeness). +.It unary Ic \&- +Negation. +.It Ic \&! +Logical +.Tn NOT ; +the result is 1 if argument is zero, 0 if not. +.It Ic \&~ +Arithmetic (bit-wise) +.Tn NOT . +.It Ic \&+\&+ +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 Ic \&-\&- +Similar to +.Ic \&+\&+ , +except the parameter is decremented by 1. +.It Ic \&, +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 Ic = +Assignment; variable on the left is set to the value on the right. +.It Xo Ic \&*= /= \&+= \&-= \&<\&<= +.Ic \&>\&>= \&&= ^= \&|= +.Xc +Assignment operators. +.Ao Ar var Ac +.Ao Ar op Ac = +.Ao Ar expr Ac +is the same as +.Ao Ar var Ac = +.Ao Ar var Ac +.Ao Ar op Ac +.Ic \&( +.Ao Ar expr Ac +.Ic \&) . +.It Ic \&|\&| +Logical +.Tn 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 Ic \&&\&& +Logical +.Tn 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 Ic \&| +Arithmetic (bit-wise) +.Tn OR . +.It Ic ^ +Arithmetic (bit-wise) +.Tn XOR +(exclusive-OR). +.It Ic \&& +Arithmetic (bit-wise) +.Tn AND . +.It Ic == +Equal; the result is 1 if both arguments are equal, 0 if not. +.It Ic \&!= +Not equal; the result is 0 if both arguments are equal, 1 if not. +.It Ic \&< +Less than; the result is 1 if the left argument is less than the right, 0 if +not. +.It Ic \&<= \&>= \&> +Less than or equal, greater than or equal, greater than. +See +.Ic \&< . +.It Ic \&<\&< \&>\&> +Shift left (right); the result is the left argument with its bits shifted left +(right) by the amount given in the right argument. +.It Ic \&+ \&- \&* / +Addition, subtraction, multiplication, and division. +.It Ic % +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 Ao Ar arg1 Ac Ic \ \&? +.Ao Ar arg2 Ac Ic \ \&: Ao Ar arg3 Ac +.Xc +If +.Ao Ar arg1 Ac +is non-zero, the result is +.Ao Ar arg2 Ac , +otherwise +.Ao Ar arg3 Ac . +.El +.Ss Co-processes +A co-process, which is a pipeline created with the +.Ic \&|\&& +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 \&>\&&p +and +.Ic \&<\&&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 \&>\&&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 \&<\&&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\&>\&&p\&; exec 3\&>\&&\&- ) . +.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 name +syntax or the Bourne/POSIX shell +.Fn name +syntax (see below for the difference between the two forms). +Functions are like +.Li .-scripts +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., $$, $\&!) 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 +.Dv OPTIND +untouched, so using +.Ic getopts +inside a function interferes with using +.Ic getopts +outside the function). +In the future, the following differences will also be added: +.Bl -bullet -offset indent +.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 +.El +.Ss POSIX mode +The shell is intended to be +.Tn POSIX +compliant; however, in some cases, +.Tn 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 Fl o Ic posix . +If it is on, the +.Tn 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 +.Dv POSIXLY_CORRECT +parameter. (The shell can also be compiled so that it is in +.Tn 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 +.Tn POSIX +mode, the +.Ic \e\&" +is interpreted when the command is interpreted; in +.Pf non- Tn POSIX +mode, the +backslash is stripped before the command substitution is interpreted. +For example, +.Ic echo \&"`echo \e\&"hi\e\&"`\&" +produces +.Dq \&"hi\&" +in +.Tn POSIX +mode, +.Dq hi +in +.Pf non- Tn POSIX +mode. +To avoid problems, use the +.Ic $(...) +form of command substitution. +.It +.Ic kill -l +output. +In +.Tn POSIX +mode, signal names are listed one per line; in +.Pf non- Tn POSIX +mode, +signal numbers, names, and descriptions are printed in columns. +In future, a new option +.Po Fl v +\ perhaps +.Pc +will be added to distinguish the two behaviours. +.It +.Ic fg +exit status. +In +.Tn POSIX +mode, the exit status is 0 if no errors occur; in +.Pf non- Tn 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 +.Tn POSIX +mode will be 0. +In +.Pf non- Tn 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 +.Tn POSIX +mode, options must start with a +.Ql - ; +in +.Pf non- Tn POSIX +mode, options can start with either +.Ql - +or +.Ql + . +.It +Brace expansion (also known as alternation). +In +.Tn POSIX +mode, brace expansion is +disabled; in +.Pf non- Tn POSIX +mode, brace expansion is enabled. +Note that +.Ic set Fl o Ic 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 +.Tn POSIX +mode, this does not clear the +.Ic verbose +or +.Ic xtrace +options; in +.Pf non- Tn POSIX +mode, it does. +.It +.Ic set +exit status. +In +.Tn POSIX +mode, the exit status of +.Ic set +is 0 if there are no errors; in +.Pf non- Tn 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 +.Tn POSIX +mode, 1 in +.Pf non- Tn POSIX +mode. +This construct is used in most shell scripts that use the old +.Xr getopt 1 +command. +.It +Argument expansion of +.Ic alias , +.Ic export , +.Ic readonly , +and +.Ic typeset +commands. +In +.Tn POSIX +mode, normal argument expansion is done; in +.Pf non- Tn 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 +.Tn POSIX +mode, signals can be specified as digits, only +if signal numbers match +.Tn POSIX +values (i.e., HUP=1, INT=2, QUIT=3, ABRT=6, +KILL=9, ALRM=14, and TERM=15); in +.Pf non- Tn POSIX +mode, signals can always be digits. +.It +Alias expansion. +In +.Tn POSIX +mode, alias expansion is only carried out when +reading command words; in +.Pf non- Tn 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 +.Pp +.Bl -item -offset indent -compact +.It +.Ic alias a='for ' i='j' +.It +.Ic a i in 1 2; do echo i=$i j=$j; done +.El +.Pp +uses parameter +.Ic i +in +.Tn POSIX +mode, +.Ic j +in +.Pf non- Tn POSIX +mode. +.It +Test. +In +.Tn POSIX +mode, the expression +.Ql Fl t +(preceded by some number of +.Ql Ic \&! +arguments) is always true as it is a non-zero length string; in +.Pf non- Tn POSIX +mode, it tests if file descriptor 1 is a tty (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, and 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 +.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 +.Tn POSIX +differ somewhat in which commands are considered +special or regular: +.Pp +.Tn POSIX +special commands +.Pp +.Ic \&. , \&: , break , continue , +.Ic eval , exec , exit , export , +.Ic readonly , return , set , shift , +.Ic trap , unset +.Pp +Additional ksh special commands +.Pp +.Ic builtin , times , typeset +.Pp +Very special commands +.Pq Pf non- Tn POSIX +.Pp +.Ic alias , readonly , set , typset +.Pp +.Tn POSIX +regular commands +.Pp +.Ic alias , bg , cd , command , +.Ic false , fc , fg , getopts , +.Ic jobs , kill , read , true , +.Ic umask , unalias , wait +.Pp +Additional ksh regular commands +.Pp +.Ic \&[ , echo , let , print , +.Ic pwd , test , ulimit , whence +.Pp +In the future, the additional ksh special and regular commands may be treated +differently from the +.Tn POSIX +special and regular commands. +.Pp +Once the type of the 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: +.Bl -tag -width Ds +.It Ic \&. Ar file Op Ar arg1 ... +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. +.It Ic \&: Op Ar ... +The null command. +Exit status is set to zero. +.It Xo Ic alias +.Op Fl d | Ic +-t Op Fl r +.Op Ic +-px +.Op Ic +- +.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 No = 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. +In addition, if the +.Fl p +option is used, each alias is prefixed with the string +.Dq alias\ \& . +.Pp +The +.Fl x +option sets +.Po Ic \&+x +\ clears +.Pc +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 +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 d +option causes directory aliases, which are used in tilde expansion, to be +listed or set (see +.Sx Tilde expansion +above). +.It Ic bg Op Ar job ... +Resume the specified stopped job(s) in the background. +If no jobs are specified, +.Ic %\&+ +is assumed. +This command is only available on systems which support job control (see +.Sx Job control +below for more information). +.It Xo Ic bind Op Fl m +.Oo Ar key +.Op Ns = Ns Ar editing-command +.Ar ... +.Oc +.Xc +Set or view the current emacs command editing key bindings/macros (see +.Sx Emacs editing mode +below for a complete description). +.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. +.It Ic builtin Ar command Op Ar arg1 ... +Execute the built-in command +.Ar command . +.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 +.Dq - , +the previous working directory is used (see +.Ev OLDPWD +parameter). +If the +.Fl L +option (logical path) is used or if the +.Ic physical +option (see +.Ic set +command below) isn't set, references to +.Dq \&.\&. +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, +.Dq \&.\&. +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. +.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. +.It Xo Ic command Op Fl pvV +.Ar cmd Op Ar arg1 ... +.Xc +If neither the +.Fl v +nor +.Fl V +options are given, +.Ar cmd +is executed exactly as if +.Ic command +had not been specified, with two exceptions. +First, +.Ar cmd +cannot be a shell function, and second, 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). +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 +.Tn POSIX Ns 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 arg1 ... ) . +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. +.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. +.It Xo Ic echo Op Fl neE +.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 +which suppresses backslash interpretation. +.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. +.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. +.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. +.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. +.It Ic false +A command that exits with a non-zero status. +.It Xo Ic fc +.Oo Fl e Ar editor No \&| +.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 stdout, 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. +.It Xo Ic fc +.Oo Fl e No \&- \&| Fl s Oc +.Op Fl g +.Op Ar old Ns No = 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 . +This command is usually accessed with the predefined +.Ic alias r='fc -e -' . +.It Ic fg Op Ar job ... +Resume the specified job(s) in the foreground. +If no jobs are specified, +.Ic %\&+ +is assumed. +This command is only available on systems which support job control (see +.Sx Job control +below for more information). +.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 next argument to be processed 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 . +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. +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. +.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. +.It Xo Ic jobs Op Fl lpn +.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. +.It Xo Ic kill +.Oo Fl s Ar signame No \&| +.Fl signum No \&| Fl signame Oc { +.Ar job No \&| +.Ar pid No \&| +.Ar 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 . +.It Ic kill -l Op Ar exit-status ... +Print the name of the signal that killed a process which exited with the +specified +.Ar exit-status Ns es. +If no arguments are specified, a list of all the signals, their numbers and +a short description of them are printed. +.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, +.Ic (( Ar expr Ic )) +is syntactic sugar for +.Ic let \&" Ns Ar expr Ns Ic \&" . +.It Xo Ic print +.Oo Fl nprsu Ns Ar n No \&| +.Fl R No 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 +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. +.It Ic pwd Op Fl LP +Print the present working directory. +If the +.Fl L +option is used or if the +.Ic physical +option (see +.Ic set +command below) isn't set, 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 +.Dq \&.\&. +directories to the root directory) is printed. +.It Xo Ic read Oo Fl prsu Ns Ar n +.Oc 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 tty (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 +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. +.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. +.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. +.It Xo Ic set Op Ic +-abCefhkmnpsuvxX +.Op Ic +-o Ar option +.Op Ic +-A Ar name +.Op Fl \&- +.Op Ar arg ... +.Xc +The 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 +.Ic \&+ Ns Fl o Ar option +syntax, where +.Ar option +is the long name of an option, or using the +.Ic \&+\&- 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 +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 +.Ar arg Ns s ) , +the rest are left untouched. +.It Fl a Ic allexport +All new parameters are created with the export attribute. +.It Fl b 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 Ic noclobber +Prevent +.Ic \&> +redirection from overwriting existing files +.Po +.Ic \&>\&| +must be used to force an overwrite +.Pc . +.It Fl e 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 , +.Ic \&&\&& , +or +.Ic \&|\&| +statements. +.It Fl f Ic noglob +Do not expand file name patterns. +.It Fl h Ic trackall +Create tracked aliases for all executed commands (see +.Sx Aliases +above). +Enabled by default for non-interactive shells. +.It Fl i Ic interactive +Enable interactive mode. +This can only be set/unset when the shell is invoked. +.It Fl k Ic keyword +Parameter assignments are recognized anywhere in a command. +.It Fl l Ic login +The shell is a login shell. +This can only be set/unset when the shell is invoked (see +.Sx Shell startup +above). +.It Fl m Ic monitor +Enable job control (default for interactive shells). +.It Fl n lc noexec +Do not execute any commands. +Useful for checking the syntax of scripts +(ignored if interactive). +.It Fl p Ic privileged +Set automatically if, when the shell starts, the read UID or GID does not match +the effective UID (EUID) or GID (EGID), respectively. +See +.Sx Shell startup +above for a description of what this means. +.It Fl r Ic restricted +Enable restricted mode. +This option can only be used when the shell is invoked. +See +.Sx Shell startup +above for a description of what this means. +.It Fl s Ic stdin +If used where 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 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 Ic verbose +Write shell input to standard error as it is read. +.It Fl x Ic xtrace +Print commands and parameter assignments when they are executed, preceded by +the value of +.Ev PS4 . +.It Fl X Ic markdirs +Mark directories with a trailing +.Ql / +during file name generation. +.It Ic bgnice +Background jobs are run with lower priority. +.It Ic braceexpand +Enable brace expansion (a.k.a., alternation). +.It Ic emacs +Enable BRL emacs-like command-line editing (interactive shells only); see +.Sx Emacs editing mode. +.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 nohup +Do not kill running jobs with a +.Dv SIGHUP +signal when a login shell exists. +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) +.Dq \&.\&. +directories instead of +.Dq logical +directories (i.e., the shell handles +.Dq \&.\&. , +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 +.Tn POSIX +mode. +See +.Sx POSIX mode +above. +.It Ic vi +Enable vi-like command-line editing (interactive shells only). +.It Ic viraw +No effect. +In the original Korn shell, unless +.Ic viraw +was set, the vi command-line mode would let the tty driver do the work until +.Tn ESC +(^[) was entered. +.Nm pdksh +is always in viraw mode. +.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 +.Dq 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. +.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 +.Dv \&- . +.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. +.It Ic shift Op Ar number +The positional parameters +.Ar number Ns +1 , +.Ar number Ns +2 , +etc. are renamed to +.Dq 1 , +.Dq 2 , +etc. +.Ar number +defaults to 1. +.It Ic test Ar expression +.It Ic \&[ Ar expression Ic \&] +.Ic test +evaluates the +.Ar expression +and returns zero status if true, 1 status 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. +The following basic expressions are available: +.Bl -tag -width 17n +.It Ar str +.Ar str +has non-zero length. +Note that there is the potential for problems if +.Ar str +turns out to be an operator (e.g., +.Fl r ) . +It is generally better to use a test like +.Sm off +.Ic \&[\ X\&" Ar str Ic \&" Ic \ \&] +.Sm on +instead (double quotes are used in case +.Ar str +contains spaces or file globbing characters). +.It Fl r Ar file +.Ar file +exists and is readable. +.It Fl w Ar file +.Ar file +exists and is writable. +.It Fl x Ar file +.Ar file +exists and is executable. +.It Fl a Ar file +.Ar file +exists. +.It Fl e Ar file +.Ar file +exists. +.It Fl f Ar file +.Ar file +is a regular file. +.It Fl d Ar file +.Ar file +is a directory. +.It Fl c Ar file +.Ar file +is a character special device. +.It Fl b Ar file +.Ar file +is a block special device. +.It Fl p Ar file +.Ar file +is a named pipe. +.It Fl u Ar file +.Ar file Ns 's +mode has setuid bit set. +.It Fl g Ar file +.Ar file Ns 's +mode has setgid bit set. +.It Fl k Ar file +.Ar file Ns 's +mode has sticky bit set. +.It Fl s Ar file +.Ar file +is not empty. +.It Fl O Ar file +.Ar file Ns 's +owner is the shell's effective user ID. +.It Fl G Ar file +.Ar file Ns 's +group is the shell's effective group ID. +.It Fl h Ar file +.Ar file +is a symbolic link. +.It Fl H Ar file +.Ar file +is a context dependent directory (only useful on HP-UX). +.It Fl L Ar file +.Ar file +is a symbolic link. +.It Fl S Ar file +.Ar file +is a socket. +.It Fl o Ar option +Shell +.Ar option +is set (see +.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 (thus +.Ic \&[ -o Ar foo +.Ic -o -o \&! Ns Ar foo Ic \&] +returns true if and only if option +.Ar foo +exists). +.It Ar file Fl nt Ar file +first +.Ar file +is newer than second +.Ar file +or first +.Ar file +exists and the second +.Ar file +does not. +.It Ar file Fl ot Ar file +first +.Ar file +is older than second +.Ar file +or second +.Ar file +exists and the first +.Ar file +does not. +.It Ar file Fl ef Ar file +first +.Ar file +is the same file as second +.Ar file . +.It Fl t Op Ar fd +File descriptor +.Ar fd +is a tty 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 +.Tn POSIX +rules described below). +.It Ar string +.Ar string +is not empty. +.It Fl z Ar string +.Ar string +is empty. +.It Fl n Ar string +.Ar string +is not 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): +.Pp +.Bl -tag -width "expr -o expr" -compact +.It Ar expr Fl o Ar expr +Logical +.Tn OR . +.It Ar expr Fl a Ar expr +Logical +.Tn AND . +.It Ic \&! Ar expr +Logical +.Tn NOT . +.It Ic \&( Ar expr Ic \&) +Grouping. +.El +.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, +.Ic \&[ -w /dev/fd/2 \&] +tests if file descriptor 2 is writable. +.Pp +Note that some special rules are applied (courtesy of +.Tn 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 +.Ic if \&[ $foo = bar \&] +which fails if parameter +.Ic foo +is +.Dv NULL +or unset, if it has embedded spaces (i.e., +.Ev IFS +characters), or if it is a unary operator like +.Ql Ic \&! +or +.Ql Fl n . +Use tests like +.Ic if \&[ \&"X$foo\&" = Xbar \&] +instead. +.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 0.00s real 0.00s user 0.00s system +.Pp +unless the +.Fl p +option is given (only possible if +.Ar pipeline +is a simple command), in which case the output is slightly longer: +.Pp +.Dl real 0.00 +.Dl user 0.00 +.Dl sys 0.00 +.Pp +(the number of digits after the decimal may vary from system to system). +Note +that simple redirections of standard error do not effect the output of the time +command: +.Pp +.Dl time sleep 1 2> afile +.Dl { time sleep 1; } 2> afile +.Pp +Times for the first command do not go to +.Dq afile , +but those of the second command do. +.It Ic times +Print the accumulated user and system times used by the shell and by processes +which have exited that the shell started. +.It Ic trap Op Ar handler signal ... +Sets 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 +.Ic kill -l +command above). +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 see -- see +.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 +can not 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. +.It Ic true +A command that exits with a zero value. +.It Xo Ic typeset +.Oo Op Ic +-Ulprtux +.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 \&| 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 Ns s +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 3n +.It Fl L Ns 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 R Ns 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 Z Ns 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. +.It Fl i Ns 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 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 f +Function mode. +Display or set functions and their attributes, instead of parameters. +.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) or +parameters. +This is the default action (option exists for ksh93 compatibility). +.It Fl r +Read-only attribute. +Parameters with this attribute may not be assigned to or unset. +Once this attribute is set, it can not 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 +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. +.El +.It Xo Ic ulimit Op Fl acdfHlmnpsSt +.Op Ar value +.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 or the word +.Dq unlimited . +The limits affect the shell and any processes created by the shell after a +limit is imposed. +Note that some systems may not allow limits to be increased +once they are set. +Also note that the types of limits available are system +dependent -- some systems have only the +.Fl f +limit. +.Bl -tag -width 5n +.It Fl a +Displays all limits; unless +.Fl H +is used, soft limits are displayed. +.It Fl H +Set the hard limit only (default is to set both hard and soft limits). +.It Fl S +Set the soft limit only (default is to set both hard and soft limits). +.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 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 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 +.Tn CPU +seconds to be used by each process. +.El +.Pp +As far as +.Ic ulimit +is concerned, a block is 512 bytes. +.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 . +.It Xo Ic unalias Op Fl adt +.Op Ar name1 ... +.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. +.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 were already unset, zero +otherwise. +.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). +.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 Fl m +or +.Ic set Fl o Ic 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 +.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 $! 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 10n +.It Ic %\&+ +The most recently stopped job, or, if there are no stopped jobs, the oldest +running job. +.It Ic %% , % +Same as +.Ic %\&+ . +.It Ic %\&- +The job that would be the +.Ic %\&+ +job if the latter did not exist. +.It Ic % Ns Ar n +The job with job number +.Ar n . +.It Ic %\&? Ns Ar string +The job containing the string +.Ar string +(an error occurs if multiple jobs are matched). +.It Ic % Ns Ar string +The job starting with 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 +.Ic \&[ Ar number Ic \&] Ar flag status command +.Pp +where +.Bl -tag -width "status" +.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 "Running" +.It Cm Running +The job has neither stopped nor exited (note that running does not necessarily +mean consuming +.Tn CPU +time -- the process could be blocked waiting for some +event). +.It Cm 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 Cm 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, etc.; 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 tty in an +interactive session, which is controlled by the +.Ic emacs , +.Ic gmacs , +and +.Ic vi +options (at most one of these can be set an once). +If none of these options are enabled, the shell simply reads lines using the +normal tty 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 +.Ev COLUMNS +parameter), +a +.Ql > , +.Ql + , +or +.Ql < +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 and the 8th +bit is stripped in emacs mode. +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 binding can be changed +using the following commands: +.Bl -tag -width Ds +.It Ic bind +The current bindings are listed. +.It Xo Ic bind +.Ar string Ns = Ns Op Ar editing-command +.Xc +The specified editing command is bound to the given +.Ar string , +which should consist of a control character (which may be written using caret +notation, i.e., ^X), optionally preceded by one of the two prefix characters. +Future input of the +.Ar string +will cause the editing command to be immediately invoked. +Note that although only two prefix characters (usually +.Tn ESC +and ^X) are supported, some +multi-character sequences can be supported. +The following binds the arrow keys on an +.Tn ANSI +terminal, or xterm (these are in the default bindings). +Of course some escape sequences won't work out quite this nicely. +.Pp +.Bl -item -compact +.It +.Ic bind '^[['=prefix-2 +.It +.Ic bind '^XA'=up-history +.It +.Ic bind '^XB'=down-history +.It +.Ic bind '^XC'=forward-char +.It +.Ic bind '^XD'=backward-char +.El +.It Ic bind Fl l +Lists the names of the functions to which keys may be bound. +.It Xo Ic bind Fl m +.Sm off +.Ar string No = Op Ar substitute +.Sm on +.Xc +The specified input +.Ar string +will afterwards be immediately replaced by the given +.Ar substitute +string, which may contain editing commands. +.El +.Pp +The following is a list of available editing commands. +Each description starts with the name of the command, an +.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, i.e., +.Tn "ASCII ESC" +character is +written as ^[). +A count prefix for a command is entered using the sequence +.Ic ^\&[ 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. +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 users' tty characters (e.g., +.Dv ERASE ) +are bound to +reasonable substitutes and override the default bindings. +.Bl -tag -width Ds +.It Ic abort ^G +Useful as a response to a request for a +.Ic search-history +pattern in order to abort the search. +.It Ic auto-insert Ar n +Simply causes the character to appear as literal input. +Most ordinary characters are bound to this. +.It Ic backward-char Ar n Ic ^B +Moves the cursor backward +.Ar n +characters. +.It Ic backward-word Ar n Ic ^[B +Moves the cursor backward to the beginning of the word; words consist of +alphanumerics, underscore +.Pq Sq _ +and dollar sign +.Pq Sq $ +characters. +.It Ic beginning-of-history ^[< +Moves to the beginning of the history. +.It Ic beginning-of-line ^A +Moves the cursor to the beginning of the edited input line. +.It Ic capitalize-word Ar n Ic ^[c , ^[C +Uppercase the first character in the next +.Ar n +words, leaving the cursor past the end of the last word. +.Pp +If the current line does not being 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 Ic complete ^[^[ +.It Ic complete ^I +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 Ic 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 Ic 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 Ic complete-list ^[= +List the possible completions for the current word. +.It Xo Ic delete-char-backward Ar n Ic ERASE , +.Ic ^? , ^H +.Xc +Deletes +.Ar n +characters before the cursor. +.It Ic delete-char-forward Ar n +Deletes +.Ar n +characters after the cursor. +.It Xo Ic delete-word-backward Ar n Ic ^[ERASE , +.Ic ^[^? , ^[^H , ^[h +.Xc +Deletes +.Ar n +words before the cursor. +.It Ic delete-word-forward Ar n Ic ^[d +Deletes characters after the cursor up to the end of +.Ar n +words. +.It Ic down-history Ar n Ic ^N +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 Ic downcase-word Ar n Ic ^[L , ^[l +Lowercases the next +.Ar n +words. +.It Ic end-of-history ^[> +Moves to the end of the history. +.It Ic end-of-line ^E +Moves the cursor to the end of the input line. +.It Ic eot ^_ +Acts as an end-of-file; this is useful because edit-mode input disables +normal terminal input canonicalization. +.It Ic eot-or-delete Ar n Ic ^D +Acts as +.Ic eot +if alone on a line; otherwise acts as +.Ic delete-char-forward . +.It Ic error +Error (ring the bell). +.It Ic exchange-point-and-mark ^X^X +Places the cursor where the mark is and sets the mark to where the cursor was. +.It Ic 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 Ic forward-char Ar n Ic ^F +Moves the cursor forward +.Ar n +characters. +.It Ic forward-word Ar n Ic ^[f +Moves the cursor forward to the end of the +.Ar n Ns th +word. +.It Ic goto-history Ar n Ic ^[g +Goes to history number +.Ar n . +.It Ic kill-line KILL +Deletes the entire input line. +.It Ic kill-region ^W +Deletes the input between the cursor and the mark. +.It Ic kill-to-eol Ar n Ic ^K +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 Ic list ^[? +Prints a sorted, columnated list of command named or file names (if any) that +can complete the partial word containing the cursor. +Directory names have +.Ql / +appended to them. +.It Ic list-command ^X? +Prints a sorted, columnated list of command names (if any) that can complete +the partial word containing the cursor. +.It Ic list-file ^X^Y +Prints a sorted, comunated 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 Ic 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 Ic 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 Ic no-op QUIT +This does nothing. +.It Ic prefix-1 ^[ +Introduces a 2-character command sequence. +.It Ic prefix-2 ^X +.It Ic prefix-2 ^[[ +Introduces a 2-character command sequence. +.It Ic prev-hist-word Ar n Ic ^[\&. , ^{_ +The last +.Pq Ar n Ns th +word of the previous command is inserted at the cursor. +.It Ic quote ^^ +The following character is taken literally rather than as an editing command. +.It Ic redrew ^L +Reprints the prompt string and the current input line. +.It Ic search-character-backward Ar n Ic ^[^] +Search backward in the current line for the +.Ar n Ns th +occurrence of the next character typed. +.It Ic search-character-forward Ar n Ic ^] +Search forward in the current line for the +.Ar n Ns th +occurrence of the next character typed. +.It Ic 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 Ic set-mark-command ^[ Ns No +Set the mark at the cursor position. +.It Ic 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 Ic stuff-reset +Acts like +.Ic stuff , +then aborts input the same as an interrupt. +.It Ic 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 Ic up-history Ar n Ic ^P +Scrolls the history buffer backward +.Ar n +lines (earlier). +.It Ic upcase-word Ar n Ic ^[U , ^[u +Uppercase the next +.Ar n +words. +.It Ic version ^V +Display the version of ksh. +The current edit buffer is restored as soon as any +key is pressed (the key is then processed, unless it is a space). +.It Ic yank ^Y +Inserts the most recently killed text string at the current cursor position. +.It Ic 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 ksh has basically the same commands as the vi +editor (see +.Xr vi 1 ) , +with the following exceptions: +.Bl -bullet +.It +You start out in insert mode. +.It +There are file name and command completion commands +.Po +.Ic = , \e , \&* , ^X , +.Ic ^E , ^F , +and, optionally, +.Ic +.Pc . +.It +The +.Ic _ +command is different (in ksh it is the last argument command, in vi it goes to +the start of the current line). +.It +The +.Ic / +and +.Ic G +commands move in the opposite direction as the +.Ic j +command. +.It +Commands which don't make sense in a single line editor are not available +(e.g., screen movement command, ex-style +.Ic \&: +commands, etc.). +.El +.Pp +Note that the ^X stands for control-X; also , and are used +for escape, space, and tab, respectively (no kidding). +.Pp +Like vi, 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 tty settings (see +.Xr tty 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 Ic ^H +Erases previous character. +.It Ic ^V +Liternal next. +The next character typed is not treated specially (can be used +to insert the characters being described here). +.It Ic ^J ^M +End of line. +The current line is read, parsed, and executed by the shell. +.It Ic +Puts the editor in command mode (see below). +.It Ic ^E +Command and file name enumeration (see below). +.It Ic ^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 Ic ^X +Command and file name expansion (see below). +.It Ic +Optional file name and command completion (see +.Ic ^F +above), enabled with +.Ic set Fl o Ic 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 +.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, non-whitespace characters (e.g., +.Dq ab2\&*\&&^ +contains two words) and a +.Dq big-word +is a sequence of non-whitespace characters. +.Pp +Special ksh vi commands +.Pp +The following commands are not in, or are different from, the normal vi file +editor: +.Bl -tag -width 10n +.It Ar n Ns _ +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 Ic \&# +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 Ar n Ns Ic g +Like +.Ic G , +except if +.Ar n +is not specified, it goes to the most recent remembered line. +.It Ic Ar n Ns Ic v +Edit line +.Ar n +using the vi editor; if +.Ar n +is not specified, the current line is edited. +The actual command executed is +.Ic fc Fl e Ic ${VISUAL;-${EDITOR:-vi}} Ar n . +.It Ic \&* No and Ic ^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 following characters: +.Ql \&; , +.Ql | , +.Ql & , +.Ql ( , +or +.Ql \&) ) +and does not contain a slash +.Pq Sq / +then the 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 places just past the last +word and the editor is in insert mode. +.It n\e,\ n^F,\ n,\ and\ n +Command/file name completion. +Replace the current big-word with the +longest unique match obtained after performing command and file name expansion. +.Ic +is only recognized if the +.Ic vi-tabcomplete +option is set, while +.Ic +is only recognized if the +.Ic vi-esccomplete +option is set (see +.Ic set Fl 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 Ic \&= No and Ic ^E +Command/file name enumeration. +List all the commands or files that match the current big-word. +.It Ic ^V +Display the version of +.Nm pdksh ; +it is displayed until another key is pressed (this key is ignored). +.It Ic @ 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 Ar n Ns Ic h No and +.Ar n Ns Ic ^H +.Xc +Move left +.Ar n +characters. +.It Xo Ar n Ns Ic l No and +.Ar n Ns Ic +.Xc +Move right +.Ar n +characters. +.It Ic \&0 +Move to column 0. +.It Ic ^ +Move to the first non-whitespace character. +.It Ar n Ns Ic \&| +Move to column +.Ar n . +.It Ic $ +Move to the last character. +.It Ar n Ns Ic b +Move back +.Ar n +words. +.It Ar n Ns Ic B +Move back +.Ar n +big-words. +.It Ar n Ns Ic e +Move forward to the end of the word, +.Ar n +times. +.It Ar n Ns Ic E +Move forward to the end of the big-word, +.Ar n +times. +.It Ar n Ns Ic w +Move forward +.Ar n +words. +.It Ar n Ns Ic W +Move forward +.Ar n +big-words. +.It Ic % +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 Ar n Ns Ic f Ns Ar c +Move forward to the +.Ar n Ns th +occurrence of the character +.Ar c . +.It Ar n Ns Ic F Ns Ar c +Move backward to the +.Ar n Ns th +occurrence of the character +.Ar c . +.It Ar n Ns Ic t Ns Ar c +Move forward to just before the +.Ar n Ns th +occurrence of the character +.Ar c . +.It Ar n Ns Ic T Ns Ar c +Move backward to just before the +.Ar n Ns th +occurrence of the character +.Ar c . +.It Ar n Ns Ic \&; +Repeats the last +.Ic f , F , t +or +.Ic T +command. +.It Ar n Ns Ic \&, +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 nj,\ n+\ and\ n^N +Move to the +.Ar n Ns th +next line in the history. +.It nk,\ n-\ and\ n^P +Move to the +.Ar n Ns th +previous line in the history. +.It Ar n Ns Ic G +Move to line +.Ar n +in the history; if +.Ar n +is not specified, the number of the first remembered line is used. +.It Ar n Ns Ic g +Like +.Ic G , +except if +.Ar n +is not specified, it goes to the most recent remembered line. +.It Ar n Ns Ic / Ns Ar string +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 Ar n Ns Ic \&? Ns Ar string +Same as +.Ic / , +except it searches forward through the history. +.It Ar n Ns Ic n +Search for the +.Ar n Ns th +occurrence of the last search string; the directory of the search is the same +as the last search. +.It Ar n Ns Ic N +Search for the +.Ar n Ns th +occurrence of the last search string; the directory of the search is the +opposite of the last search. +.El +.Pp +Edit commands +.Bl -tag -width Ds +.It Ar n Ns Ic a +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., is used). +.It Ar n Ns Ic A +Same as +.Ic a , +except it appends at the end of the line. +.It Ar n Ns Ic i +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., is used). +.It Ar n Ns Ic I +Same as +.Ic i , +except the insertion is done just before the first non-blank character. +.It Ar n Ns Ic s +Substitute the next +.Ar n +characters (i.e., delete the characters and go into insert mode). +.It Ic 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 Ar n Ns Ic c Ns Ar move-cmd +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 Ic 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 Ar n Ns Ic x +Delete the next +.Ar n +characters. +.It Ar n Ns Ic X +Delete the previous +.Ar n +characters. +.It Ic D +Delete to the end of the line. +.It Ar n Ns Ic d Ns Ar move-cmd +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 Ar n Ns Ic r Ns Ar c +Replace the next +.Ar n +characters with the character +.Ar c . +.It Ar n Ns Ic R +Replace. +Enter insert mode but overwrite existing characters instead of +inserting before existing characters. +The replacement is repeated +.Ar n +times. +.It Ar n Ns Ic \&~ +Change the case of the next +.Ar n +characters. +.It Ar n Ns Ic y Ns Ar move-cmd +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 Ic Y +Yank from the current position to the end of the line. +.It Ar n Ns Ic p +Paste the contents of the yank buffer just after the current position, +.Ar n +times. +.It Ar n Ns Ic P +Same as +.Ic p , +except the buffer is pasted at the current position. +.El +.Pp +Miscellaneous vi commands +.Pp +.Bl -tag -width Ds +.It Ic ^J No and Ic ^M +The current line is read, parsed, and executed by the shell. +.It Ic ^L No and Ic ^R +Redraw the current line. +.It Ar n Ns Ic \&. +Redo the last edit command +.Ar n +times. +.It Ic u +Undo the last edit command. +.It Ic 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. +.Sh FILES +.Bl -tag -width "/etc/suid_profile" -compact +.It Pa ~/.profile +.It Pa /etc/profile +.It Pa /etc/suid_profile +.El +.Sh SEE ALSO +.Xr awk 1 , +.Xr csh 1 , +.Xr ed 1 , +.Xr getconf 1 , +.Xr getopt 1 , +.Xr sed 1 , +.Xr sh 1 , +.Xr stty 1 , +.Xr vi 1 , +.Xr dup 2 , +.Xr execve 2 , +.Xr getgid 2 , +.Xr getuid 2 , +.Xr open 2 , +.Xr pipe 2 , +.Xr wait 2 , +.Xr getopt 3 , +.Xr rand 3 , +.Xr signal 3 , +.Xr system 3 , +.Xr environ 7 +.Pp +.Rs +.%A Morris Bolsky +.%A David Korn +.%T "The KornShell Command and Programming Language" +.%D 1983 +.%O "ISBN 0-13-516972-0" +.Re +.Rs +.%A Stephen G. Kochan +.%A Patrick H. Wood +.%T "UNIX Shell Programming" +.%O "Hayden" +.Re +.Rs +.%A "IEEE Inc." +.%T "IEEE Standard for Information Technology - Portable Operating System Interface (POSIX) - Part 2: Shell and Utilities" +.%D 1993 +.%O "ISBN 1-55937-266-9" +.Re +.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. +.Sh VERSION +This page documents version @(#)PD KSH v5.2.14 99/07/13.2 of the public +domain Korn shell. +.Sh AUTHORS +This shell is based on the public domain 7th edition Bourne shell clone by +Charles Forsyth and parts of the BRL shell by Doug A. Gwyn, Doug Kingston, +Ron Natalie, Arnold Robbins, Lou Salkind, and others. +The first release of +.Nm pdksh +was created by Eric Gisin, and it was subsequently maintained by John R. +MacMillan (change!john@sq.sq.com) and Simon J. Gerraty (sjg@zen.void.oz.au). +The current maintainer is Michael Rendell (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. diff --git a/ksh_dir.h b/ksh_dir.h new file mode 100644 index 0000000..ee02d7a --- /dev/null +++ b/ksh_dir.h @@ -0,0 +1,26 @@ +/* $OpenBSD: ksh_dir.h,v 1.1.1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* Wrapper around the ugly dir includes/ifdefs */ + +#if defined(HAVE_DIRENT_H) +# include +# define NLENGTH(dirent) (strlen(dirent->d_name)) +#else +# define dirent direct +# define NLENGTH(dirent) (dirent->d_namlen) +# ifdef HAVE_SYS_NDIR_H +# include +# endif /* HAVE_SYS_NDIR_H */ +# ifdef HAVE_SYS_DIR_H +# include +# endif /* HAVE_SYSDIR_H */ +# ifdef HAVE_NDIR_H +# include +# endif /* HAVE_NDIR_H */ +#endif /* HAVE_DIRENT_H */ + +#ifdef OPENDIR_DOES_NONDIR +extern DIR *ksh_opendir ARGS((const char *d)); +#else /* OPENDIR_DOES_NONDIR */ +# define ksh_opendir(d) opendir(d) +#endif /* OPENDIR_DOES_NONDIR */ diff --git a/ksh_limval.h b/ksh_limval.h new file mode 100644 index 0000000..5fbe6c9 --- /dev/null +++ b/ksh_limval.h @@ -0,0 +1,24 @@ +/* $OpenBSD: ksh_limval.h,v 1.1.1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* Wrapper around the values.h/limits.h includes/ifdefs */ + +#ifdef HAVE_VALUES_H +# include +#endif /* HAVE_VALUES_H */ +/* limits.h is included in sh.h */ + +#ifndef DMAXEXP +# define DMAXEXP 128 /* should be big enough */ +#endif + +#ifndef BITSPERBYTE +# ifdef CHAR_BIT +# define BITSPERBYTE CHAR_BIT +# else +# define BITSPERBYTE 8 /* probably true.. */ +# endif +#endif + +#ifndef BITS +# define BITS(t) (BITSPERBYTE * sizeof(t)) +#endif diff --git a/ksh_stat.h b/ksh_stat.h new file mode 100644 index 0000000..1abcc7c --- /dev/null +++ b/ksh_stat.h @@ -0,0 +1,59 @@ +/* $OpenBSD: ksh_stat.h,v 1.3 1996/10/01 02:05:39 downsj Exp $ */ + +/* Wrapper around the ugly sys/stat includes/ifdefs */ + +/* assumes already included */ +#include + +#ifndef HAVE_LSTAT +# define lstat(path, buf) stat(path, buf) +#endif /* HAVE_LSTAT */ + +#ifdef STAT_MACROS_BROKEN +# undef S_ISREG +# undef S_ISDIR +# undef S_ISCHR +# undef S_ISBLK +# undef S_ISFIFO +# undef S_ISSOCK +# undef S_ISLNK +#endif /* STAT_MACROS_BROKEN */ + +#if !defined(S_ISREG) && defined(S_IFREG) +# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif /* S_ISREG */ +#if !defined(S_ISDIR) && defined(S_IFDIR) +# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#endif /* S_ISDIR */ +#if !defined(S_ISCHR) && defined(S_IFCHR) +# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) +#endif /* S_ISCHR */ +#if !defined(S_ISBLK) && defined(S_IFBLK) +# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) +#endif /* S_ISBLK */ +#if !defined(S_ISFIFO) && defined(S_IFIFO) +# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) +#endif /* S_ISFIFO */ +#if !defined(S_ISLNK) && defined(S_IFLNK) +# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) +#endif /* S_ISLNK */ +#if !defined(S_ISSOCK) && defined(S_IFSOCK) +# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) +#endif /* S_ISSOCK */ +#if !defined(S_ISCDF) && defined(S_CDF) +# define S_ISCDF(m) (S_ISDIR(m) && ((m) & S_CDF)) +#endif /* S_ISSOCK */ + +#ifndef S_ISVTX +# define S_ISVTX 01000 /* sticky bit */ +#endif /* S_ISVTX */ + +#ifndef S_IXUSR +# define S_IXUSR 00100 /* user execute bit */ +#endif /* S_IXUSR */ +#ifndef S_IXGRP +# define S_IXGRP 00010 /* user execute bit */ +#endif /* S_IXGRP */ +#ifndef S_IXOTH +# define S_IXOTH 00001 /* user execute bit */ +#endif /* S_IXOTH */ diff --git a/ksh_time.h b/ksh_time.h new file mode 100644 index 0000000..ee8dc9b --- /dev/null +++ b/ksh_time.h @@ -0,0 +1,26 @@ +/* $OpenBSD: ksh_time.h,v 1.2 1996/10/01 02:05:40 downsj Exp $ */ + +#ifndef KSH_TIME_H +# define KSH_TIME_H + +/* Wrapper around the ugly time.h,sys/time.h includes/ifdefs */ + +#ifdef TIME_WITH_SYS_TIME +# include +# include +#else /* TIME_WITH_SYS_TIME */ +# ifdef HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif /* TIME_WITH_SYS_TIME */ + +#ifndef TIME_DECLARED +extern time_t time ARGS((time_t *)); +#endif + +#ifndef CLK_TCK +# define CLK_TCK 60 /* 60HZ */ +#endif +#endif /* KSH_TIME_H */ diff --git a/ksh_times.h b/ksh_times.h new file mode 100644 index 0000000..ec7b97e --- /dev/null +++ b/ksh_times.h @@ -0,0 +1,20 @@ +/* $OpenBSD: ksh_times.h,v 1.2 1996/10/01 02:05:41 downsj Exp $ */ + +#ifndef KSH_TIMES_H +# define KSH_TIMES_H + +/* Needed for clock_t on some systems (ie, NeXT in non-posix mode) */ +#include "ksh_time.h" + +#include + +#ifdef TIMES_BROKEN +extern clock_t ksh_times ARGS((struct tms *)); +#else /* TIMES_BROKEN */ +# define ksh_times times +#endif /* TIMES_BROKEN */ + +#ifdef HAVE_TIMES +extern clock_t times ARGS((struct tms *)); +#endif /* HAVE_TIMES */ +#endif /* KSH_TIMES_H */ diff --git a/ksh_wait.h b/ksh_wait.h new file mode 100644 index 0000000..a077b90 --- /dev/null +++ b/ksh_wait.h @@ -0,0 +1,51 @@ +/* $OpenBSD: ksh_wait.h,v 1.3 1997/06/19 13:58:43 kstailey Exp $ */ + +/* Wrapper around the ugly sys/wait includes/ifdefs */ + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + +#ifndef POSIX_SYS_WAIT +/* Get rid of system macros (which probably use union wait) */ +# undef WIFCORED +# undef WIFEXITED +# undef WEXITSTATUS +# undef WIFSIGNALED +# undef WTERMSIG +# undef WIFSTOPPED +# undef WSTOPSIG +#endif /* POSIX_SYS_WAIT */ + +typedef int WAIT_T; + +#ifndef WIFCORED +# define WIFCORED(s) ((s) & 0x80) +#endif +#define WSTATUS(s) (s) + +#ifndef WIFEXITED +# define WIFEXITED(s) (((s) & 0xff) == 0) +#endif +#ifndef WEXITSTATUS +# define WEXITSTATUS(s) (((s) >> 8) & 0xff) +#endif +#ifndef WIFSIGNALED +# define WIFSIGNALED(s) (((s) & 0xff) != 0 && ((s) & 0xff) != 0x7f) +#endif +#ifndef WTERMSIG +# define WTERMSIG(s) ((s) & 0x7f) +#endif +#ifndef WIFSTOPPED +# define WIFSTOPPED(s) (((s) & 0xff) == 0x7f) +#endif +#ifndef WSTOPSIG +# define WSTOPSIG(s) (((s) >> 8) & 0xff) +#endif + +#if !defined(HAVE_WAITPID) && defined(HAVE_WAIT3) + /* always used with p == -1 */ +# define ksh_waitpid(p, s, o) wait3((s), (o), (struct rusage *) 0) +#else /* !HAVE_WAITPID && HAVE_WAIT3 */ +# define ksh_waitpid(p, s, o) waitpid((p), (s), (o)) +#endif /* !HAVE_WAITPID && HAVE_WAIT3 */ diff --git a/lex.c b/lex.c new file mode 100644 index 0000000..11f0657 --- /dev/null +++ b/lex.c @@ -0,0 +1,1398 @@ +/* $OpenBSD: lex.c,v 1.17 2003/02/28 09:45:09 jmc Exp $ */ + +/* + * lexical analysis and source input + */ + +#include "sh.h" +#include + + +/* 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 ARGS((struct ioword *iop)); +static int getsc__ ARGS((void)); +static void getsc_line ARGS((Source *s)); +static int getsc_bn ARGS((void)); +static char *get_brace_var ARGS((XString *wsp, char *wp)); +static int arraysub ARGS((char **strp)); +static const char *ungetsc ARGS((int c)); +static void gethere ARGS((void)); +static Lex_state *push_state_ ARGS((State_info *si, Lex_state *old_end)); +static Lex_state *pop_state_ ARGS((State_info *si, Lex_state *old_end)); + +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(cf) + int cf; +{ + Lex_state states[STATE_BSIZE], *statep; + State_info state_info; + register int c, state; + XString ws; /* expandable output word */ + register char *wp; /* output word pointer */ + char *sp, *dp; + int c2; + + + Again: + states[0].ls_state = -1; + states[0].ls_info.base = (Lex_state *) 0; + 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; +#ifdef KSH + else if (cf&LETEXPR) { + *wp++ = OQUOTE; /* enclose arguments in (double) quotes */ + state = SLETPAREN; + statep->ls_sletparen.nparen = 0; + } +#endif /* KSH */ + 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 (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; + } + /* fall through.. */ + Sbase1: /* includes *(...|...) pattern (*+?@!) */ +#ifdef KSH + if (c == '*' || c == '@' || c == '+' || c == '?' + || c == '!') + { + c2 = getsc(); + if (c2 == '(' /*)*/ ) { + *wp++ = OPAT; + *wp++ = c; + PUSH_STATE(SPATTERN); + break; + } + ungetsc(c2); + } +#endif /* KSH */ + /* fall through.. */ + Sbase2: /* doesn't include *(...|...) pattern (*+?@!) */ + switch (c) { + case '\\': + c = getsc(); +#ifdef OS2 + if (isalnum(c)) { + *wp++ = CHAR, *wp++ = '\\'; + *wp++ = CHAR, *wp++ = c; + } else +#endif + if (c) /* trailing \ is lost */ + *wp++ = QCHAR, *wp++ = c; + break; + case '\'': + *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 '$': case '`': + *wp++ = QCHAR, *wp++ = c; + break; + default: + 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); + 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(); + *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 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; + } + /* fall through.. */ + default: + if (c) { /* trailing \ is lost */ + *wp++ = '\\'; + *wp++ = c; + } + break; + } + } else + *wp++ = c; + break; + + case SWORD: /* ONEWORD */ + goto Subst; + +#ifdef KSH + 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; +#endif /* KSH */ + + 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; +#ifdef KSH + else if (c == '|' && c2 == '&') + c = COPROC; +#endif /* KSH */ + else + ungetsc(c2); + return c; + + case '\n': + gethere(); + if (cf & CONTIN) + goto Again; + return c; + + case '(': /*)*/ +#ifdef KSH + if (!Flag(FSH)) { + if ((c2 = getsc()) == '(') /*)*/ + /* XXX need to handle ((...); (...)) */ + c = MDPAREN; + else + ungetsc(c2); + } +#endif /* KSH */ + return c; + /*(*/ + case ')': + return c; + } + } + + *wp++ = EOS; /* terminate word */ + yylval.cp = Xclose(ws, wp); + if (state == SWORD +#ifdef KSH + || state == SLETPAREN +#endif /* KSH */ + ) /* 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 = tsearch(&keywords, ident, h)) + && (!(cf & ESACONLY) || p->val.i == ESAC || p->val.i == '}')) + { + afree(yylval.cp, ATEMP); + return p->val.i; + } + if ((cf & ALIAS) && (p = tsearch(&aliases, ident, h)) + && (p->flag & ISSET)) + { + register 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() +{ + register struct ioword **p; + + for (p = heres; p < herep; p++) + readhere(*p); + herep = heres; +} + +/* + * read "<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 +#ifdef HAVE_PROTOTYPES +yyerror(const char *fmt, ...) +#else +yyerror(fmt, va_alist) + const char *fmt; + va_dcl +#endif +{ + 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); + SH_VA_START(va, fmt); + shf_vfprintf(shl_out, fmt, va); + va_end(va); + errorf(null); +} + +/* + * input for yylex with alias expansion + */ + +Source * +pushs(type, areap) + int type; + Area *areap; +{ + register Source *s; + + s = (Source *) alloc(sizeof(Source), areap); + s->type = type; + s->str = null; + s->start = NULL; + s->line = 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__() +{ + register Source *s = source; + register 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(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(s) + 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; + +#ifdef KSH + if (have_tty && ksh_tmout) { + ksh_tmout_state = TMOUT_READING; + alarm(ksh_tmout); + } +#endif /* KSH */ +#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; +#ifdef KSH + if (have_tty && ksh_tmout) + { + ksh_tmout_state = TMOUT_EXECUTING; + alarm(0); + } +#endif /* KSH */ + 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) { +# ifdef EASY_HISTORY + if (cur_prompt == PS2) + histappend(Xstring(s->xs, xp), 1); + else +# endif /* EASY_HISTORY */ + { + s->line++; + histsave(s->line, s->str, 1); + } + } +#endif /* HISTORY */ + } + if (interactive) + set_prompt(PS2, (Source *) 0); +} + +void +set_prompt(to, s) + int to; + Source *s; +{ + cur_prompt = to; + + switch (to) { + case PS1: /* command */ +#ifdef KSH + /* Substitute ! and !! here, before substitutions are done + * so ! in expanded variables are not expanded. + * NOTE: this is not what at&t ksh does (it does it after + * substitutions, POSIX doesn't say which is to be done. + */ + { + struct shf *shf; + char * volatile ps1; + Area *saved_atemp; + + ps1 = str_val(global("PS1")); + shf = shf_sopen((char *) 0, strlen(ps1) * 2, + SHF_WR | SHF_DYNAMIC, (struct shf *) 0); + while (*ps1) { + if (*ps1 != '!' || *++ps1 == '!') + shf_putchar(*ps1++, shf); + else + shf_fprintf(shf, "%d", + s ? s->line + 1 : 0); + } + ps1 = shf_sclose(shf); + saved_atemp = ATEMP; + newenv(E_ERRH); + if (ksh_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 + prompt = str_save(substitute(ps1, 0), + saved_atemp); + quitenv(); + } +#else /* KSH */ + prompt = str_val(global("PS1")); +#endif /* KSH */ + break; + + case PS2: /* command continuation */ + prompt = str_val(global("PS2")); + break; + } +} + +/* See also related routine, promptlen() in edit.c */ +void +pprompt(cp, ntruncate) + const char *cp; + int ntruncate; +{ +#if 0 + char nbuf[32]; + int c; + + while (*cp != 0) { + if (*cp != '!') + c = *cp++; + else if (*++cp == '!') + c = *cp++; + else { + int len; + 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; + } + shf_write(p, len, shl_out); + continue; + } + if (ntruncate) + --ntruncate; + else + shf_putc(c, shl_out); + } +#endif /* 0 */ + shf_puts(cp + ntruncate, shl_out); + shf_flush(shl_out); +} + +/* Read the variable part of a ${...} expression (ie, up to but not including + * the :[-+?=#%] or close-brace. + */ +static char * +get_brace_var(wsp, wp) + 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; + } + /* fall through.. */ + 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(strp) + 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(c) + 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 ARGS((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_(si, old_end) + 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_(si, old_end) + 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 new file mode 100644 index 0000000..35af497 --- /dev/null +++ b/lex.h @@ -0,0 +1,132 @@ +/* $OpenBSD: lex.h,v 1.7 2003/02/28 09:45:09 jmc 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 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() */ +#ifdef KSH +#define SLETPAREN 2 /* inside (( )), implicit quoting */ +#endif /* KSH */ +#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 ${..[#%]..} */ + +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 +#ifdef KSH +#define MDPAREN 277 /* (( )) */ +#endif /* KSH */ +#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 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 128 /* 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 new file mode 100644 index 0000000..c0f0b1f --- /dev/null +++ b/mail.c @@ -0,0 +1,205 @@ +/* $OpenBSD: mail.c,v 1.9 1999/06/15 01:18:35 millert Exp $ */ + +/* + * Mailbox checking code by Robert J. Gibson, adapted for PD ksh by + * John R. MacMillan + */ + +#include "config.h" + +#ifdef KSH +#include "sh.h" +#include "ksh_stat.h" +#include "ksh_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 ARGS((mbox_t *mlist)); /* free mlist and mval */ +static mbox_t * mballoc ARGS((char *p, char *m)); /* allocate a new mbox */ +static void mprintit ARGS((mbox_t *mbp)); + +void +mcheck() +{ + register mbox_t *mbp; + time_t now; + struct tbl *vp; + struct stat stbuf; + + now = time((time_t *) 0); + 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(interval) + long interval; +{ + mailcheck_interval = interval; +} + +void +mbset(p) + register 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(mptoparse) + register char *mptoparse; +{ + register mbox_t *mbp; + register char *mpath, *mmsg, *mval; + char *p; + + munset( mplist ); + mplist = NULL; + mval = str_save(mptoparse, APERM); + while (mval) { + mpath = mval; + if ((mval = strchr(mval, PATHSEP)) != 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(mlist) +register mbox_t *mlist; +{ + register 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(p, m) + char *p; + char *m; +{ + struct stat stbuf; + register 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( mbp ) +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); +} +#endif /* KSH */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..d6af946 --- /dev/null +++ b/main.c @@ -0,0 +1,863 @@ +/* $OpenBSD: main.c,v 1.23 2003/03/10 03:48:16 david Exp $ */ + +/* + * startup, main loop, environments and error handling + */ + +#define EXTERN /* define EXTERNs in sh.h */ + +#include "sh.h" +#include "ksh_stat.h" +#include "ksh_time.h" + +extern char **environ; + +/* + * global data + */ + +static void reclaim ARGS((void)); +static void remove_temps ARGS((struct temp *tp)); +static int is_restricted ARGS((char *name)); + +/* + * shell initialization + */ + +static const char initifs[] = "IFS= \t\n"; + +static const char initsubs[] = "${PS2=> } ${PS3=#? } ${PS4=+ }"; + +static const char version_param[] = +#ifdef KSH + "KSH_VERSION" +#else /* KSH */ + "SH_VERSION" +#endif /* KSH */ + ; + +static const char *const initcoms [] = { + "typeset", "-x", "SHELL", "PATH", "HOME", NULL, + "typeset", "-r", version_param, NULL, + "typeset", "-i", "PPID", NULL, + "typeset", "-i", "OPTIND=1", NULL, +#ifdef KSH + "eval", "typeset -i RANDOM MAILCHECK=\"${MAILCHECK-600}\" SECONDS=\"${SECONDS-0}\" TMOUT=\"${TMOUT-0}\"", NULL, +#endif /* KSH */ + "alias", + /* Standard ksh aliases */ + "hash=alias -t", /* not "alias -t --": hash -r needs to work */ + "type=whence -v", +#ifdef JOBS + "stop=kill -STOP", + "suspend=kill -STOP $$", +#endif +#ifdef KSH + "autoload=typeset -fu", + "functions=typeset -f", +# ifdef HISTORY + "history=fc -l", +# endif /* HISTORY */ + "integer=typeset -i", + "nohup=nohup ", + "local=typeset", + "r=fc -e -", +#endif /* KSH */ +#ifdef KSH + /* Aliases that are builtin commands in at&t */ + "login=exec login", +#ifndef __OpenBSD__ + "newgrp=exec newgrp", +#endif /* __OpenBSD__ */ +#endif /* KSH */ + 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, +#ifdef EXTRA_INITCOMS + EXTRA_INITCOMS, NULL, +#endif /* EXTRA_INITCOMS */ + NULL +}; + +int +main(argc, argv) + int argc; + register char **argv; +{ + register int i; + int argi; + Source *s; + struct block *l; + int restricted, errexit; + char **wp; + struct env env; + pid_t ppid; + +#ifdef MEM_DEBUG + chmem_set_defaults("ct", 1); + /* chmem_push("+c", 1); */ +#endif /* MEM_DEBUG */ + +#ifdef OS2 + setmode (0, O_BINARY); + setmode (1, O_TEXT); +#endif + + /* make sure argv[] is sane */ + if (!*argv) { + static const char *empty_argv[] = { + "pdksh", (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(); + +#ifdef KSH + coproc_init(); +#endif /* KSH */ + + /* set up variable and command dictionaries */ + tinit(&taliases, APERM, 0); + tinit(&aliases, APERM, 0); + tinit(&homedirs, APERM, 0); + + /* define shell keywords */ + initkeywords(); + + /* define built-in commands */ + tinit(&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 = DEFAULT__PATH; +#if defined(HAVE_CONFSTR) && defined(_CS_PATH) + { + 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; + } + } +#endif /* HAVE_CONFSTR && _CS_PATH */ + + /* 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 how - 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[strlen(kshname) - 3], "/sh") + || !strcmp(kshname, "sh") || !strcmp(kshname, "-sh")) + Flag(FSH) = 1; + + /* 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 (!ISABSPATH(pwd) + || 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); +#ifdef KSH + setint(global("RANDOM"), (long) (time((time_t *)0) * kshpid * ppid)); +#endif /* KSH */ + /* 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(); + 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 #. + */ + if (!(vp->flag & ISSET) + || (!ksheuid && !strchr(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); +#ifdef OS2 + /* a bug in os2 extproc shell processing doesn't + * pass full pathnames so we have to search for it. + * This changes the behavior of 'ksh arg' to search + * the users search path but it can't be helped. + */ + s->file = search(argv[argi++], path, R_OK, (int *) 0); + if (!s->file || !*s->file) + s->file = argv[argi - 1]; +#else + s->file = argv[argi++]; +#endif /* OS2 */ + 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 = ""; + 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 = &argv[argi - 1]; + l->argc = argc - argi; + l->argv[0] = (char *) kshname; + 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)) { +#ifdef OS2 + char *profile; + + /* Try to find a profile - first see if $INIT has a value, + * then try /etc/profile.ksh, then c:/usr/etc/profile.ksh. + */ + if (!Flag(FPRIVILEGED) + && strcmp(profile = substitute("$INIT/profile.ksh", 0), + "/profile.ksh")) + include(profile, 0, (char **) 0, 1); + else if (include("/etc/profile.ksh", 0, (char **) 0, 1) < 0) + include("c:/usr/etc/profile.ksh", 0, (char **) 0, 1); + if (!Flag(FPRIVILEGED)) + include(substitute("$HOME/profile.ksh", 0), 0, + (char **) 0, 1); +#else /* OS2 */ + include(KSH_SYSTEM_PROFILE, 0, (char **) 0, 1); + if (!Flag(FPRIVILEGED)) + include(substitute("$HOME/.profile", 0), 0, + (char **) 0, 1); +#endif /* OS2 */ + } + + if (Flag(FPRIVILEGED)) + include("/etc/suid_profile", 0, (char **) 0, 1); + else { + char *env_file; + +#ifndef KSH + if (!Flag(FPOSIX)) + env_file = null; + else +#endif /* !KSH */ + /* 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); +#ifdef OS2 + else if (Flag(FTALKING)) + include(substitute("$HOME/kshrc.ksh", 0), 0, + (char **) 0, 1); +#endif /* OS2 */ + } + + 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); +#ifdef KSH + alarm_init(); +#endif /* KSH */ + } else + Flag(FTRACKALL) = 1; /* set after ENV */ + + shell(s, TRUE); /* doesn't return */ + return 0; +} + +int +include(name, argc, argv, intr_ok) + const char *name; + int argc; + char **argv; + int intr_ok; +{ + register 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 = ksh_sigsetjmp(e->jbuf, 0); + if (i) { + if (s) /* Do this before quitenv(), which frees the memory */ + shf_close(s->u.shf); + quitenv(); + 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; + /* fall through... */ + case LEXIT: + case LLEAVE: + case LSHELL: + unwind(i); + /*NOREACHED*/ + default: + internal_errorf(1, "include: %d", i); + /*NOREACHED*/ + } + } + 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); + shf_close(s->u.shf); + quitenv(); + if (old_argv) { + e->loc->argv = old_argv; + e->loc->argc = old_argc; + } + return i & 0xff; /* & 0xff to ensure value not -1 */ +} + +int +command(comm) + const char *comm; +{ + register Source *s; + + s = pushs(SSTRING, ATEMP); + s->start = s->str = comm; + return shell(s, FALSE); +} + +/* + * run the commands from the input source, returning status. + */ +int +shell(s, toplevel) + Source *volatile s; /* input source */ + int volatile 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 = ksh_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; + } + /* fall through... */ + case LEXIT: + case LLEAVE: + case LRETURN: + source = old_source; + quitenv(); + unwind(i); /* keep on going */ + /*NOREACHED*/ + default: + source = old_source; + quitenv(); + internal_errorf(1, "shell: %d", i); + /*NOREACHED*/ + } + } + + while (1) { + if (trap) + runtraps(0); + + if (s->next == NULL) { + if (Flag(FVERBOSE)) + s->flags |= SF_ECHO; + else + s->flags &= ~SF_ECHO; + } + + if (interactive) { + j_notify(); +#ifdef KSH + mcheck(); +#endif /* KSH */ + 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); + + if (t != NULL && t->type != TEOF && interactive && really_exit) + really_exit = 0; + + reclaim(); + } + quitenv(); + source = old_source; + return exstat; +} + +/* return to closest error handler or shell(), exit if none found */ +void +unwind(i) + 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)) + { + runtrap(&sigtraps[SIGEXIT_]); + i = LLEAVE; + } else if (Flag(FERREXIT) && (i == LERROR || i == LINTR)) { + 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: + ksh_siglongjmp(e->jbuf, i); + /*NOTREACHED*/ + + case E_NONE: + if (i == LINTR) + e->flags |= EF_FAKE_SIGDIE; + /* Fall through... */ + + default: + quitenv(); + } + } +} + +void +newenv(type) + int type; +{ + register 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() +{ + register struct env *ep = e; + register 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); + } + reclaim(); + + /* 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) { + setsig(&sigtraps[sig], SIG_DFL, + SS_RESTORE_CURR|SS_FORCE); + kill(0, sig); + } + } +#ifdef MEM_DEBUG + chmem_allfree(); +#endif /* MEM_DEBUG */ + } + exit(exstat); + } + + e = e->oenv; + afree(ep, ATEMP); +} + +/* Called after a fork to cleanup stuff left over from parents environment */ +void +cleanup_parents_env() +{ + 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() +{ + struct env *ep; + + for (ep = e; ep; ep = ep->oenv) + remove_temps(ep->temps); +} + +/* remove temp files and free ATEMP Area */ +static void +reclaim() +{ + remove_temps(e->temps); + e->temps = NULL; + afreeall(&e->area); +} + +static void +remove_temps(tp) + struct temp *tp; +{ +#ifdef OS2 + static struct temp *delayed_remove; + struct temp *t, **tprev; + + if (delayed_remove) { + for (tprev = &delayed_remove, t = delayed_remove; t; t = *tprev) + /* No need to check t->pid here... */ + if (unlink(t->name) >= 0 || errno == ENOENT) { + *tprev = t->next; + afree(t, APERM); + } else + tprev = &t->next; + } +#endif /* OS2 */ + + for (; tp != NULL; tp = tp->next) + if (tp->pid == procpid) { +#ifdef OS2 + /* OS/2 (and dos) do not allow files that are currently + * open to be removed, so we cache it away for future + * removal. + * XXX should only do this if errno + * is Efile-still-open-can't-remove + * (but I don't know what that is...) + */ + if (unlink(tp->name) < 0 && errno != ENOENT) { + t = (struct temp *) alloc( + sizeof(struct temp) + strlen(tp->name) + 1, + APERM); + memset(t, 0, sizeof(struct temp)); + t->name = (char *) &t[1]; + strcpy(t->name, tp->name); + t->next = delayed_remove; + delayed_remove = t; + } +#else /* OS2 */ + unlink(tp->name); +#endif /* OS2 */ + } +} + +/* Returns true if name refers to a restricted shell */ +static int +is_restricted(name) + char *name; +{ + char *p; + + if ((p = ksh_strrchr_dirsep(name))) + name = p; + /* accepts rsh, rksh, rpdksh, pdrksh, etc. */ + return (p = strchr(name, 'r')) && strstr(p, "sh"); +} + +void +aerror(ap, msg) + Area *ap; + const char *msg; +{ + internal_errorf(1, "alloc: %s", msg); + errorf(null); /* this is never executed - keeps gcc quiet */ + /*NOTREACHED*/ +} diff --git a/misc.c b/misc.c new file mode 100644 index 0000000..d916ec4 --- /dev/null +++ b/misc.c @@ -0,0 +1,1350 @@ +/* $OpenBSD: misc.c,v 1.14 2003/03/13 09:03:07 deraadt Exp $ */ + +/* + * Miscellaneous functions + */ + +#include "sh.h" +#include /* for FILECHCONV */ +#ifdef HAVE_LIMITS_H +# include +#endif + +#ifndef UCHAR_MAX +# define UCHAR_MAX 0xFF +#endif + +short ctypes [UCHAR_MAX+1]; /* type bits for unsigned char */ + +static int do_gmatch ARGS((const unsigned char *s, const unsigned char *p, + const unsigned char *se, const unsigned char *pe, + int isfile)); +static const unsigned char *cclass ARGS((const unsigned char *p, int sub)); + +/* + * Fast character classes + */ +void +setctypes(s, t) + register const char *s; + register int t; +{ + register 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() +{ + register 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(n, base) + register unsigned long n; + int base; +{ + register 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(s, ap) + register const char *s; + Area *ap; +{ + return s ? strcpy((char*) alloc((size_t)strlen(s)+1, ap), s) : NULL; +} + +/* 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(s, n, ap) + register 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_(xsp, xp, more) + 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 }, +#ifdef EMACS + { "emacs", 0, OF_ANY }, +#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 visable to user) + */ + { (char *) 0, 0, OF_INTERNAL }, /* FTALKING_I */ +}; + +/* + * translate -o option into F* constant (also used for test -o option) + */ +int +option(n) + 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 ARGS((void *arg, int i, char *buf, int buflen)); +static void printoptions ARGS((int verbose)); + +/* format a single select menu item */ +static char * +options_fmt_entry(arg, i, buf, buflen) + 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(verbose) + 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() +{ + int i; + char m[(int) FNFLAGS + 1]; + register 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(f, what, newval) + enum sh_flag f; /* flag to change */ + int what; /* what is changing the flag (command line vs set) */ + int newval; +{ + 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) { +#ifdef OS2 + ; +#else /* OS2 */ + seteuid(ksheuid = getuid()); + setuid(ksheuid); + setegid(getgid()); + setgid(getgid()); +#endif /* OS2 */ + } 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(argv, what, setargsp) + 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 = ksh_strrchr_dirsep(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)) != EOF) { + 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(as, ai) + 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(as, ai) + 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(s, p, isfile) + const char *s, *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); + return !strcmp(t, s); + } + return do_gmatch((const unsigned char *) s, (const unsigned char *) se, + (const unsigned char *) p, (const unsigned char *) pe, + isfile); +} + +/* 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(xp, xpe) + const char *xp, *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(s, se, p, pe, isfile) + const unsigned char *s, *p; + const unsigned char *se, *pe; + int isfile; +{ + register 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 (isfile) { + sc = FILECHCONV(sc); + pc = FILECHCONV(pc); + } + 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, isfile)) + 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, isfile)) + 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, isfile) + && (do_gmatch(srest, se, + prest, pe, isfile) + || (s != srest + && do_gmatch(srest, se, + p - 2, pe, isfile)))) + 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, isfile)) + 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, isfile) + && do_gmatch(srest, se, + prest, pe, isfile)) + 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, isfile)) + { + matched = 1; + break; + } + if (pnext == prest) + break; + } + if (!matched && do_gmatch(srest, se, + prest, pe, isfile)) + return 1; + } + return 0; + + default: + if (sc != p[-1]) + return 0; + break; + } + } + return s == se; +} + +static const unsigned char * +cclass(p, sub) + const unsigned char *p; + register int sub; +{ + register int c, d, not, found = 0; + const unsigned char *orig_p = p; + + if ((not = (ISMAGIC(*p) && *++p == NOT))) + p++; + do { + 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(p, pe, match_sep) + 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; +} + + +/* -------- qsort.c -------- */ + +/* + * quick sort of array of generic pointers to objects. + */ +static void qsort1 ARGS((void **base, void **lim, int (*f)(void *, void *))); + +void +qsortp(base, n, f) + void **base; /* base address */ + size_t n; /* elements */ + int (*f) ARGS((void *, void *)); /* compare function */ +{ + qsort1(base, base + n, f); +} + +#define swap2(a, b) {\ + register void *t; t = *(a); *(a) = *(b); *(b) = t;\ +} +#define swap3(a, b, c) {\ + register void *t; t = *(a); *(a) = *(c); *(c) = *(b); *(b) = t;\ +} + +static void +qsort1(base, lim, f) + void **base, **lim; + int (*f) ARGS((void *, void *)); +{ + register void **i, **j; + register void **lptr, **hptr; + size_t n; + int c; + + top: + n = (lim - base) / 2; + if (n == 0) + return; + hptr = lptr = base+n; + i = base; + j = lim - 1; + + for (;;) { + if (i < lptr) { + if ((c = (*f)(*i, *lptr)) == 0) { + lptr --; + swap2(i, lptr); + continue; + } + if (c < 0) { + i += 1; + continue; + } + } + + begin: + if (j > hptr) { + if ((c = (*f)(*hptr, *j)) == 0) { + hptr ++; + swap2(hptr, j); + goto begin; + } + if (c > 0) { + if (i == lptr) { + hptr ++; + swap3(i, hptr, j); + i = lptr += 1; + goto begin; + } + swap2(i, j); + j -= 1; + i += 1; + continue; + } + j -= 1; + goto begin; + } + + if (i == lptr) { + if (lptr-base >= lim-hptr) { + qsort1(hptr+1, lim, f); + lim = lptr; + } else { + qsort1(base, lptr, f); + base = hptr+1; + } + goto top; + } + + lptr -= 1; + swap3(j, lptr, i); + j = hptr -= 1; + } +} + +int +xstrcmp(p1, p2) + void *p1, *p2; +{ + return (strcmp((char *)p1, (char *)p2)); +} + +/* Initialize a Getopt structure */ +void +ksh_getopt_reset(go, flags) + 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. If the argument doesn't start with a + * digit, 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'. + * - 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(argv, go, options) + 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 EOF; + } + if (arg == (char *) 0 + || ((flag != '-' ) /* neither a - nor a + (if + allowed) */ + && (!(go->flags & GF_PLUSOPT) || flag != '+')) + || (c = arg[1]) == '\0') + { + go->p = 0; + return EOF; + } + 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])) { + 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])) { + 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(s) + 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(shf, n, func, arg, max_width, prefcol) + struct shf *shf; + int n; + char *(*func) ARGS((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; + + /* 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; + } + + 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", + max_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(buf, nbytes) + 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; +} + +/* Copy at most dsize-1 bytes from src to dst, ensuring dst is null terminated. + * Returns dst. + */ +char * +str_zcpy(dst, src, dsize) + char *dst; + const char *src; + int dsize; +{ + if (dsize > 0) { + int len = strlen(src); + + if (len >= dsize) + len = dsize - 1; + memcpy(dst, src, len); + dst[len] = '\0'; + } + return dst; +} + +/* Like read(2), but if read fails due to non-blocking flag, resets flag + * and restarts read. + */ +int +blocking_read(fd, buf, nbytes) + int fd; + char *buf; + int nbytes; +{ + int ret; + int tried_reset = 0; + + while ((ret = read(fd, buf, nbytes)) < 0) { + if (!tried_reset && (errno == EAGAIN +#ifdef EWOULDBLOCK + || errno == EWOULDBLOCK +#endif /* EWOULDBLOCK */ + )) + { + 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(fd) + int fd; +{ + int flags; + int blocking_flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) < 0) + return -1; + /* With luck, the C compiler will reduce this to a constant */ + blocking_flags = 0; +#ifdef O_NONBLOCK + blocking_flags |= O_NONBLOCK; +#endif /* O_NONBLOCK */ +#ifdef O_NDELAY + blocking_flags |= O_NDELAY; +#else /* O_NDELAY */ +# ifndef O_NONBLOCK + blocking_flags |= FNDELAY; /* hope this exists... */ +# endif /* O_NONBLOCK */ +#endif /* O_NDELAY */ + if (!(flags & blocking_flags)) + return 0; + flags &= ~blocking_flags; + if (fcntl(fd, F_SETFL, flags) < 0) + return -1; + return 1; +} + + +#ifdef HAVE_SYS_PARAM_H +# include +#endif /* HAVE_SYS_PARAM_H */ +#ifndef MAXPATHLEN +# define MAXPATHLEN PATH +#endif /* MAXPATHLEN */ + +#ifdef HPUX_GETWD_BUG +# include "ksh_dir.h" + +/* + * Work around bug in hpux 10.x C library - getwd/getcwd dump core + * if current directory is not readable. Done in macro 'cause code + * is needed in GETWD and GETCWD cases. + */ +# define HPUX_GETWD_BUG_CODE \ + { \ + DIR *d = ksh_opendir("."); \ + if (!d) \ + return (char *) 0; \ + closedir(d); \ + } +#else /* HPUX_GETWD_BUG */ +# define HPUX_GETWD_BUG_CODE +#endif /* HPUX_GETWD_BUG */ + +/* Like getcwd(), except bsize is ignored if buf is 0 (MAXPATHLEN is used) */ +char * +ksh_get_wd(buf, bsize) + char *buf; + int bsize; +{ +#ifdef HAVE_GETCWD + char *b; + char *ret; + + /* Before memory allocated */ + HPUX_GETWD_BUG_CODE + + /* 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; +#else /* HAVE_GETCWD */ + extern char *getwd ARGS((char *)); + char *b; + int len; + + /* Before memory allocated */ + HPUX_GETWD_BUG_CODE + + if (buf && bsize > MAXPATHLEN) + b = buf; + else + b = alloc(MAXPATHLEN + 1, ATEMP); + if (!getwd(b)) { + errno = EACCES; + if (b != buf) + afree(b, ATEMP); + return (char *) 0; + } + len = strlen(b) + 1; + if (!buf) + b = aresize(b, len, ATEMP); + else if (buf != b) { + if (len > bsize) { + errno = ERANGE; + return (char *) 0; + } + memcpy(buf, b, len); + afree(b, ATEMP); + b = buf; + } + + return b; +#endif /* HAVE_GETCWD */ +} diff --git a/missing.c b/missing.c new file mode 100644 index 0000000..2bcffac --- /dev/null +++ b/missing.c @@ -0,0 +1,295 @@ +/* $OpenBSD: missing.c,v 1.4 1999/06/15 01:18:35 millert Exp $ */ + +/* + * Routines which may be missing on some machines + */ + +#include "sh.h" +#include "ksh_stat.h" +#include "ksh_dir.h" + + +#ifndef HAVE_MEMSET +void * +memset(d, c, n) + void *d; + int c; + size_t n; +{ + unsigned char *p = (unsigned char *) d; + + /* Not amazingly fast.. */ + for (; n > 0; --n) + *p++ = c; + return d; +} +#endif /* !HAVE_MEMSET */ + +#if !defined(HAVE_MEMMOVE) && !defined(HAVE_BCOPY) +void * +memmove(d, s, n) + void *d; + const void *s; + size_t n; +{ + char *dp = (char *) d, *sp = (char *) s; + + if (n <= 0) + ; + else if (dp < sp) + do + *dp++ = *sp++; + while (--n > 0); + else if (dp > sp) { + dp += n; + sp += n; + do + *--dp = *--sp; + while (--n > 0); + } + return d; +} +#endif /* !HAVE_MEMMOVE && !HAVE_BCOPY */ + + +#ifndef HAVE_STRCASECMP +/* + * Case insensitive string compare routines, same semantics as str[n]cmp() + * (assumes ASCII..). + */ +static const char ichars[256] = { + 0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', + 'x', 'y', 'z', 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff + }; + +int +strcasecmp(s1, s2) + const char *s1; + const char *s2; +{ + const unsigned char *us1 = (const unsigned char *) s1; + const unsigned char *us2 = (const unsigned char *) s2; + + while (ichars[*us1] == ichars[*us2++]) + if (!*us1++) + return 0; + + return ichars[*us1] - ichars[*--us2]; +} + +int +strncasecmp(s1, s2, n) + const char *s1; + const char *s2; + int n; +{ + const unsigned char *us1 = (const unsigned char *) s1; + const unsigned char *us2 = (const unsigned char *) s2; + + while (--n >= 0 && ichars[*us1] == ichars[*us2++]) + if (!*us1++) + return 0; + + return n < 0 ? 0 : ichars[*us1] - ichars[*--us2]; +} +#endif /* HAVE_STRCASECMP */ + + +#ifndef HAVE_STRSTR +char * +strstr(s, p) + const char *s; + const char *p; +{ + int len; + + if (s && p) + for (len = strlen(p); *s; s++) + if (*s == *p && strncmp(s, p, len) == 0) + return (char *) s; + + return 0; +} +#endif /* HAVE_STRSTR */ + + +#ifndef HAVE_STRERROR +char * +strerror(err) + int err; +{ + static char buf[64]; +# ifdef HAVE_SYS_ERRLIST +# ifndef SYS_ERRLIST_DECLARED + extern int sys_nerr; + extern char *sys_errlist[]; +# endif + char *p; + + if (err < 0 || err >= sys_nerr) + shf_snprintf(p = buf, sizeof(buf), "Unknown system error %d", + err); + else + p = sys_errlist[err]; + return p; +# else /* HAVE_SYS_ERRLIST */ + switch (err) { + case EINVAL: + return "Invalid argument"; + case EACCES: + return "Permission denied"; + case ESRCH: + return "No such process"; + case EPERM: + return "Not owner"; + case ENOENT: + return "No such file or directory"; + case ENOTDIR: + return "Not a directory"; + case ENOEXEC: + return "Exec format error"; + case ENOMEM: + return "Not enough memory"; + case E2BIG: + return "Argument list too long"; + default: + shf_snprintf(buf, sizeof(buf), "Unknown system error %d", err); + return buf; + } +# endif /* HAVE_SYS_ERRLIST */ +} +#endif /* !HAVE_STRERROR */ + + +#ifdef TIMES_BROKEN +# include "ksh_time.h" +# include "ksh_times.h" +# ifdef HAVE_GETRUSAGE +# include +# else /* HAVE_GETRUSAGE */ +# include +# endif /* HAVE_GETRUSAGE */ + +clock_t +ksh_times(tms) + struct tms *tms; +{ + static clock_t base_sec; + clock_t rv; + +# ifdef HAVE_GETRUSAGE + { + struct timeval tv; + struct rusage ru; + + getrusage(RUSAGE_SELF, &ru); + tms->tms_utime = ru.ru_utime.tv_sec * CLK_TCK + + ru.ru_utime.tv_usec * CLK_TCK / 1000000; + tms->tms_stime = ru.ru_stime.tv_sec * CLK_TCK + + ru.ru_stime.tv_usec * CLK_TCK / 1000000; + + getrusage(RUSAGE_CHILDREN, &ru); + tms->tms_cutime = ru.ru_utime.tv_sec * CLK_TCK + + ru.ru_utime.tv_usec * CLK_TCK / 1000000; + tms->tms_cstime = ru.ru_stime.tv_sec * CLK_TCK + + ru.ru_stime.tv_usec * CLK_TCK / 1000000; + + gettimeofday(&tv, (struct timezone *) 0); + if (base_sec == 0) + base_sec = tv.tv_sec; + rv = (tv.tv_sec - base_sec) * CLK_TCK; + rv += tv.tv_usec * CLK_TCK / 1000000; + } +# else /* HAVE_GETRUSAGE */ + /* Assume times() available, but always returns 0 + * (also assumes ftime() available) + */ + { + struct timeb tb; + + if (times(tms) == (clock_t) -1) + return (clock_t) -1; + ftime(&tb); + if (base_sec == 0) + base_sec = tb.time; + rv = (tb.time - base_sec) * CLK_TCK; + rv += tb.millitm * CLK_TCK / 1000; + } +# endif /* HAVE_GETRUSAGE */ + return rv; +} +#endif /* TIMES_BROKEN */ + +#ifdef OPENDIR_DOES_NONDIR +/* Prevent opendir() from attempting to open non-directories. Such + * behavior can cause problems if it attempts to open special devices... + */ +DIR * +ksh_opendir(d) + const char *d; +{ + struct stat statb; + + if (stat(d, &statb) != 0) + return (DIR *) 0; + if (!S_ISDIR(statb.st_mode)) { + errno = ENOTDIR; + return (DIR *) 0; + } + return opendir(d); +} +#endif /* OPENDIR_DOES_NONDIR */ + +#ifndef HAVE_DUP2 +int +dup2(oldd, newd) + int oldd; + int newd; +{ + int old_errno; + + if (fcntl(oldd, F_GETFL, 0) == -1) + return -1; /* errno == EBADF */ + + if (oldd == newd) + return newd; + + old_errno = errno; + + close(newd); /* in case its open */ + + errno = old_errno; + + return fcntl(oldd, F_DUPFD, newd); +} +#endif /* !HAVE_MEMSET */ diff --git a/path.c b/path.c new file mode 100644 index 0000000..645a2cc --- /dev/null +++ b/path.c @@ -0,0 +1,312 @@ +/* $OpenBSD: path.c,v 1.8 2003/02/28 09:45:09 jmc Exp $ */ + +#include "sh.h" +#include "ksh_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) + */ + +#ifdef S_ISLNK +static char *do_phys_path ARGS((XString *xsp, char *xp, const char *path)); +#endif /* S_ISLNK */ + +/* + * 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 appened to result. + */ +int +make_path(cwd, file, cdpathp, xsp, phys_pathp) + 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 (!ISRELPATH(file)) { + *phys_pathp = 0; + use_cdpath = 0; + } else { + if (file[0] == '.') { + char c = file[1]; + + if (c == '.') + c = file[2]; + if (ISDIRSEP(c) || c == '\0') + use_cdpath = 0; + } + + plist = *cdpathp; + if (!plist) + use_cdpath = 0; + else if (use_cdpath) { + char *pend; + + for (pend = plist; *pend && *pend != PATHSEP; pend++) + ; + plen = pend - plist; + *cdpathp = *pend ? ++pend : (char *) 0; + } + + if ((use_cdpath == 0 || !plen || ISRELPATH(plist)) + && (cwd && *cwd)) + { + len = strlen(cwd); + XcheckN(*xsp, xp, len); + memcpy(xp, cwd, len); + xp += len; + if (!ISDIRSEP(cwd[len - 1])) + Xput(*xsp, xp, DIRSEP); + } + *phys_pathp = Xlength(*xsp, xp); + if (use_cdpath && plen) { + XcheckN(*xsp, xp, plen); + memcpy(xp, plist, plen); + xp += plen; + if (!ISDIRSEP(plist[plen - 1])) + Xput(*xsp, xp, DIRSEP); + 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(path) + char *path; +{ + char *cur; + char *t; + int isrooted; + char *very_start = path; + char *start; + + if (!*path) + return; + + if ((isrooted = ISROOTEDPATH(path))) + very_start++; +#if defined (OS2) || defined (__CYGWIN__) + if (path[0] && path[1] == ':') /* skip a: */ + very_start += 2; +#endif /* OS2 || __CYGWIN__ */ + + /* Before After + * /foo/ /foo + * /foo/../../bar /bar + * /foo/./blah/.. /foo + * . . + * .. .. + * ./foo foo + * foo/../../../bar ../../bar + * OS2 and CYGWIN: + * a:/foo/../.. a:/ + * a:. a: + * a:.. a:.. + * a:foo/../../blah a:../blah + */ + +#ifdef __CYGWIN__ + /* preserve leading double-slash on pathnames (for UNC paths) */ + if (path[0] && ISDIRSEP(path[0]) && path[1] && ISDIRSEP(path[1])) + very_start++; +#endif /* __CYGWIN__ */ + + for (cur = t = start = very_start; ; ) { + /* treat multiple '/'s as one '/' */ + while (ISDIRSEP(*t)) + t++; + + if (*t == '\0') { + if (cur == path) + /* convert empty path to dot */ + *cur++ = '.'; + *cur = '\0'; + break; + } + + if (t[0] == '.') { + if (!t[1] || ISDIRSEP(t[1])) { + t += 1; + continue; + } else if (t[1] == '.' && (!t[2] || ISDIRSEP(t[2]))) { + if (!isrooted && cur == start) { + if (cur != very_start) + *cur++ = DIRSEP; + *cur++ = '.'; + *cur++ = '.'; + start = cur; + } else if (cur != start) + while (--cur > start && !ISDIRSEP(*cur)) + ; + t += 2; + continue; + } + } + + if (cur != very_start) + *cur++ = DIRSEP; + + /* find/copy next component of pathname */ + while (*t && !ISDIRSEP(*t)) + *cur++ = *t++; + } +} + + +void +set_current_wd(path) + 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); +} + +#ifdef S_ISLNK +char * +get_phys_path(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, DIRSEP); + Xput(xs, xp, '\0'); + + return Xclose(xs, xp); +} + +static char * +do_phys_path(xsp, xp, 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 (ISDIRSEP(*p)) + p++; + if (!*p) + break; + len = (q = ksh_strchr_dirsep(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 (ISDIRSEP(*xp)) + break; + } + continue; + } + + savepos = Xsavepos(*xsp, xp); + Xput(*xsp, xp, DIRSEP); + 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 = ISABSPATH(lbuf) ? Xstring(*xsp, xp) + : Xrestpos(*xsp, xp, savepos); + if (!(xp = do_phys_path(xsp, xp, lbuf))) + return (char *) 0; + } + return xp; +} +#endif /* S_ISLNK */ + +#ifdef TEST + +main(argc, argv) +{ + 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 new file mode 100644 index 0000000..7a7c9a8 --- /dev/null +++ b/proto.h @@ -0,0 +1,303 @@ +/* $OpenBSD: proto.h,v 1.9 2001/02/19 09:49:54 camield 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 ARGS((Area *ap)); +void afreeall ARGS((Area *ap)); +void * alloc ARGS((size_t size, Area *ap)); +void * aresize ARGS((void *ptr, size_t size, Area *ap)); +void afree ARGS((void *ptr, Area *ap)); +/* c_ksh.c */ +int c_hash ARGS((char **wp)); +int c_cd ARGS((char **wp)); +int c_pwd ARGS((char **wp)); +int c_print ARGS((char **wp)); +int c_whence ARGS((char **wp)); +int c_command ARGS((char **wp)); +int c_typeset ARGS((char **wp)); +int c_alias ARGS((char **wp)); +int c_unalias ARGS((char **wp)); +int c_let ARGS((char **wp)); +int c_jobs ARGS((char **wp)); +int c_fgbg ARGS((char **wp)); +int c_kill ARGS((char **wp)); +void getopts_reset ARGS((int val)); +int c_getopts ARGS((char **wp)); +int c_bind ARGS((char **wp)); +/* c_sh.c */ +int c_label ARGS((char **wp)); +int c_shift ARGS((char **wp)); +int c_umask ARGS((char **wp)); +int c_dot ARGS((char **wp)); +int c_wait ARGS((char **wp)); +int c_read ARGS((char **wp)); +int c_eval ARGS((char **wp)); +int c_trap ARGS((char **wp)); +int c_brkcont ARGS((char **wp)); +int c_exitreturn ARGS((char **wp)); +int c_set ARGS((char **wp)); +int c_unset ARGS((char **wp)); +int c_ulimit ARGS((char **wp)); +int c_times ARGS((char **wp)); +int timex ARGS((struct op *t, int f)); +void timex_hook ARGS((struct op *t, char ** volatile *app)); +int c_exec ARGS((char **wp)); +int c_builtin ARGS((char **wp)); +/* c_test.c */ +int c_test ARGS((char **wp)); +/* edit.c: most prototypes in edit.h */ +void x_init ARGS((void)); +int x_read ARGS((char *buf, size_t len)); +void set_editmode ARGS((const char *ed)); +/* emacs.c: most prototypes in edit.h */ +int x_bind ARGS((const char *a1, const char *a2, int macro, + int list)); +/* eval.c */ +char * substitute ARGS((const char *cp, int f)); +char ** eval ARGS((char **ap, int f)); +char * evalstr ARGS((char *cp, int f)); +char * evalonestr ARGS((char *cp, int f)); +char *debunk ARGS((char *dp, const char *sp)); +void expand ARGS((char *cp, XPtrV *wp, int f)); +int glob_str ARGS((char *cp, XPtrV *wp, int markdirs)); +/* exec.c */ +int fd_clexec ARGS((int fd)); +int execute ARGS((struct op * volatile t, volatile int flags)); +int shcomexec ARGS((char **wp)); +struct tbl * findfunc ARGS((const char *name, unsigned int h, int create)); +int define ARGS((const char *name, struct op *t)); +void builtin ARGS((const char *name, int (*func)(char **))); +struct tbl * findcom ARGS((const char *name, int flags)); +void flushcom ARGS((int all)); +char * search ARGS((const char *name, const char *path, int mode, + int *errnop)); +int search_access ARGS((const char *path, int mode, int *errnop)); +int pr_menu ARGS((char *const *ap)); +int pr_list ARGS((char *const *ap)); +/* expr.c */ +int evaluate ARGS((const char *expr, long *rval, int error_ok)); +int v_evaluate ARGS((struct tbl *vp, const char *expr, volatile int error_ok)); +/* history.c */ +void init_histvec ARGS((void)); +void hist_init ARGS((Source *s)); +void hist_finish ARGS((void)); +void histsave ARGS((int lno, const char *cmd, int dowrite)); +#ifdef HISTORY +int c_fc ARGS((register char **wp)); +void sethistsize ARGS((int n)); +void sethistfile ARGS((const char *name)); +# ifdef EASY_HISTORY +void histappend ARGS((const char *cmd, int nl_separate)); +# endif +char ** histpos ARGS((void)); +int histN ARGS((void)); +int histnum ARGS((int n)); +int findhist ARGS((int start, int fwd, const char *str, + int anchored)); +#endif /* HISTORY */ +/* io.c */ +void errorf ARGS((const char *fmt, ...)) + GCC_FUNC_ATTR2(noreturn, format(printf, 1, 2)); +void warningf ARGS((int fileline, const char *fmt, ...)) + GCC_FUNC_ATTR(format(printf, 2, 3)); +void bi_errorf ARGS((const char *fmt, ...)) + GCC_FUNC_ATTR(format(printf, 1, 2)); +void internal_errorf ARGS((int jump, const char *fmt, ...)) + GCC_FUNC_ATTR(format(printf, 2, 3)); +void error_prefix ARGS((int fileline)); +void shellf ARGS((const char *fmt, ...)) + GCC_FUNC_ATTR(format(printf, 1, 2)); +void shprintf ARGS((const char *fmt, ...)) + GCC_FUNC_ATTR(format(printf, 1, 2)); +#ifdef KSH_DEBUG +void kshdebug_init_ ARGS((void)); +void kshdebug_printf_ ARGS((const char *fmt, ...)) + GCC_FUNC_ATTR(format(printf, 1, 2)); +void kshdebug_dump_ ARGS((const char *str, const void *mem, int nbytes)); +#endif /* KSH_DEBUG */ +int can_seek ARGS((int fd)); +void initio ARGS((void)); +int ksh_dup2 ARGS((int ofd, int nfd, int errok)); +int savefd ARGS((int fd, int noclose)); +void restfd ARGS((int fd, int ofd)); +void openpipe ARGS((int *pv)); +void closepipe ARGS((int *pv)); +int check_fd ARGS((char *name, int mode, const char **emsgp)); +#ifdef KSH +void coproc_init ARGS((void)); +void coproc_read_close ARGS((int fd)); +void coproc_readw_close ARGS((int fd)); +void coproc_write_close ARGS((int fd)); +int coproc_getfd ARGS((int mode, const char **emsgp)); +void coproc_cleanup ARGS((int reuse)); +#endif /* KSH */ +struct temp *maketemp ARGS((Area *ap, Temp_type type, struct temp **tlist)); +/* jobs.c */ +void j_init ARGS((int mflagset)); +void j_exit ARGS((void)); +void j_change ARGS((void)); +int exchild ARGS((struct op *t, int flags, int close_fd)); +void startlast ARGS((void)); +int waitlast ARGS((void)); +int waitfor ARGS((const char *cp, int *sigp)); +int j_kill ARGS((const char *cp, int sig)); +int j_resume ARGS((const char *cp, int bg)); +int j_jobs ARGS((const char *cp, int slp, int nflag)); +void j_notify ARGS((void)); +pid_t j_async ARGS((void)); +int j_stopped_running ARGS((void)); +/* lex.c */ +int yylex ARGS((int cf)); +void yyerror ARGS((const char *fmt, ...)) + GCC_FUNC_ATTR2(noreturn, format(printf, 1, 2)); +Source * pushs ARGS((int type, Area *areap)); +void set_prompt ARGS((int to, Source *s)); +void pprompt ARGS((const char *cp, int ntruncate)); +/* mail.c */ +#ifdef KSH +void mcheck ARGS((void)); +void mcset ARGS((long interval)); +void mbset ARGS((char *p)); +void mpset ARGS((char *mptoparse)); +#endif /* KSH */ +/* main.c */ +int include ARGS((const char *name, int argc, char **argv, + int intr_ok)); +int command ARGS((const char *comm)); +int shell ARGS((Source *volatile s, int volatile toplevel)); +void unwind ARGS((int i)) GCC_FUNC_ATTR(noreturn); +void newenv ARGS((int type)); +void quitenv ARGS((void)); +void cleanup_parents_env ARGS((void)); +void cleanup_proc_env ARGS((void)); +void aerror ARGS((Area *ap, const char *msg)) + GCC_FUNC_ATTR(noreturn); +/* misc.c */ +void setctypes ARGS((const char *s, int t)); +void initctypes ARGS((void)); +char * ulton ARGS((unsigned long n, int base)); +char * str_save ARGS((const char *s, Area *ap)); +char * str_nsave ARGS((const char *s, int n, Area *ap)); +int option ARGS((const char *n)); +char * getoptions ARGS((void)); +void change_flag ARGS((enum sh_flag f, int what, int newval)); +int parse_args ARGS((char **argv, int what, int *setargsp)); +int getn ARGS((const char *as, int *ai)); +int bi_getn ARGS((const char *as, int *ai)); +char * strerror ARGS((int i)); +int gmatch ARGS((const char *s, const char *p, int isfile)); +int has_globbing ARGS((const char *xp, const char *xpe)); +const unsigned char *pat_scan ARGS((const unsigned char *p, + const unsigned char *pe, int match_sep)); +void qsortp ARGS((void **base, size_t n, int (*f)(void *, void *))); +int xstrcmp ARGS((void *p1, void *p2)); +void ksh_getopt_reset ARGS((Getopt *go, int)); +int ksh_getopt ARGS((char **argv, Getopt *go, const char *options)); +void print_value_quoted ARGS((const char *s)); +void print_columns ARGS((struct shf *shf, int n, + char *(*func)(void *, int, char *, int), + void *arg, int max_width, int prefcol)); +int strip_nuls ARGS((char *buf, int nbytes)); +char *str_zcpy ARGS((char *dst, const char *src, int dsize)); +int blocking_read ARGS((int fd, char *buf, int nbytes)); +int reset_nonblock ARGS((int fd)); +char *ksh_get_wd ARGS((char *buf, int bsize)); +/* path.c */ +int make_path ARGS((const char *cwd, const char *file, + char **pathlist, XString *xsp, int *phys_pathp)); +void simplify_path ARGS((char *path)); +char *get_phys_path ARGS((const char *path)); +void set_current_wd ARGS((char *path)); +/* syn.c */ +void initkeywords ARGS((void)); +struct op * compile ARGS((Source *s)); +/* table.c */ +unsigned int hash ARGS((const char *n)); +void tinit ARGS((struct table *tp, Area *ap, int tsize)); +struct tbl * tsearch ARGS((struct table *tp, const char *n, unsigned int h)); +struct tbl * tenter ARGS((struct table *tp, const char *n, unsigned int h)); +void tdelete ARGS((struct tbl *p)); +void twalk ARGS((struct tstate *ts, struct table *tp)); +struct tbl * tnext ARGS((struct tstate *ts)); +struct tbl ** tsort ARGS((struct table *tp)); +/* trace.c */ +/* trap.c */ +void inittraps ARGS((void)); +#ifdef KSH +void alarm_init ARGS((void)); +#endif /* KSH */ +Trap * gettrap ARGS((const char *name, int igncase)); +RETSIGTYPE trapsig ARGS((int i)); +void intrcheck ARGS((void)); +int fatal_trap_check ARGS((void)); +int trap_pending ARGS((void)); +void runtraps ARGS((int intr)); +void runtrap ARGS((Trap *p)); +void cleartraps ARGS((void)); +void restoresigs ARGS((void)); +void settrap ARGS((Trap *p, char *s)); +int block_pipe ARGS((void)); +void restore_pipe ARGS((int restore_dfl)); +int setsig ARGS((Trap *p, handler_t f, int flags)); +void setexecsig ARGS((Trap *p, int restore)); +/* tree.c */ +int fptreef ARGS((struct shf *f, int indent, const char *fmt, ...)); +char * snptreef ARGS((char *s, int n, const char *fmt, ...)); +struct op * tcopy ARGS((struct op *t, Area *ap)); +char * wdcopy ARGS((const char *wp, Area *ap)); +char * wdscan ARGS((const char *wp, int c)); +char * wdstrip ARGS((const char *wp)); +void tfree ARGS((struct op *t, Area *ap)); +/* var.c */ +void newblock ARGS((void)); +void popblock ARGS((void)); +void initvar ARGS((void)); +struct tbl * global ARGS((const char *n)); +struct tbl * local ARGS((const char *n, bool_t copy)); +char * str_val ARGS((struct tbl *vp)); +long intval ARGS((struct tbl *vp)); +int setstr ARGS((struct tbl *vq, const char *s, int error_ok)); +struct tbl *setint_v ARGS((struct tbl *vq, struct tbl *vp)); +void setint ARGS((struct tbl *vq, long n)); +int getint ARGS((struct tbl *vp, long *nump)); +struct tbl * typeset ARGS((const char *var, Tflag set, Tflag clr, int field, int base)); +void unset ARGS((struct tbl *vp, int array_ref)); +char * skip_varname ARGS((const char *s, int aok)); +char *skip_wdvarname ARGS((const char *s, int aok)); +int is_wdvarname ARGS((const char *s, int aok)); +int is_wdvarassign ARGS((const char *s)); +char ** makenv ARGS((void)); +void change_random ARGS((void)); +int array_ref_len ARGS((const char *cp)); +char * arrayname ARGS((const char *str)); +void set_array ARGS((const char *var, int reset, char **vals)); +/* version.c */ +/* vi.c: see edit.h */ + + +/* Hack to avoid billions of compile warnings on SunOS 4.1.x */ +#if defined(MUN) && defined(sun) && !defined(__svr4__) +extern void bcopy ARGS((const void *src, void *dst, size_t size)); +extern int fclose ARGS((FILE *fp)); +extern int fprintf ARGS((FILE *fp, const char *fmt, ...)); +extern int fread ARGS((void *buf, int size, int num, FILE *fp)); +extern int ioctl ARGS((int fd, int request, void *arg)); +extern int killpg ARGS((int pgrp, int sig)); +extern int nice ARGS((int n)); +extern int readlink ARGS((const char *path, char *buf, int bufsize)); +extern int setpgrp ARGS((int pid, int pgrp)); +extern int strcasecmp ARGS((const char *s1, const char *s2)); +extern int tolower ARGS((int)); +extern int toupper ARGS((int)); +/* Include files aren't included yet */ +extern int getrlimit ARGS(( /* int resource, struct rlimit *rpl */ )); +extern int getrusage ARGS(( /* int who, struct rusage *rusage */ )); +extern int gettimeofday ARGS(( /* struct timeval *tv, struct timezone *tz */ )); +extern int setrlimit ARGS(( /* int resource, struct rlimit *rlp */ )); +extern int lstat ARGS(( /* const char *path, struct stat *buf */ )); +#endif diff --git a/sh.1tbl b/sh.1tbl new file mode 100644 index 0000000..7a7e8cc --- /dev/null +++ b/sh.1tbl @@ -0,0 +1,3838 @@ +.\" $OpenBSD: sh.1tbl,v 1.31 2003/03/14 11:05:55 jmc Exp $ +.\" +.\" Copyright (c) 1980, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the University of +.\" California, Berkeley and its contributors. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)ksh.1tbl 8.2 (Berkeley) 8/19/96 +.\" +.Dd August 19, 1996 +.Dt KSH 1 +.Os +.Sh NAME +.Nm sh +.Nd public domain Bourne shell +.Sh SYNOPSIS +.Nm sh +.Op Fl +abCefhiklmnprsuvxX +.Op Fl +o Ar option +.Oo [ Fl c Ar command-string [ +.Xo Ar command-name ] No \&| Fl s No \&| +.Ar file No ]\ +.Xc +.Op Ar argument ... Oc +.Sh DESCRIPTION +.Nm +is a reimplementation of the Bourne shell, a command interpreter for both +interactive and script use. +.Ss Shell startup +The following options can be specified only on the command line: +.Bl -tag -width Ds +.It Fl c Ar command-string +.Nm ksh +will execute the command(s) contained in +.Ar command-string . +.It Fl i +Interactive mode; see below. +.It Fl l +Login shell; see below. +.It Fl s +The shell reads commands from standard input; all non-option arguments +are positional parameters. +.It Fl r +Restricted mode; see below. +.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. +.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 name the shell was called with (i.e., argv[0]) is used. +.Pp +A shell is +.Dq interactive +if the +.Fl i +option is used or if both standard input and standard error are attached +to a tty. +An interactive shell has job control enabled (if available), ignores the +.Dv SIGINT , +.Dv SIGQUIT , +and +.Dv SIGTERM +signals, and prints prompts before reading input (see +.Ev PS1 +and +.Ev PS2 +parameters). +For non-interactive shells, the +.Ic trackall +option is on by default (see +.Ic set +command below). +.Pp +A shell is +.Dq restricted +if the +.Fl r +option is used or if either the basename of the name the shell was invoked +with or the +.Ev SHELL +parameter match the pattern +.Dq \&*r\&*sh +(e.g., +.Dq rsh , +.Dq rksh , +.Dq rpdksh , +etc.). +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 > , +.Ql >| , +.Ql >> , +.Ql <> ) . +.El +.Pp +A shell is +.Dq privileged +if the +.Fl p +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). +.Pp +If the basename of the name the shell is called with (i.e., argv[0]) +starts with +.Ql - +or if the +.Fl l +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. +.Pp +If the +.Ev ENV +parameter is set when the 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. +If the +.Ev ENV +parameter is not set (and not +.Dv NULL ) +and +.Nm pdksh +was compiled with the +.Dv DEFAULT_ENV +macro defined, the file named in that macro is included (after the above +mentioned substitutions have been performed). +.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 shells 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 < , +.Ql > , +.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 tokens: +.Ql < , +.Ql <& , +.Ql << , +.Ql > , +.Ql >& , +.Ql >> , +etc. are used to specify redirections (see +.Sx Input/output redirection +below); +.Ql | +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 -- 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 +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 is 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 ! +reversed 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 -- +.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. +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): +.Pp +.TS +center; +lfB lfB lfB lfB lfB . +case else function ! +do esac if until [[ +done fi in while { +elif for time then } +.TE +.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, +.Pp +.Bl -inset -indent -compact +.It Ic { echo foo; echo bar; } +.It Ic { echo foo; echo bar } +.It Ic { { echo foo; echo bar; } } +.El +.Pp +are all valid, but +.Pp +.Bl -inset -indent -compact +.It Ic { echo foo; echo bar } +.El +.Pp +is not. +.Bl -tag -width Ds +.It Ic \&( Ar list Ic \&) +Execute +.Ar list +in a subshell. +There is no implicit way to pass environment changes from a +subshell back to its parent. +.It Ic \&{ Ar list Ic \&} +Compound construct; +.Ar list +is executed, but not in a subshell. +Note that +.Ic \&{ +and +.Ic \&} +are reserved words, not meta-characters. +.It Xo Ic case Ar word Ic in [ +.Ns [ Ic \&( ] Ar pattern [ +.Ns Ic \&| Ar pattern ] ... Ic \&) +.Ar list Ic \&;\&; +.Ns ] Ar ... +.Ic esac +.Xc +The +.Ic case +statement attempts to match +.Ar word +against the specified +.Ar pattern Ns s ; +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 Ic for Ar name No [ +.Ic in Ar word Ar ... term Ns No ] +.Ic do Ar list Ic 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. +.Ar term +is either a newline or a +.Ql \&; . +.It Xo Ic if Ar list Ic then +.Ar list [ Ic elif Ar list Ic then +.Ar list ] Ar ... [ Ic else +.Ar list ] Ic 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 Ic until Ar list Ic do Ar list +.Ic 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 Ic while Ar list Ic do Ar list +.Ic 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 Ic function Ar name Ic \&{ +.Ar list Ic \&} +.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 Ic () Ar command +Mostly the same as +.Ic function +(see +.Sx Functions +below). +.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 sequences of the form +.Ic \&"...`...\e\&"...`..\&" . +.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: +.Pp +.Bl -item -offset indent -compact +.It +.Ic hash='alias -t' +.It +.Ic type='whence -v' +.El +.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 Fl o Ic trackall +or +.Ic set Fl 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: +.Ic cat , cc , chmod , cp , +.Ic date , ed , emacs , grep , +.Ic ls , mail , make , mv , +.Ic pr , rm , sed , sh , +.Ic vi , +and +.Ic who . +.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 +.Ic $ Ns Ar name +or +.Ic ${ Ns Ar ... Ns Ic \&} ; +command substitutions take the form +.Ic $( Ns Ar command Ns Ic \&) +or +.Ic ` Ns Ar command Ns Ic ` ; +and arithmetic substitutions take the form +.Ic $(( Ns Ar expression Ns Ic )) . +.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 no +.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 or trailing +.Pf non- Ev IFS +whitespace does create an empty field. +.Pp +Example: If +.Ev IFS +is set to +.Dq : , +and VAR is set to +.Dq A:B::D , +the substitution for $VAR results in four fields: +.Dq A , +.Dq B , +.Dq , +and +.Dq 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: +.Dq A , +.Dq B , +.Dq , +and +.Dq D:E , +not +.Dq A , +.Dq B , +.Dq , +and +.Dq 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 +.Ic $( Ns Ar command Ns Ic \&) +substitutions, normal quoting rules are used when +.Ar command +is parsed; however, for the +.Ic ` Ns Ar command Ns Ic ` +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 +.Ic \&< Ar file +is interpreted to mean substitute the contents of +.Ar file +(note that +.Ic $(< foo) +has the same effect as +.Ic $(cat foo) , +but it is carried out more efficiently because no process is started). +.Pp +.Sy Note: +.Ic $( Ns Ar command Ns Ic \&) +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 +.Ic $ Ns Ar name +or +.Ic ${ Ns Ar name Ns Ic \&} , +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 +.Ic # , PWD , +etc.; 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 +.Ev 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 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 Fl A +commands. +Lastly, parameters can be assigned values using assignment operators +inside arithmetic expressions (see +.Sx Arithmetic expressions +below) or using the +.Xo Ic ${ Ns Ar name Ns No = +.Ns Ar value Ns Ic \&} +.Xc +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 No = 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 +.Ic ${ Ns Ar name Ns Ic \&} +form of parameter substitution: +.Bl -tag -width Ds +.It Xo Ic ${ Ns Ar name Ns +.Ic \&:\&- Ns Ar word Ns Ic \&} +.Xc +If +.Ar name +is set and not +.Dv NULL , +it is substituted; otherwise, +.Ar word +is substituted. +.It Xo Ic ${ Ns Ar name Ns +.Ic \&:\&+ Ns Ar word Ns Ic \&} +.Xc +If +.Ar name +is set and not +.Dv NULL , +.Ar word +is substituted; otherwise, nothing is substituted. +.It Xo Ic ${ Ns Ar name Ns +.Ic \&:\&= Ns Ar word Ns Ic \&} +.Xc +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. +.It Xo Ic ${ Ns Ar name Ns +.Ic \&:\&? Ns Ar word Ns Ic \&} +.Xc +If +.Ar name +is set and not +.Dv NULL , +it is substituted; otherwise, +.Ar word +is printed on standard error (preceded by +.Ar name Ns No \&: ) +and an error occurs (normally causing termination of a shell script, function +or .-script). +If 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: +.Bl -tag -width Ds +.It Ic ${# Ns Ar name Ns Ic \&} +The number of positional parameters if +.Ar name +is +.Ql * , +.Ql @ , +not specified, or the length of the string value of parameter +.Ar name . +.It Xo Ic ${# Ns Ar name Ns +.Ic [*\&]} , ${# Ns Ar name Ns Ic [@\&]} +.Xc +The number of elements in the array +.Ar name . +.Sm off +.It Xo +.Ic ${ Ar name Ic # Ar pattern +.Sm on +.Ic } , +.Sm off +.Ic ${ Ar name Ic ## Ar pattern Ic \&} +.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. +.Sm off +.It Xo +.Ic ${ Ar name Ic % Ar pattern +.Sm on +.Ic } , +.Sm off +.Ic ${ Ar name Ic %% Ar pattern Ic } +.Xc +.Sm on +Like +.Ic ${..#..} +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 (i.e., $1, $2, etc.). +.It Ev \&$ +The process ID of the shell, or the +.Tn 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 +.Ic set +command below for 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 the shell was invoked with (i.e., +.Ic argv[0] ) , +or the +.Ar command-name +if it was invoked with the +.Fl c +option and the +.Ar command-name +was supplied, or the +.Ar file +argument, if it was supplied. +If the +.Ic posix +option is not set, +.Ic \&$0 +is the name of the current function or script. +.It Ev 1 ... Ev 9 +The first nine positional parameters that were supplied to the shell, function +or .-script. +Further positional parameters may be accessed using +.Ic ${ Ns Ar number Ns Ic \&} . +.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. +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 +.Dq \&. +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 +.Ic set Fl 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 +.Fn 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. +Not yet implemented. +.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 last argument 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 .'d files. +An empty string resulting from a leading or trailing +colon, or two adjacent colons, is treated as a +.Dq \&. , +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 prompt is printed verbatim (i.e., no substitutions are done). +Default is +.Dq \&$\ \& +for non-root users, +.Dq \&#\ \& +for root. +.It Ev PS2 +Secondary prompt string, by default +.Dq \&>\ \& , +used when more input is needed to complete a command. +.It Ev PS4 +Used to prefix commands that are printed during execution tracing (see +.Ic set Fl x +command below). +The prompt is printed verbatim (i.e., no substitutions are done). +Default is +.Dq \&+\ \& . +.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 shell temporary 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 (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 unquoted colon +.Pq Sq \&: , +and 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 ? +or +.Ql * +characters or +.Dq [..] +sequences. +Once brace expansion has been performed, the shell replaces file +name patterns with the sorted named 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 Ic \&? +Matches any single character. +.It Ic \&* +Matches any sequence of characters. +.It Ic \&[ Ns No .. Ns Ic \&] +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 +.Dq 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. +.It Ic \&[\&! Ns No .. Ns Ic \&] +Like +.Ic \&[ Ns No .. Ns Ic \&] , +except it matches any character not inside the brackets. +.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 Xo Ic \&+( Ar pattern Ic \&| No \ ...\ +.Ic \&| Ar pattern Ic \&) +.Xc +.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 Xo Ic \&?( Ar pattern Ic \&| No \ ...\ +.Ic \&| Ar pattern Ic \&) +.Xc +.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 Xo Ic \&@( Ar pattern Ic \&| No \ ...\ +.Ic \&| Ar pattern Ic \&) +.Xc +.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 Xo Ic \&!( Ar pattern Ic \&| No \ ...\ +.Ic \&| Ar pattern Ic \&) +.Xc +.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 +.Dq \&. +and +.Dq \&.\&. , +but the original +.Xr ksh , +Bourne +.Xr sh +and +.Xr 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 +.Ic \&[ Ns No .. Ns Ic \&] +sequence; also, the names +.Dq \&. +and +.Dq \&.\&. +are never matched, even by the pattern +.Dq \&.\&* . +.Pp +If the +.Ic markdirs +option is set, any directories that result from file name generation are marked +with a trailing +.Ql / . +.Pp +The +.Tn POSIX +character classes (i.e., +.Ic \&[\&: Ns Ar class-name Ns Ic \&:\&] +inside a +.Ic \&[ Ns No .. Ns Ic \&] +expression) are not yet implemented. +.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 Ic \&> 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 < foo > 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 Ic \&>\&| Ar file +Same as +.Ic \&> , +except the file is truncated, even if the +.Ic noclobber +option is set. +.It Ic \&>\&> Ar file +Same as +.Ic \&> , +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 +.Fn open 2 ) . +.It Ic \&< Ar file +Standard input is redirected from +.Ar file , +which is opened for reading. +.It Ic \&<\&> Ar file +Same as +.Ic \&< , +except the file is opened for reading and writing. +.It Ic \&<\&< 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 Ic \&<\&<\&- Ar marker +Same as +.Ic \&<\&< , +except leading tabs are stripped from lines in the here document. +.It Ic \&<\&& 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 Ic \&>\&& Ar fd +Same as +.Ic \&<\&& , +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 +.Pp +.Ic cat /foo/bar 2\&>&1 \&> /dev/null \&| cat -n +.Pp +will print an error with a line number prepended to it. +.Ss Arithmetic expressions +Integer arithmetic expressions can be used with the +.Ic let +command, inside +.Ic $(( Ns No .. Ns Ic )) +expressions, inside array references (e.g., +.Sm off +.Ar name Ic \&[ Ar expr Ic \&] ) , +.Sm on +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: +.Bl -item -offset indent -compact +.It +.Ic \&+ \&- \&! \&~ \&+\&+ \&-\&- +.El +.Pp +Binary operators: +.Bl -item -offset indent -compact +.It +.Ic \&, +.It +.Ic = \&*= /= %= \&+= \&-= \&<\&<= +.Ic \&>\&>= \&&= ^= \&|= +.It +.Ic \&|\&| +.It +.Ic \&&\&& +.It +.Ic \&| +.It +.Ic ^ +.It +.Ic \&& +.It +.Ic == \&!= +.It +.Ic \&< \&<= \&>= \&> +.It +.Ic \&<\&< \&>\&> +.It +.Ic \&+ \&- +.It +.Ic \&* / % +.El +.Pp +Ternary operators: +.Bl -item -offset indent -compact +.It +.Ic \&?\&: +(precedence is immediately higher than assignment) +.El +.Pp +Grouping operators: +.Bl -item -offset indent -compact +.It +.Ic \&( \&) +.El +.Pp +Integer constants may be specified with arbitrary bases using the notation +.Ar base Ns Ic \&# Ns Ar number , +where +.Ar base +is a decimal integer specifying the base, and +.Ar number +is a number in the specified base. +.Pp +The operators are evaluated as follows: +.Pp +.Bl -tag -width Ds -offset indent +.It unary Ic \&+ +Result is the argument (included for completeness). +.It unary Ic \&- +Negation. +.It Ic \&! +Logical +.Tn NOT ; +the result is 1 if argument is zero, 0 if not. +.It Ic \&~ +Arithmetic (bit-wise) +.Tn NOT . +.It Ic \&+\&+ +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 Ic \&-\&- +Similar to +.Ic \&+\&+ , +except the parameter is decremented by 1. +.It Ic \&, +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 Ic = +Assignment; variable on the left is set to the value on the right. +.It Xo Ic \&*= /= \&+= \&-= \&<\&<= +.Ic \&>\&>= \&&= ^= \&|= +.Xc +Assignment operators. +.Ao Ar var Ac +.Ao Ar op Ac = +.Ao Ar expr Ac +is the same as +.Ao Ar var Ac = +.Ao Ar var Ac +.Ao Ar op Ac +.Ic \&( +.Ao Ar expr Ac +.Ic \&) . +.It Ic \&|\&| +Logical +.Tn 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 Ic \&&\&& +Logical +.Tn 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 Ic \&| +Arithmetic (bit-wise) +.Tn OR . +.It Ic ^ +Arithmetic (bit-wise) +.Tn XOR +(exclusive-OR). +.It Ic \&& +Arithmetic (bit-wise) +.Tn AND . +.It Ic == +Equal; the result is 1 if both arguments are equal, 0 if not. +.It Ic \&!= +Not equal; the result is 0 if both arguments are equal, 1 if not. +.It Ic \&< +Less than; the result is 1 if the left argument is less than the right, 0 if +not. +.It Ic \&<= \&>= \&> +Less than or equal, greater than or equal, greater than. +See +.Ic \&< . +.It Ic \&<\&< \&>\&> +Shift left (right); the result is the left argument with its bits shifted left +(right) by the amount given in the right argument. +.It Ic \&+ \&- \&* / +Addition, subtraction, multiplication, and division. +.It Ic % +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 Ao Ar arg1 Ac Ic \ \&? +.Ao Ar arg2 Ac Ic \ \&: Ao Ar arg3 Ac +.Xc +If +.Ao Ar arg1 Ac +is non-zero, the result is +.Ao Ar arg2 Ac , +otherwise +.Ao Ar arg3 Ac . +.El +.Ss Functions +Functions are defined using either Korn shell +.Ic function Ar name +syntax or the Bourne/POSIX shell +.Fn name +syntax (see below for the difference between the two forms). +Functions are like +.Li .-scripts +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., $$, $\&!) 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 +.Dv OPTIND +untouched, so using +.Ic getopts +inside a function interferes with using +.Ic getopts +outside the function). +In the future, the following differences will also be added: +.Bl -bullet -offset indent +.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 +.El +.Ss POSIX mode +The shell is intended to be +.Tn POSIX +compliant; however, in some cases, +.Tn 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 Fl o Ic posix . +If it is on, the +.Tn 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 +.Dv POSIXLY_CORRECT +parameter. (The shell can also be compiled so that it is in +.Tn 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 +.Tn POSIX +mode, the +.Ic \e\&" +is interpreted when the command is interpreted; in +.Pf non- Tn POSIX +mode, the +backslash is stripped before the command substitution is interpreted. +For example, +.Ic echo \&"`echo \e\&"hi\e\&"`\&" +produces +.Dq \&"hi\&" +in +.Tn POSIX +mode, +.Dq hi +in +.Pf non- Tn POSIX +mode. +To avoid problems, use the +.Ic $(...) +form of command substitution. +.It +.Ic kill -l +output. +In +.Tn POSIX +mode, signal names are listed one per line; in +.Pf non- Tn POSIX +mode, +signal numbers, names, and descriptions are printed in columns. +In future, a new option +.Po Fl v +\ perhaps +.Pc +will be added to distinguish the two behaviours. +.It +.Ic fg +exit status. +In +.Tn POSIX +mode, the exit status is 0 if no errors occur; in +.Pf non- Tn POSIX +mode, the exit status is that of the last foregrounded job. +.It +.Ic getopts . +In +.Tn POSIX +mode, options must start with a +.Ql - ; +in +.Pf non- Tn POSIX +mode, options can start with either +.Ql - +or +.Ql + . +.It +Brace expansion (also known as alternation). +In +.Tn POSIX +mode, brace expansion is +disabled; in +.Pf non- Tn POSIX +mode, brace expansion is enabled. +Note that +.Ic set Fl o Ic 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 +.Tn POSIX +mode, this does not clear the +.Ic verbose +or +.Ic xtrace +options; in +.Pf non- Tn POSIX +mode, it does. +.It +.Ic set +exit status. +In +.Tn POSIX +mode, the exit status of +.Ic set +is 0 if there are no errors; in +.Pf non- Tn 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 +.Tn POSIX +mode, 1 in +.Pf non- Tn POSIX +mode. +This construct is used in most shell scripts that use the old +.Xr getopt 1 +command. +.It +Argument expansion of +.Ic alias , +.Ic export , +.Ic readonly , +and +.Ic typeset +commands. +In +.Tn POSIX +mode, normal argument expansion is done; in +.Pf non- Tn 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 +.Tn POSIX +mode, signals can be specified as digits, only +if signal numbers match +.Tn POSIX +values (i.e., HUP=1, INT=2, QUIT=3, ABRT=6, +KILL=9, ALRM=14, and TERM=15); in +.Pf non- Tn POSIX +mode, signals can always be digits. +.It +Alias expansion. +In +.Tn POSIX +mode, alias expansion is only carried out when +reading command words; in +.Pf non- Tn 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 +.Pp +.Bl -item -offset indent -compact +.It +.Ic alias a='for ' i='j' +.It +.Ic a i in 1 2; do echo i=$i j=$j; done +.El +.Pp +uses parameter +.Ic i +in +.Tn POSIX +mode, +.Ic j +in +.Pf non- Tn POSIX +mode. +.It +Test. +In +.Tn POSIX +mode, the expression +.Ql Fl t +(preceded by some number of +.Ql Ic \&! +arguments) is always true as it is a non-zero length string; in +.Pf non- Tn POSIX +mode, it tests if file descriptor 1 is a tty (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, and 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 +.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 +.Tn POSIX +differ somewhat in which commands are considered +special or regular: +.Pp +.Tn POSIX +special commands +.Pp +.Ic \&. , \&: , break , continue , +.Ic eval , exec , exit , export , +.Ic readonly , return , set , shift , +.Ic trap , unset +.Pp +Additional ksh special commands +.Pp +.Ic builtin , times , typeset +.Pp +Very special commands +.Pq Pf non- Tn POSIX +.Pp +.Ic alias , readonly , set , typset +.Pp +.Tn POSIX +regular commands +.Pp +.Ic alias , bg , cd , command , +.Ic false , fc , fg , getopts , +.Ic jobs , kill , read , true , +.Ic umask , unalias , wait +.Pp +Additional ksh regular commands +.Pp +.Ic \&[ , echo , let , print , +.Ic pwd , test , ulimit , whence +.Pp +In the future, the additional ksh special and regular commands may be treated +differently from the +.Tn POSIX +special and regular commands. +.Pp +Once the type of the 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: +.Bl -tag -width Ds +.It Ic \&. Ar file Op Ar arg1 ... +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. +.It Ic \&: Op Ar ... +The null command. +Exit status is set to zero. +.It Xo Ic alias +.Op Fl d | Ic +-t Op Fl r +.Op Ic +-px +.Op Ic +- +.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 No = 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. +In addition, if the +.Fl p +option is used, each alias is prefixed with the string +.Dq alias\ \& . +.Pp +The +.Fl x +option sets +.Po Ic \&+x +\ clears +.Pc +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 +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 d +option causes directory aliases, which are used in tilde expansion, to be +listed or set (see +.Sx Tilde expansion +above). +.It Ic bg Op Ar job ... +Resume the specified stopped job(s) in the background. +If no jobs are specified, +.Ic %\&+ +is assumed. +This command is only available on systems which support job control (see +.Sx Job control +below for more information). +.It Xo Ic bind Op Fl m +.Oo Ar key +.Op Ns = Ns Ar editing-command +.Ar ... +.Oc +.Xc +Set or view the current emacs command editing key bindings/macros (see +.Sx Emacs interactive input line editing +below for a complete description). +.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. +.It Ic builtin Ar command Op Ar arg1 ... +Execute the built-in command +.Ar command . +.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 +.Ev OLDPWD +parameter). +If the +.Fl L +option (logical path) is used or if the +.Ic physical +option (see +.Ic set +command below) isn't set, references to +.Dq \&.\&. +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, +.Dq \&.\&. +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. +.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. +.It Xo Ic command Op Fl p +.Ar cmd Op Ar arg1 ... +.Xc +.Ar cmd +is executed exactly as if +.Ic command +had not been specified, with two exceptions. +First, +.Ar cmd +cannot be a shell function, and second, 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). +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 +.Tn POSIX Ns ish +systems, it is the value returned by +.Ic getconf CS_PATH ) . +.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. +.It Xo Ic echo Op Fl neE +.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 +which suppresses backslash interpretation. +.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. +.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 No '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). +.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. +.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. +.It Ic false +A command that exits with a non-zero status. +.It Xo Ic fc +.Oo Fl e No \&- \&| Fl s Oc +.Op Fl g +.Op Ar old Ns No = 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 . +This command is usually accessed with the predefined +.Ic alias r='fc -e -' . +.It Ic fg Op Ar job ... +Resume the specified job(s) in the foreground. +If no jobs are specified, +.Ic %\&+ +is assumed. +This command is only available on systems which support job control (see +.Sx Job control +below for more information). +.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 next argument to be processed 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 . +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. +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. +.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. +.It Xo Ic jobs Op Fl lpn +.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. +.It Xo Ic kill +.Oo Fl s Ar signame No \&| +.Fl signum No \&| Fl signame Oc { +.Ar job No \&| +.Ar pid No \&| +.Ar 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 . +.It Ic kill -l Op Ar exit-status ... +Print the name of the signal that killed a process which exited with the +specified +.Ar exit-status Ns es. +If no arguments are specified, a list of all the signals, their numbers and +a short description of them are printed. +.It Xo Ic print +.Oo Fl nprsu Ns Ar n No \&| +.Fl R No 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 +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. +.It Ic pwd Op Fl LP +Print the present working directory. +If the +.Fl L +option is used or if the +.Ic physical +option (see +.Ic set +command below) isn't set, 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 +.Dq \&.\&. +directories to the root directory) is printed. +.It Xo Ic read Oo Fl prsu Ns Ar n +.Oc 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 tty (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 +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. +.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. +.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. +.It Xo Ic set Op Ic +-abCefhkmnpsuvxX +.Op Ic +-o Ar option +.Op Ic +-A Ar name +.Op Fl \&- +.Op Ar arg ... +.Xc +The 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 +.Ic \&+ Ns Fl o Ar option +syntax, where +.Ar option +is the long name of an option, or using the +.Ic \&+\&- 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 +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 +.Ar arg Ns s ) , +the rest are left untouched. +.It Fl a Ic allexport +All new parameters are created with the export attribute. +.It Fl b 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 Ic noclobber +Prevent +.Ic \&> +redirection from overwriting existing files +.Po +.Ic \&>\&| +must be used to force an overwrite +.Pc . +.It Fl e 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 , +.Ic \&&\&& , +or +.Ic \&|\&| +statements. +.It Fl f Ic noglob +Do not expand file name patterns. +.It Fl h Ic trackall +Create tracked aliases for all executed commands (see +.Sx Aliases +above). +Enabled by default for non-interactive shells. +.It Fl i Ic interactive +Enable interactive mode. +This can only be set/unset when the shell is invoked. +.It Fl k Ic keyword +Parameter assignments are recognized anywhere in a command. +.It Fl l Ic login +The shell is a login shell. +This can only be set/unset when the shell is invoked (see +.Sx Shell startup +above). +.It Fl m Ic monitor +Enable job control (default for interactive shells). +.It Fl n lc noexec +Do not execute any commands. +Useful for checking the syntax of scripts +(ignored if interactive). +.It Fl p Ic privileged +Set automatically if, when the shell starts, the read UID or GID does not match +the effective UID (EUID) or GID (EGID), respectively. +See +.Sx Shell startup +above for a description of what this means. +.It Fl r Ic restricted +Enable restricted mode. +This option can only be used when the shell is invoked. +See +.Sx Shell startup +above for a description of what this means. +.It Fl s Ic stdin +If used where 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 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 Ic verbose +Write shell input to standard error as it is read. +.It Fl x Ic xtrace +Print commands and parameter assignments when they are executed, preceded by +the value of +.Ev PS4 . +.It Fl X Ic markdirs +Mark directories with a trailing +.Ql / +during file name generation. +.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 nohup +Do not kill running jobs with a +.Dv SIGHUP +signal when a login shell exists. +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) +.Dq \&.\&. +directories instead of +.Dq logical +directories (i.e., the shell handles +.Dq \&.\&. , +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 +.Tn POSIX +mode. +See +.Sx POSIX mode +above. +.It Ic vi +Enable vi-like command-line editing (interactive shells only). +.It Ic viraw +No effect. +In the original Korn shell, unless +.Ic viraw +was set, the vi command-line mode would let the tty driver do the work until +.Tn ESC +(^[) was entered. +.Nm pdksh +is always in viraw mode. +.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 +.Dq 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. +.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 +.Dv \&- . +.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. +.It Ic shift Op Ar number +The positional parameters +.Ar number Ns +1 , +.Ar number Ns +2 , +etc. are renamed to +.Dq 1 , +.Dq 2 , +etc. +.Ar number +defaults to 1. +.It Ic test Ar expression +.It Ic \&[ Ar expression Ic \&] +.Ic test +evaluates the +.Ar expression +and returns zero status if true, 1 status 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. +The following basic expressions are available: +.Bl -tag -width 17n +.It Ar str +.Ar str +has non-zero length. +Note that there is the potential for problems if +.Ar str +turns out to be an operator (e.g., +.Fl r ) . +It is generally better to use a test like +.Sm off +.Ic \&[\ X\&" Ar str Ic \&" Ic \ \&] +.Sm on +instead (double quotes are used in case +.Ar str +contains spaces or file globbing characters). +.It Fl r Ar file +.Ar file +exists and is readable. +.It Fl w Ar file +.Ar file +exists and is writable. +.It Fl x Ar file +.Ar file +exists and is executable. +.It Fl a Ar file +.Ar file +exists. +.It Fl e Ar file +.Ar file +exists. +.It Fl f Ar file +.Ar file +is a regular file. +.It Fl d Ar file +.Ar file +is a directory. +.It Fl c Ar file +.Ar file +is a character special device. +.It Fl b Ar file +.Ar file +is a block special device. +.It Fl p Ar file +.Ar file +is a named pipe. +.It Fl u Ar file +.Ar file Ns 's +mode has setuid bit set. +.It Fl g Ar file +.Ar file Ns 's +mode has setgid bit set. +.It Fl k Ar file +.Ar file Ns 's +mode has sticky bit set. +.It Fl s Ar file +.Ar file +is not empty. +.It Fl O Ar file +.Ar file Ns 's +owner is the shell's effective user ID. +.It Fl G Ar file +.Ar file Ns 's +group is the shell's effective group ID. +.It Fl h Ar file +.Ar file +is a symbolic link. +.It Fl H Ar file +.Ar file +is a context dependent directory (only useful on HP-UX). +.It Fl L Ar file +.Ar file +is a symbolic link. +.It Fl S Ar file +.Ar file +is a socket. +.It Fl o Ar option +Shell +.Ar option +is set (see +.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 (thus +.Ic \&[ -o Ar foo +.Ic -o -o \&! Ns Ar foo Ic \&] +returns true if and only if option +.Ar foo +exists). +.It Ar file Fl nt Ar file +first +.Ar file +is newer than second +.Ar file . +.It Ar file Fl ot Ar file +first +.Ar file +is older than second +.Ar file . +.It Ar file Fl ef Ar file +first +.Ar file +is the same file as second +.Ar file . +.It Fl t Op Ar fd +File descriptor +.Ar fd +is a tty 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 +.Tn POSIX +rules described below). +.It Ar string +.Ar string +is not empty. +.It Fl z Ar string +.Ar string +is empty. +.It Fl n Ar string +.Ar string +is not 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): +.Pp +.Bl -tag -width "expr -o expr" -compact +.It Ar expr Fl o Ar expr +Logical +.Tn OR . +.It Ar expr Fl a Ar expr +Logical +.Tn AND . +.It Ic \&! Ar expr +Logical +.Tn NOT . +.It Ic \&( Ar expr Ic \&) +Grouping. +.El +.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, +.Ic \&[ -w /dev/fd/2 \&] +tests if file descriptor 2 is writable. +.Pp +Note that some special rules are applied (courtesy of +.Tn 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 +.Ic if \&[ $foo = bar \&] +which fails if parameter +.Ic foo +is +.Dv NULL +or unset, if it has embedded spaces (i.e., +.Ev IFS +characters), or if it is a unary operator like +.Ql Ic \&! +or +.Ql Fl n . +Use tests like +.Ic if \&[ \&"X$foo\&" = Xbar \&] +instead. +.It Ic times +Print the accumulated user and system times used by the shell and by processes +which have exited that the shell started. +.It Ic trap Op Ar handler signal ... +Sets 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 +.Ic kill -l +command above). +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 see -- see +.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. +.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. +.It Ic true +A command that exits with a zero value. +.It Xo Ic typeset +.Oo Op Ic +-Ulprtux +.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 \&| 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 Ns s +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 3n +.It Fl L Ns 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 R Ns 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 Z Ns 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. +.It Fl i Ns 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 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 f +Function mode. +Display or set functions and their attributes, instead of parameters. +.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) or +parameters. +This is the default action (option exists for ksh93 compatibility). +.It Fl r +Read-only attribute. +Parameters with this attribute may not be assigned to or unset. +Once this attribute is set, it can not 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 +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. +.El +.It Xo Ic ulimit Op Fl acdfHlmnpsStv +.Op Ar value +.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 or the word +.Dq unlimited . +The limits affect the shell and any processes created by the shell after a +limit is imposed. +Note that some systems may not allow limits to be increased +once they are set. +Also note that the types of limits available are system +dependent -- some systems have only the +.Fl f +limit. +.Bl -tag -width 5n +.It Fl a +Displays all limits; unless +.Fl H +is used, soft limits are displayed. +.It Fl H +Set the hard limit only (default is to set both hard and soft limits). +.It Fl S +Set the soft limit only (default is to set both hard and soft limits). +.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 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 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 +.Tn CPU +seconds to be used by each process. +.El +.Pp +As far as +.Ic ulimit +is concerned, a block is 512 bytes. +.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 . +.It Xo Ic unalias Op Fl adt +.Op Ar name1 ... +.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. +.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 were already unset, zero +otherwise. +.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). +.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 Fl m +or +.Ic set Fl o Ic 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 +.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 $! 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 10n +.It Ic %\&+ +The most recently stopped job, or, if there are no stopped jobs, the oldest +running job. +.It Ic %% , % +Same as +.Ic %\&+ . +.It Ic %\&- +The job that would be the +.Ic %\&+ +job if the latter did not exist. +.It Ic % Ns Ar n +The job with job number +.Ar n . +.It Ic %\&? Ns Ar string +The job containing the string +.Ar string +(an error occurs if multiple jobs are matched). +.It Ic % Ns Ar string +The job starting with 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 +.Ic \&[ Ar number Ic \&] Ar flag status command +.Pp +where +.Bl -tag -width "status" +.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 "Running" +.It Cm Running +The job has neither stopped nor exited (note that running does not necessarily +mean consuming +.Tn CPU +time -- the process could be blocked waiting for some +event). +.It Cm 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 Cm 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, etc.; 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_profile" -compact +.It Pa ~/.profile +.It Pa /etc/profile +.It Pa /etc/suid_profile +.El +.Sh SEE ALSO +.Xr awk 1 , +.Xr csh 1 , +.Xr ed 1 , +.Xr getconf 1 , +.Xr getopt 1 , +.Xr ksh 1 , +.Xr sed 1 , +.Xr stty 1 , +.Xr vi 1 , +.Xr dup 2 , +.Xr execve 2 , +.Xr getgid 2 , +.Xr getuid 2 , +.Xr open 2 , +.Xr pipe 2 , +.Xr wait 2 , +.Xr getopt 3 , +.Xr rand 3 , +.Xr signal 3 , +.Xr system 3 , +.Xr environ 7 +.Pp +.Rs +.%A Morris Bolsky +.%A David Korn +.%T "The KornShell Command and Programming Language" +.%D 1983 +.%O "ISBN 0-13-516972-0" +.Re +.Rs +.%A Stephen G. Kochan +.%A Patrick H. Wood +.%T "UNIX Shell Programming" +.%O "Hayden" +.Re +.Rs +.%A "IEEE Inc." +.%T "IEEE Standard for Information Technology - Portable Operating System Interface (POSIX) - Part 2: Shell and Utilities" +.%D 1993 +.%O "ISBN 1-55937-266-9" +.Re +.Sh NOTES +.Nm +is implemented as a run-time option of +.Nm pdksh , +with only those +.Nm ksh +features whose syntax or semantics are incompatible with a traditional Bourne +shell disabled. +Since this leaves some +.Nm ksh +extensions exposed, caution should be used where backwards compatibility with +traditional Bourne or +.Tn POSIX +compliant shells is an issue. +.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. +.Sh AUTHORS +This shell is based on the public domain 7th edition Bourne shell clone by +Charles Forsyth and parts of the BRL shell by Doug A. Gwyn, Doug Kingston, +Ron Natalie, Arnold Robbins, Lou Salkind, and others. +The first release of +.Nm pdksh +was created by Eric Gisin, and it was subsequently maintained by John R. +MacMillan (change!john@sq.sq.com) and Simon J. Gerraty (sjg@zen.void.oz.au). +The current maintainer is Michael Rendell (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. diff --git a/sh.h b/sh.h new file mode 100644 index 0000000..4e8c4ac --- /dev/null +++ b/sh.h @@ -0,0 +1,747 @@ +/* $OpenBSD: sh.h,v 1.12 2002/10/07 23:09:32 vincent 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 */ + +#ifdef HAVE_PROTOTYPES +# define ARGS(args) args /* prototype declaration */ +#else +# define ARGS(args) () /* K&R declaration */ +#endif + + +/* Start of common headers */ + +#include +#include +#include +#ifdef HAVE_STDDEF_H +# include +#endif + +#ifdef HAVE_STDLIB_H +# include +#else +/* just a useful subset of what stdlib.h would have */ +extern char * getenv ARGS((const char *)); +extern void * malloc ARGS((size_t)); +extern void * realloc ARGS((void *, size_t)); +extern int free ARGS((void *)); +extern int exit ARGS((int)); +extern int rand ARGS((void)); +extern void srand ARGS((unsigned int)); +extern int atoi ARGS((const char *)); +#endif /* HAVE_STDLIB_H */ + +#ifdef HAVE_UNISTD_H +# include +#else +/* just a useful subset of what unistd.h would have */ +extern int access ARGS((const char *, int)); +extern int open ARGS((const char *, int, ...)); +extern int creat ARGS((const char *, mode_t)); +extern int read ARGS((int, char *, unsigned)); +extern int write ARGS((int, const char *, unsigned)); +extern off_t lseek ARGS((int, off_t, int)); +extern int close ARGS((int)); +extern int pipe ARGS((int [])); +extern int dup2 ARGS((int, int)); +extern int unlink ARGS((const char *)); +extern int fork ARGS((void)); +extern int execve ARGS((const char *, char * const[], char * const[])); +extern int chdir ARGS((const char *)); +extern int kill ARGS((pid_t, int)); +extern char *getcwd(); /* no ARGS here - differs on different machines */ +extern int geteuid ARGS((void)); +extern int readlink ARGS((const char *, char *, int)); +extern int getegid ARGS((void)); +extern int getpid ARGS((void)); +extern int getppid ARGS((void)); +extern unsigned int sleep ARGS((unsigned int)); +extern int isatty ARGS((int)); +# ifdef POSIX_PGRP +extern int getpgrp ARGS((void)); +extern int setpgid ARGS((pid_t, pid_t)); +# endif /* POSIX_PGRP */ +# ifdef BSD_PGRP +extern int getpgrp ARGS((pid_t)); +extern int setpgrp ARGS((pid_t, pid_t)); +# endif /* BSD_PGRP */ +# ifdef SYSV_PGRP +extern int getpgrp ARGS((void)); +extern int setpgrp ARGS((void)); +# endif /* SYSV_PGRP */ +#endif /* HAVE_UNISTD_H */ + +#ifdef HAVE_STRING_H +# include +#else +# include +# define strchr index +# define strrchr rindex +#endif /* HAVE_STRING_H */ +#ifndef HAVE_STRSTR +char *strstr ARGS((const char *s, const char *p)); +#endif /* HAVE_STRSTR */ +#ifndef HAVE_STRCASECMP +int strcasecmp ARGS((const char *s1, const char *s2)); +int strncasecmp ARGS((const char *s1, const char *s2, int n)); +#endif /* HAVE_STRCASECMP */ + +#ifdef HAVE_MEMORY_H +# include +#endif +#ifndef HAVE_MEMSET +# define memcpy(d, s, n) bcopy(s, d, n) +# define memcmp(s1, s2, n) bcmp(s1, s2, n) +void *memset ARGS((void *d, int c, size_t n)); +#endif /* HAVE_MEMSET */ +#ifndef HAVE_MEMMOVE +# ifdef HAVE_BCOPY +# define memmove(d, s, n) bcopy(s, d, n) +# else +void *memmove ARGS((void *d, const void *s, size_t n)); +# endif +#endif /* HAVE_MEMMOVE */ + +#ifdef HAVE_PROTOTYPES +# include +# define SH_VA_START(va, argn) va_start(va, argn) +#else +# include +# define SH_VA_START(va, argn) va_start(va) +#endif /* HAVE_PROTOTYPES */ + +#include +extern int errno; + +#ifdef HAVE_FCNTL_H +# include +#else +# include +#endif /* HAVE_FCNTL_H */ +#ifndef O_ACCMODE +# define O_ACCMODE (O_RDONLY|O_WRONLY|O_RDWR) +#endif /* !O_ACCMODE */ + +#ifndef F_OK /* access() arguments */ +# define F_OK 0 +# define X_OK 1 +# define W_OK 2 +# define R_OK 4 +#endif /* !F_OK */ + +#ifndef SEEK_SET +# ifdef L_SET +# define SEEK_SET L_SET +# define SEEK_CUR L_INCR +# define SEEK_END L_XTND +# else /* L_SET */ +# define SEEK_SET 0 +# define SEEK_CUR 1 +# define SEEK_END 2 +# endif /* L_SET */ +#endif /* !SEEK_SET */ + +/* Some machines (eg, FreeBSD 1.1.5) define CLK_TCK in limits.h + * (ksh_limval.h assumes limits has been included, if available) + */ +#ifdef HAVE_LIMITS_H +# include +#endif /* HAVE_LIMITS_H */ + +#include +#ifdef NSIG +# define SIGNALS NSIG +#else +# ifdef _MINIX +# define SIGNALS (_NSIG+1) /* _NSIG is # of signals used, excluding 0. */ +# else +# ifdef _SIGMAX /* QNX */ +# define SIGNALS _SIGMAX +# else /* _SIGMAX */ +# define SIGNALS 32 +# endif /* _SIGMAX */ +# endif /* _MINIX */ +#endif /* NSIG */ +#ifndef SIGCHLD +# define SIGCHLD SIGCLD +#endif +/* struct sigaction.sa_flags is set to KSH_SA_FLAGS. Used to ensure + * system calls are interrupted + */ +#ifdef SA_INTERRUPT +# define KSH_SA_FLAGS SA_INTERRUPT +#else /* SA_INTERRUPT */ +# define KSH_SA_FLAGS 0 +#endif /* SA_INTERRUPT */ + +typedef RETSIGTYPE (*handler_t) ARGS((int)); /* signal handler */ + +#ifdef USE_FAKE_SIGACT +# include "sigact.h" /* use sjg's fake sigaction() */ +#endif + +#ifdef HAVE_PATHS_H +# include +#endif /* HAVE_PATHS_H */ +#ifdef _PATH_DEFPATH +# define DEFAULT__PATH _PATH_DEFPATH +#else /* _PATH_DEFPATH */ +# define DEFAULT__PATH DEFAULT_PATH +#endif /* _PATH_DEFPATH */ + +#ifndef offsetof +# define offsetof(type,id) ((size_t)&((type*)NULL)->id) +#endif + +#ifndef HAVE_KILLPG +# define killpg(p, s) kill(-(p), (s)) +#endif /* !HAVE_KILLPG */ + +/* Special cases for execve(2) */ +#ifdef OS2 +extern int ksh_execve(char *cmd, char **args, char **env, int flags); +#else /* OS2 */ +# if defined(OS_ISC) && defined(_POSIX_SOURCE) +/* Kludge for ISC 3.2 (and other versions?) so programs will run correctly. */ +# define ksh_execve(p, av, ev, flags) \ + do { \ + __setostype(0); \ + execve(p, av, ev); \ + __setostype(1); \ + } while (0) +# else /* OS_ISC && _POSIX */ +# define ksh_execve(p, av, ev, flags) execve(p, av, ev) +# endif /* OS_ISC && _POSIX */ +#endif /* OS2 */ + +/* this is a hang-over from older versions of the os2 port */ +#define ksh_dupbase(fd, base) fcntl(fd, F_DUPFD, base) + +#ifdef HAVE_SIGSETJMP +# define ksh_sigsetjmp(env,sm) sigsetjmp((env), (sm)) +# define ksh_siglongjmp(env,v) siglongjmp((env), (v)) +# define ksh_jmp_buf sigjmp_buf +#else /* HAVE_SIGSETJMP */ +# ifdef HAVE__SETJMP +# define ksh_sigsetjmp(env,sm) _setjmp(env) +# define ksh_siglongjmp(env,v) _longjmp((env), (v)) +# else /* HAVE__SETJMP */ +# define ksh_sigsetjmp(env,sm) setjmp(env) +# define ksh_siglongjmp(env,v) longjmp((env), (v)) +# endif /* HAVE__SETJMP */ +# define ksh_jmp_buf jmp_buf +#endif /* HAVE_SIGSETJMP */ + +#ifndef HAVE_DUP2 +extern int dup2 ARGS((int, int)); +#endif /* !HAVE_DUP2 */ + +/* 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. + */ +#if SIZEOF_INT >= 4 +# define INT32 int +#else /* SIZEOF_INT */ +# if SIZEOF_LONG >= 4 +# define INT32 long +# else /* SIZEOF_LONG */ + #error cannot find 32 bit type... +# endif /* SIZEOF_LONG */ +#endif /* SIZEOF_INT */ + +/* end of common headers */ + +/* Stop gcc and lint from complaining about possibly uninitialized variables */ +#if defined(__GNUC__) || defined(lint) +# define UNINITIALIZED(var) var = 0 +#else +# define UNINITIALIZED(var) var +#endif /* GNUC || lint */ + +/* some useful #defines */ +#ifdef EXTERN +# define I__(i) = i +#else +# define I__(i) +# define EXTERN extern +# define EXTERN_DEFINED +#endif + +#ifdef OS2 +# define inDOS() (!(_emx_env & 0x200)) +#endif + +#ifndef EXECSHELL +/* shell to exec scripts (see also $SHELL initialization in main.c) */ +# ifdef OS2 +# define EXECSHELL (inDOS() ? "c:\\command.com" : "c:\\os2\\cmd.exe") +# define EXECSHELL_STR (inDOS() ? "COMSPEC" : "OS2_SHELL") +# else /* OS2 */ +# define EXECSHELL "/bin/sh" +# define EXECSHELL_STR "EXECSHELL" +# endif /* OS2 */ +#endif + +/* ISABSPATH() means path is fully and completely specified, + * ISROOTEDPATH() means a .. as the first component is a no-op, + * ISRELPATH() means $PWD can be tacked on to get an absolute path. + * + * OS Path ISABSPATH ISROOTEDPATH ISRELPATH + * unix /foo yes yes no + * unix foo no no yes + * unix ../foo no no yes + * os2+cyg a:/foo yes yes no + * os2+cyg a:foo no no no + * os2+cyg /foo no yes no + * os2+cyg foo no no yes + * os2+cyg ../foo no no yes + * cyg //foo yes yes no + */ +#ifdef OS2 +# define PATHSEP ';' +# define DIRSEP '/' /* even though \ is native */ +# define DIRSEPSTR "\\" +# define ISDIRSEP(c) ((c) == '\\' || (c) == '/') +# define ISABSPATH(s) (((s)[0] && (s)[1] == ':' && ISDIRSEP((s)[2]))) +# define ISROOTEDPATH(s) (ISDIRSEP((s)[0]) || ISABSPATH(s)) +# define ISRELPATH(s) (!(s)[0] || ((s)[1] != ':' && !ISDIRSEP((s)[0]))) +# define FILECHCONV(c) (isascii(c) && isupper(c) ? tolower(c) : c) +# define FILECMP(s1, s2) stricmp(s1, s2) +# define FILENCMP(s1, s2, n) strnicmp(s1, s2, n) +extern char *ksh_strchr_dirsep(const char *path); +extern char *ksh_strrchr_dirsep(const char *path); +# define chdir _chdir2 +# define getcwd _getcwd2 +#else +# define PATHSEP ':' +# define DIRSEP '/' +# define DIRSEPSTR "/" +# define ISDIRSEP(c) ((c) == '/') +#ifdef __CYGWIN__ +# define ISABSPATH(s) \ + (((s)[0] && (s)[1] == ':' && ISDIRSEP((s)[2])) || ISDIRSEP((s)[0])) +# define ISRELPATH(s) (!(s)[0] || ((s)[1] != ':' && !ISDIRSEP((s)[0]))) +#else /* __CYGWIN__ */ +# define ISABSPATH(s) ISDIRSEP((s)[0]) +# define ISRELPATH(s) (!ISABSPATH(s)) +#endif /* __CYGWIN__ */ +# define ISROOTEDPATH(s) ISABSPATH(s) +# define FILECHCONV(c) c +# define FILECMP(s1, s2) strcmp(s1, s2) +# define FILENCMP(s1, s2, n) strncmp(s1, s2, n) +# define ksh_strchr_dirsep(p) strchr(p, DIRSEP) +# define ksh_strrchr_dirsep(p) strrchr(p, DIRSEP) +#endif + +typedef int bool_t; +#define FALSE 0 +#define TRUE 1 + +#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 */ + +/* you're not going to run setuid shell scripts, are you? */ +#define eaccess(path, mode) access(path, mode) + +/* 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 1024 /* input line size */ +#define PATH 1024 /* pathname size (todo: PATH_MAX/pathconf()) */ +#define ARRAYMAX 1023 /* max array index */ + +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 */ + + +/* + * 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 MEM_DEBUG +# include "chmem.h" /* a debugging front end for malloc et. al. */ +#endif /* MEM_DEBUG */ + +#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 */ + ksh_jmp_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 ksh_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 */ +#ifdef EMACS + FEMACS, /* emacs command editing */ +#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 anywere */ + 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 behavour */ + 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"); +EXTERN char slash [] I__("/"); + +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 */ + int volatile set; /* trap pending */ + int flags; /* TF_* */ + handler_t cursig; /* current handler (valid if TF_ORIG_* set) */ + handler_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_ SIGNALS /* for trap ERR */ + +EXTERN int volatile trap; /* traps pending? */ +EXTERN int volatile intrsig; /* pending trap interrupts executing command */ +EXTERN int volatile fatal_trap;/* received a fatal signal */ +#ifndef FROM_TRAP_C +/* Kludge to avoid bogus re-declaration of sigtraps[] error on AIX 3.2.5 */ +extern Trap sigtraps[SIGNALS+1]; +#endif /* !FROM_TRAP_C */ + + +#ifdef KSH +/* + * 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); +#endif /* KSH */ + + +/* 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 */ + + +#ifdef KSH +/* 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; +#endif /* KSH */ + +/* Used in jobs.c and by coprocess stuff in exec.c */ +#ifdef JOB_SIGS +EXTERN sigset_t sm_default, sm_sigchld; +#endif /* JOB_SIGS */ + +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 +/* Minimium 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 +/* Minimium 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 */ +#ifndef KSH_SYSTEM_PROFILE +# ifdef __NeXT +# define KSH_SYSTEM_PROFILE "/etc/profile.std" +# else /* __NeXT */ +# define KSH_SYSTEM_PROFILE "/etc/profile" +# endif /* __NeXT */ +#endif /* KSH_SYSTEM_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 new file mode 100644 index 0000000..2947858 --- /dev/null +++ b/shf.c @@ -0,0 +1,1295 @@ +/* $OpenBSD: shf.c,v 1.8 2003/02/28 09:45:09 jmc Exp $ */ + +/* + * Shell file I/O routines + */ + +#include "sh.h" +#include "ksh_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 ARGS((struct shf *shf)); +static int shf_emptybuf ARGS((struct shf *shf, int flags)); + +/* 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(name, oflags, mode, sflags) + 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 = ksh_dupbase(fd, 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(fd, sflags, shf) + 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) + fd_clexec(fd); + return shf; +} + +/* Set up an existing shf (and buffer) to use the given fd */ +struct shf * +shf_reopen(fd, sflags, shf) + 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) + fd_clexec(fd); + 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(buf, bsize, sflags, shf) + 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(shf) + 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(shf) + 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(shf) + 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; +} + +/* Flush and free file structure, don't close file descriptor */ +int +shf_finish(shf) + struct shf *shf; +{ + int ret = 0; + + if (shf->fd >= 0) + ret = shf_flush(shf); + if (shf->flags & SHF_ALLOCS) + afree(shf, shf->areap); + else if (shf->flags & SHF_ALLOCB) + afree(shf->buf, shf->areap); + + return ret; +} + +/* 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(shf) + 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, 1); + 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(shf, flags) + 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(shf) + 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; +} + +/* Seek to a new position in the file. If writing, flushes the buffer + * first. If reading, optimizes small relative seeks that stay inside the + * buffer. Returns 0 for success, EOF otherwise. + */ +int +shf_seek(shf, where, from) + struct shf *shf; + off_t where; + int from; +{ + if (shf->fd < 0) { + errno = EINVAL; + return EOF; + } + + if (shf->flags & SHF_ERROR) { + errno = shf->errno_; + return EOF; + } + + if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF) + return EOF; + + if (shf->flags & SHF_READING) { + if (from == SEEK_CUR && + (where < 0 ? + -where >= shf->rbsize - shf->rnleft : + where < shf->rnleft)) { + shf->rnleft -= where; + shf->rp += where; + return 0; + } + shf->rnleft = 0; + shf->rp = shf->buf; + } + + shf->flags &= ~(SHF_EOF | SHF_READING | SHF_WRITING); + + if (lseek(shf->fd, where, from) < 0) { + shf->errno_ = errno; + shf->flags |= SHF_ERROR; + return 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(buf, bsize, shf) + 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(buf, bsize, shf) + 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; +#ifdef OS2 + if (end && buf > orig_buf + 1 && buf[-2] == '\r') { + buf--; + bsize++; + buf[-1] = '\n'; + } +#endif + + } while (!end && bsize); + *buf = '\0'; + return buf; +} + +/* Returns the char read. Returns EOF for error and end of file. */ +int +shf_getchar(shf) + 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(c, shf) + 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(c, shf) + 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(s, shf) + 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(buf, nbytes, shf) + 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 +#ifdef HAVE_PROTOTYPES +shf_fprintf(struct shf *shf, const char *fmt, ...) +#else +shf_fprintf(shf, fmt, va_alist) + struct shf *shf; + const char *fmt; + va_dcl +#endif +{ + va_list args; + int n; + + SH_VA_START(args, fmt); + n = shf_vfprintf(shf, fmt, args); + va_end(args); + + return n; +} + +int +#ifdef HAVE_PROTOTYPES +shf_snprintf(char *buf, int bsize, const char *fmt, ...) +#else +shf_snprintf(buf, bsize, fmt, va_alist) + char *buf; + int bsize; + const char *fmt; + va_dcl +#endif +{ + 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); + SH_VA_START(args, fmt); + n = shf_vfprintf(&shf, fmt, args); + va_end(args); + shf_sclose(&shf); /* null terminates */ + return n; +} + +char * +#ifdef HAVE_PROTOTYPES +shf_smprintf(const char *fmt, ...) +#else +shf_smprintf(fmt, va_alist) + char *fmt; + va_dcl +#endif +{ + struct shf shf; + va_list args; + + shf_sopen((char *) 0, 0, SHF_WR|SHF_DYNAMIC, &shf); + SH_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_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_ZERO 0x040 /* `0' seen */ +#define FL_DOT 0x080 /* '.' seen */ +#define FL_UPPER 0x100 /* format character was uppercase */ +#define FL_NUMBER 0x200 /* a number was formated %[douxefg] */ + + +#ifdef FP +#include + +static double +my_ceil(d) + double d; +{ + double i; + + return d - modf(d, &i) + (d < 0 ? -1 : 1); +} +#endif /* FP */ + +int +shf_vfprintf(shf, fmt, args) + struct shf *shf; + const char *fmt; + va_list args; +{ + char c, *s; + int UNINITIALIZED(tmp); + int field, precision; + int len; + int flags; + unsigned long lnum; + /* %#o produces the longest output */ + char numbuf[(BITS(long) + 2) / 3 + 1]; + /* this stuff for dealing with the buffer */ + int nwritten = 0; +#ifdef FP + /* should be in + * 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': + 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_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)]; + lnum = POP_INT(flags, c == 'd', args); + switch (c) { + case 'd': + case 'i': + if (0 > (long) lnum) + lnum = - (long) lnum, tmp = 1; + else + tmp = 0; + /* aaahhhh..... */ + + case 'u': + do { + *--s = lnum % 10 + '0'; + lnum /= 10; + } while (lnum); + + if (c != 'u') { + if (tmp) + *--s = '-'; + else if (flags & FL_PLUS) + *--s = '+'; + else if (flags & FL_BLANK) + *--s = ' '; + } + break; + + case 'o': + do { + *--s = (lnum & 0x7) + '0'; + lnum >>= 3; + } while (lnum); + + if ((flags & FL_HASH) && *s != '0') + *--s = '0'; + break; + + case 'p': + case 'x': + { + const char *digits = (flags & FL_UPPER) ? + "0123456789ABCDEF" + : "0123456789abcdef"; + do { + *--s = digits[lnum & 0xf]; + lnum >>= 4; + } while (lnum); + + 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_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 new file mode 100644 index 0000000..b4be5a7 --- /dev/null +++ b/shf.h @@ -0,0 +1,86 @@ +/* $OpenBSD: shf.h,v 1.2 1999/01/08 20:25:02 millert 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 ARGS((const char *name, int oflags, int mode, + int sflags)); +struct shf *shf_fdopen ARGS((int fd, int sflags, struct shf *shf)); +struct shf *shf_reopen ARGS((int fd, int sflags, struct shf *shf)); +struct shf *shf_sopen ARGS((char *buf, int bsize, int sflags, + struct shf *shf)); +int shf_close ARGS((struct shf *shf)); +int shf_fdclose ARGS((struct shf *shf)); +char *shf_sclose ARGS((struct shf *shf)); +int shf_finish ARGS((struct shf *shf)); +int shf_flush ARGS((struct shf *shf)); +int shf_seek ARGS((struct shf *shf, off_t where, int from)); +int shf_read ARGS((char *buf, int bsize, struct shf *shf)); +char *shf_getse ARGS((char *buf, int bsize, struct shf *shf)); +int shf_getchar ARGS((struct shf *shf)); +int shf_ungetc ARGS((int c, struct shf *shf)); +int shf_putchar ARGS((int c, struct shf *shf)); +int shf_puts ARGS((const char *s, struct shf *shf)); +int shf_write ARGS((const char *buf, int nbytes, struct shf *shf)); +int shf_fprintf ARGS((struct shf *shf, const char *fmt, ...)); +int shf_snprintf ARGS((char *buf, int bsize, const char *fmt, ...)); +char *shf_smprintf ARGS((const char *fmt, ...)); +int shf_vfprintf ARGS((struct shf *, const char *fmt, va_list args)); + +#endif /* SHF_H */ diff --git a/siglist.in b/siglist.in new file mode 100644 index 0000000..e192a98 --- /dev/null +++ b/siglist.in @@ -0,0 +1,56 @@ +# $OpenBSD: siglist.in,v 1.1.1.1 1996/08/14 06:19:11 downsj Exp $ +# +# List of signals used to initialize ksh's signal table (see trap.c +# and siglist.sh). +# +# Note that if a system has multiple defines for the same signal +# (eg, SIGABRT vs SIGIOT, SIGCHLD vs SIGCLD), only the first one +# will be seen, so the order in this list is important. +# + HUP Hangup + INT Interrupt + QUIT Quit + ILL Illegal instruction + TRAP Trace trap +# before IOT (ABRT is posix and ABRT is sometimes the same as IOT) + ABRT Abort + IOT IOT instruction + EMT EMT trap + FPE Floating point exception + KILL Killed +# before BUS (linux doesn't really have a BUS, but defines it to UNUSED) + UNUSED Unused + BUS Bus error + SEGV Memory fault + SYS Bad system call + PIPE Broken pipe + ALRM Alarm clock + TERM Terminated + STKFLT Stack fault + IO I/O possible + XCPU CPU time limit exceeded + XFSZ File size limit exceeded + VTALRM Virtual timer expired + PROF Profiling timer expired + WINCH Window size change + LOST File lock lost + USR1 User defined signal 1 + USR2 User defined signal 2 + PWR Power-fail/Restart + POLL Pollable event occurred + STOP Stopped (signal) + TSTP Stopped + CONT Continued +# before CLD (CHLD is posix and CHLD is sometimes the same as CLD) + CHLD Child exited + CLD Child exited + TTIN Stopped (tty input) + TTOU Stopped (tty output) + INFO Information request + URG Urgent I/O condition +# Solaris (svr4?) signals + WAITING No runnable LWPs + LWP Inter-LWP signal + FREEZE Checkpoint freeze + THAW Checkpoint thaw + CANCEL Thread cancellation diff --git a/siglist.sh b/siglist.sh new file mode 100644 index 0000000..123b190 --- /dev/null +++ b/siglist.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# $OpenBSD: siglist.sh,v 1.4 1997/06/19 13:58:47 kstailey Exp $ + +# +# Script to generate a sorted, complete list of signals, suitable +# for inclusion in trap.c as array initializer. +# + +set -e + +in=tmpi$$.c +out=tmpo$$.c +ecode=1 +trapsigs='0 1 2 13 15' +trap 'rm -f $in $out; trap 0; exit $ecode' $trapsigs + +CPP="${1-cc -E}" + +# The trap here to make up for a bug in bash (1.14.3(1)) that calls the trap +(trap $trapsigs; + echo '#include "sh.h"'; + echo ' { QwErTy SIGNALS , "DUMMY" , "hook for number of signals" },'; + sed -e '/^[ ]*#/d' -e 's/^[ ]*\([^ ][^ ]*\)[ ][ ]*\(.*[^ ]\)[ ]*$/#ifdef SIG\1\ + { QwErTy SIG\1 , "\1", "\2" },\ +#endif/') > $in +$CPP $in > $out +sed -n 's/{ QwErTy/{/p' < $out | awk '{print NR, $0}' | sort +2n +0n | + sed 's/^[0-9]* //' | + awk 'BEGIN { last=0; nsigs=0; } + { + if ($2 ~ /^[0-9][0-9]*$/ && $3 == ",") { + n = $2; + if (n > 0 && n != last) { + while (++last < n) { + printf "\t{ %d , (char *) 0, `Signal %d` } ,\n", last, last; + } + print; + } + } + }' | + tr '`' '"' | grep -v '"DUMMY"' +ecode=0 diff --git a/syn.c b/syn.c new file mode 100644 index 0000000..5dee5d4 --- /dev/null +++ b/syn.c @@ -0,0 +1,947 @@ +/* $OpenBSD: syn.c,v 1.13 2002/06/09 05:47:27 todd 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 ARGS((void)); +static struct op *pipeline ARGS((int cf)); +static struct op *andor ARGS((void)); +static struct op *c_list ARGS((int multi)); +static struct ioword *synio ARGS((int cf)); +static void musthave ARGS((int c, int cf)); +static struct op *nested ARGS((int type, int smark, int emark)); +static struct op *get_command ARGS((int cf)); +static struct op *dogroup ARGS((void)); +static struct op *thenpart ARGS((void)); +static struct op *elsepart ARGS((void)); +static struct op *caselist ARGS((void)); +static struct op *casepart ARGS((int endtok)); +static struct op *function_body ARGS((char *name, int ksh_func)); +static char ** wordlist ARGS((void)); +static struct op *block ARGS((int type, struct op *t1, struct op *t2, + char **wp)); +static struct op *newtp ARGS((int type)); +static void syntaxerr ARGS((const char *what)) + GCC_FUNC_ATTR(noreturn); +static void nesting_push ARGS((struct nesting_state *save, int tok)); +static void nesting_pop ARGS((struct nesting_state *saved)); +static int assign_command ARGS((char *s)); +static int inalias ARGS((struct source *s)); +#ifdef KSH +static int dbtestp_isa ARGS((Test_env *te, Test_meta meta)); +static const char *dbtestp_getopnd ARGS((Test_env *te, Test_op op, + int do_eval)); +static int dbtestp_eval ARGS((Test_env *te, Test_op op, const char *opnd1, + const char *opnd2, int do_eval)); +static void dbtestp_error ARGS((Test_env *te, int offset, const char *msg)); +#endif /* KSH */ + +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() +{ + 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(cf) + int cf; +{ + register 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() +{ + register struct op *t, *p; + register 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(multi) + int multi; +{ + register struct op *t = NULL, *p, *tl = NULL; + register 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(cf) + int cf; +{ + register 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(c, cf) + int c, cf; +{ + if ((token(cf)) != c) + syntaxerr((char *) 0); +} + +static struct op * +nested(type, smark, emark) + int type, smark, emark; +{ + register 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(cf) + int cf; +{ + register struct op *t; + register 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; + +#ifdef KSH + 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; + } +#endif /* KSH */ + +#ifdef KSH + 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; +#endif /* KSH */ + + 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); + 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() +{ + register int c; + register 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() +{ + register 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() +{ + register 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() +{ + register 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(endtok) + int endtok; +{ + register struct op *t; + register 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(name, ksh_func) + 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 "typset -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() +{ + register 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); + if (XPsize(args) == 0) { + XPfree(args); + return NULL; + } else { + XPput(args, NULL); + return (char **) XPclose(args); + } +} + +/* + * supporting functions + */ + +static struct op * +block(type, t1, t2, wp) + int type; + struct op *t1, *t2; + char **wp; +{ + register 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 }, +#ifdef KSH + { "select", SELECT, TRUE }, +#endif /* KSH */ + { "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 }, +#ifdef KSH + { "[[", DBRACKET, TRUE }, +#endif /* KSH */ + /* Lexical tokens (0[EOF], LWORD and REDIR handled specially) */ + { "&&", LOGAND, FALSE }, + { "||", LOGOR, FALSE }, + { ";;", BREAK, FALSE }, +#ifdef KSH + { "((", MDPAREN, FALSE }, + { "|&", COPROC, FALSE }, +#endif /* KSH */ + /* and some special cases... */ + { "newline", '\n', FALSE }, + { 0 } +}; + +void +initkeywords() +{ + register struct tokeninfo const *tt; + register struct tbl *p; + + tinit(&keywords, APERM, 32); /* must be 2^n (currently 20 keywords) */ + for (tt = tokentab; tt->name; tt++) { + if (tt->reserved) { + p = tenter(&keywords, tt->name, hash(tt->name)); + p->flag |= DEFINED|ISSET; + p->type = CKEYWD; + p->val.i = tt->val; + } + } +} + +static void +syntaxerr(what) + 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(save, tok) + struct nesting_state *save; + int tok; +{ + *save = nesting; + nesting.start_token = tok; + nesting.start_line = source->line; +} + +static void +nesting_pop(saved) + struct nesting_state *saved; +{ + nesting = *saved; +} + +static struct op * +newtp(type) + int type; +{ + register 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(s) + 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(s) + char *s; +{ + char c = *s; + + if (Flag(FPOSIX) || !*s) + return 0; + return (c == 'a' && strcmp(s, "alias") == 0) + || (c == 'e' && strcmp(s, "export") == 0) + || (c == 'r' && strcmp(s, "readonly") == 0) + || (c == 't' && strcmp(s, "typeset") == 0); +} + +/* Check if we are in the middle of reading an alias */ +static int +inalias(s) + struct source *s; +{ + for (; s && s->type == SALIAS; s = s->next) + if (!(s->flags & SF_ALIASEND)) + return 1; + return 0; +} + + +#ifdef KSH +/* 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(te, meta) + 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(te, op, do_eval) + 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(te, op, opnd1, opnd2, do_eval) + Test_env *te; + Test_op op; + const char *opnd1; + const char *opnd2; + int do_eval; +{ + return 1; +} + +static void +dbtestp_error(te, offset, msg) + 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); +} +#endif /* KSH */ diff --git a/table.c b/table.c new file mode 100644 index 0000000..91b33bc --- /dev/null +++ b/table.c @@ -0,0 +1,240 @@ +/* $OpenBSD: table.c,v 1.5 1999/01/10 17:55:03 millert 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 ARGS((struct table *tp, int nsize)); +static int tnamecmp ARGS((void *p1, void *p2)); + + +unsigned int +hash(n) + register const char * n; +{ + register unsigned int h = 0; + + while (*n != '\0') + h = 2*h + *n++; + return h * 32821; /* scatter bits */ +} + +void +tinit(tp, ap, tsize) + register struct table *tp; + register Area *ap; + int tsize; +{ + tp->areap = ap; + tp->tbls = NULL; + tp->size = tp->nfree = 0; + if (tsize) + texpand(tp, tsize); +} + +static void +texpand(tp, nsize) + register struct table *tp; + int nsize; +{ + register int i; + register struct tbl *tblp, **p; + register 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 = 8*nsize/10; /* table can get 80% 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); +} + +struct tbl * +tsearch(tp, n, h) + register struct table *tp; /* table */ + register const char *n; /* name to enter */ + unsigned int h; /* hash(n) */ +{ + register 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; +} + +struct tbl * +tenter(tp, n, h) + register struct table *tp; /* table */ + register const char *n; /* name to enter */ + unsigned int h; /* hash(n) */ +{ + register struct tbl **pp, *p; + register 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 */ + texpand(tp, 2*tp->size); + 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 +tdelete(p) + register struct tbl *p; +{ + p->flag = 0; +} + +void +twalk(ts, tp) + struct tstate *ts; + struct table *tp; +{ + ts->left = tp->size; + ts->next = tp->tbls; +} + +struct tbl * +tnext(ts) + 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(p1, p2) + void *p1, *p2; +{ + return strcmp(((struct tbl *)p1)->name, ((struct tbl *)p2)->name); +} + +struct tbl ** +tsort(tp) + register struct table *tp; +{ + register int i; + register 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 ARGS((struct table *tp)); + +void +tprintinfo(tp) + 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"); + twalk(&ts, tp); + while ((te = tnext(&ts))) { + register struct tbl **pp, *p; + + h = hash(n = te->name); + ncmp = 0; + + /* taken from tsearch() 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 new file mode 100644 index 0000000..f88c178 --- /dev/null +++ b/table.h @@ -0,0 +1,183 @@ +/* $OpenBSD: table.h,v 1.5 1999/06/15 01:18:36 millert 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 */ + short 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) ARGS((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 twalk() and tnext() 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) ARGS((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/tests/README b/tests/README new file mode 100644 index 0000000..73cf83b --- /dev/null +++ b/tests/README @@ -0,0 +1,22 @@ +Tests can be assigned categories to restrict what program they +are applied to (eg, pdksh, ksh88, etc.). The following are +a list of names to be used for various shells (to keep things +consistent): + + sh generic any v7 bourne shell like thing + sh-v generic any system V bourne shell like thing + ksh generic any ksh + posix generic basic posix shell + posix-upu generic `user portability utility' options + sh-v7 specific the real v7 bourne shell + sh-sysv specific the real sysv bourne shell + pdksh specific public domain ksh + ksh88 specific at&t ksh88 + ksh93 specific at&t ksh93 + bash specific GNU bourne-again shell + +The idea is to categorize all the tests according to the `best match' +(most generic thing). All generics that apply should be specified. +Generally, at most one specific shell will be given. + +At the moment, most (all) tests have not been categorized (any volunteers?). diff --git a/tests/alias.t b/tests/alias.t new file mode 100644 index 0000000..511744b --- /dev/null +++ b/tests/alias.t @@ -0,0 +1,112 @@ +name: alias-1 +description: + Check that recursion is detected/avoided in aliases. +stdin: + alias fooBar=fooBar + fooBar + exit 0 +expected-stderr-pattern: + /fooBar.*not found.*/ +--- + +name: alias-2 +description: + Check that recursion is detected/avoided in aliases. +stdin: + alias fooBar=barFoo + alias barFoo=fooBar + fooBar + barFoo + exit 0 +expected-stderr-pattern: + /fooBar.*not found.*\n.*barFoo.*not found/ +--- + +name: alias-3 +description: + Check that recursion is detected/avoided in aliases. +stdin: + alias Echo='echo ' + alias fooBar=barFoo + alias barFoo=fooBar + Echo fooBar + unalias barFoo + Echo fooBar +expected-stdout: + fooBar + barFoo +--- + +name: alias-4 +description: + Check that alias expansion isn't done on keywords (in keyword + postitions). +stdin: + alias Echo='echo ' + alias while=While + while false; do echo hi ; done + Echo while +expected-stdout: + While +--- + +name: alias-5 +description: + Check that alias expansion done after alias with trailing space. +stdin: + alias Echo='echo ' + alias foo='bar stuff ' + alias bar='Bar1 Bar2 ' + alias stuff='Stuff' + alias blah='Blah' + Echo foo blah +expected-stdout: + Bar1 Bar2 Stuff Blah +--- + +name: alias-6 +description: + Check that alias expansion done after alias with trailing space. +stdin: + alias Echo='echo ' + alias foo='bar bar' + alias bar='Bar ' + alias blah=Blah + Echo foo blah +expected-stdout: + Bar Bar Blah +--- + +name: alias-7 +description: + Check that alias expansion done after alias with trailing space + after a keyword. +stdin: + alias X='case ' + alias Y=Z + X Y in 'Y') echo is y ;; Z) echo is z ; esac +expected-stdout: + is z +--- + +name: alias-8 +description: + Check that newlines in an alias don't cause the command to be lost. +stdin: + alias foo=' + + + echo hi + + + + echo there + + + ' + foo +expected-stdout: + hi + there +--- + diff --git a/tests/arith.t b/tests/arith.t new file mode 100644 index 0000000..e18ea2e --- /dev/null +++ b/tests/arith.t @@ -0,0 +1,79 @@ +name: arith-lazy-1 +description: + Check that only one side of ternary operator is evaluated +stdin: + x=i+=2 + y=j+=2 + typeset -i i=1 j=1 + echo $((1 ? 20 : (x+=2))) + echo $i,$x + echo $((0 ? (y+=2) : 30)) + echo $j,$y +expected-stdout: + 20 + 1,i+=2 + 30 + 1,j+=2 +--- + +name: arith-lazy-2 +description: + Check that assignments not done on non-evaluated side of ternary + operator +stdin: + x=i+=2 + y=j+=2 + typeset -i i=1 j=1 + echo $((1 ? 20 : (x+=2))) + echo $i,$x + echo $((0 ? (y+=2) : 30)) + echo $i,$y +expected-stdout: + 20 + 1,i+=2 + 30 + 1,j+=2 +--- + +name: arith-ternary-prec-1 +description: + Check precidance of ternary operator vs assignment +stdin: + typeset -i x=2 + y=$((1 ? 20 : x+=2)) +expected-exit: e != 0 +expected-stderr-pattern: + /.*:.*1 \? 20 : x\+=2.*lvalue.*\n$/ +--- + +name: arith-ternary-prec-2 +description: + Check precidance of ternary operator vs assignment +stdin: + typeset -i x=2 + echo $((0 ? x+=2 : 20)) +expected-stdout: + 20 +--- + +name: arith-div-assoc-1 +description: + Check associativity of division operator +stdin: + echo $((20 / 2 / 2)) +expected-stdout: + 5 +--- + +name: arith-assop-assoc-1 +description: + Check associativity of assignment-operator operator +stdin: + typeset -i i=1 j=2 k=3 + echo $((i += j += k)) + echo $i,$j,$k +expected-stdout: + 6 + 6,5,3 +--- + diff --git a/tests/bksl-nl.t b/tests/bksl-nl.t new file mode 100644 index 0000000..a4a6f54 --- /dev/null +++ b/tests/bksl-nl.t @@ -0,0 +1,341 @@ +# $OpenBSD: bksl-nl.t,v 1.2 2001/01/28 23:04:56 niklas Exp $ + +# +# These tests deal with how \newline is handled in various situations. The +# first group of tests are places where it shouldn't be collapsed, the next +# group of tests are places where it should be collapsed. +# +name: bksl-nl-ign-1 +description: + Check that \newline is not collasped after # +stdin: + echo hi #there \ + echo folks +expected-stdout: + hi + folks +--- + +name: bksl-nl-ign-2 +description: + Check that \newline is not collasped inside single quotes +stdin: + echo 'hi \ + there' + echo folks +expected-stdout: + hi \ + there + folks +--- + +name: bksl-nl-ign-3 +description: + Check that \newline is not collasped inside single quotes +stdin: + cat << \EOF + hi \ + there + EOF +expected-stdout: + hi \ + there +--- + +name: blsk-nl-ign-4 +description: + Check interaction of aliases, single quotes and here-documents + with backslash-newline + (don't know what posix has to say about this) +stdin: + a=2 + alias x='echo hi + cat << "EOF" + foo\ + bar + some' + x + more\ + stuff$a + EOF +expected-stdout: + hi + foo\ + bar + some + more\ + stuff$a +--- + +name: blsk-nl-ign-5 +description: + Check what happens with backslash at end of input + (the old bourne shell trashes them; so do we) +stdin: ! + echo `echo foo\\`bar + echo hi\ +expected-stdout: + foobar + hi +--- + + +# +# Places \newline should be collapsed +# +name: bksl-nl-1 +description: + Check that \newline is collasped before, in the middle of, and + after words +stdin: + \ + echo hi\ + There, \ + folks +expected-stdout: + hiThere, folks +--- + +name: bksl-nl-2 +description: + Check that \newline is collasped in $ sequences + (ksh93 fails this) +stdin: + a=12 + ab=19 + echo $\ + a + echo $a\ + b + echo $\ + {a} + echo ${a\ + b} + echo ${ab\ + } +expected-stdout: + 12 + 19 + 12 + 19 + 19 +--- + +name: bksl-nl-3 +description: + Check that \newline is collasped in $(..) and `...` sequences + (ksh93 fails this) +stdin: + echo $\ + (echo foobar1) + echo $(\ + echo foobar2) + echo $(echo foo\ + bar3) + echo $(echo foobar4\ + ) + echo ` + echo stuff1` + echo `echo st\ + uff2` +expected-stdout: + foobar1 + foobar2 + foobar3 + foobar4 + stuff1 + stuff2 +--- + +name: bksl-nl-4 +description: + Check that \newline is collasped in $((..)) sequences + (ksh93 fails this) +stdin: + echo $\ + ((1+2)) + echo $(\ + (1+2+3)) + echo $((\ + 1+2+3+4)) + echo $((1+\ + 2+3+4+5)) + echo $((1+2+3+4+5+6)\ + ) +expected-stdout: + 3 + 6 + 10 + 15 + 21 +--- + +name: bksl-nl-5 +description: + Check that \newline is collasped in double quoted strings +stdin: + echo "\ + hi" + echo "foo\ + bar" + echo "folks\ + " +expected-stdout: + hi + foobar + folks +--- + +name: bksl-nl-6 +description: + Check that \newline is collasped in here document delimiters + (ksh93 fails second part of this) +stdin: + a=12 + cat << EO\ + F + a=$a + foo\ + bar + EOF + cat << E_O_F + foo + E_O_\ + F + echo done +expected-stdout: + a=12 + foobar + foo + done +--- + +name: bksl-nl-7 +description: + Check that \newline is collasped in double-quoted here-document + delimiter. +stdin: + a=12 + cat << "EO\ + F" + a=$a + foo\ + bar + EOF + echo done +expected-stdout: + a=$a + foo\ + bar + done +--- + +name: bksl-nl-8 +description: + Check that \newline is collasped in various 2+ character tokens + delimiter. + (ksh93 fails this) +stdin: + echo hi &\ + & echo there + echo foo |\ + | echo bar + cat <\ + < EOF + stuff + EOF + cat <\ + <\ + - EOF + more stuff + EOF + cat <<\ + EOF + abcdef + EOF + echo hi >\ + > /dev/null + echo $? + i=1 + case $i in + (\ + x|\ + 1\ + ) echo hi;\ + ; + (*) echo oops + esac +expected-stdout: + hi + there + foo + stuff + more stuff + abcdef + 0 + hi +--- + +name: blsk-nl-9 +description: + Check that \ at the end of an alias is collapsed when followed + by a newline + (don't know what posix has to say about this) +stdin: + alias x='echo hi\' + x + echo there +expected-stdout: + hiecho there +--- + +name: blsk-nl-10 +description: + Check that \newline in a keyword is collapsed +stdin: + i\ + f true; then\ + echo pass; el\ + se echo fail; fi +expected-stdout: + pass +--- + +# +# Places \newline should be collapsed (ksh extensions) +# + +name: blsk-nl-ksh-1 +description: + Check that \newline is collapsed in extended globbing + (ksh93 fails this) +stdin: + xxx=foo + case $xxx in + (f*\ + (\ + o\ + )\ + ) echo ok ;; + *) echo bad + esac +expected-stdout: + ok +--- + +name: blsk-nl-ksh-2 +description: + Check that \newline is collapsed in ((...)) expressions + (ksh93 fails this) +stdin: + i=1 + (\ + (\ + i=i+2\ + )\ + ) + echo $i +expected-stdout: + 3 +--- + diff --git a/tests/brkcont.t b/tests/brkcont.t new file mode 100644 index 0000000..1eb9c25 --- /dev/null +++ b/tests/brkcont.t @@ -0,0 +1,195 @@ +name: break-1 +description: + See if break breaks out of loops +stdin: + for i in a b c; do echo $i; break; echo bad-$i; done + echo end-1 + for i in a b c; do echo $i; break 1; echo bad-$i; done + echo end-2 + for i in a b c; do + for j in x y z; do + echo $i:$j + break + echo bad-$i + done + echo end-$i + done + echo end-3 +expected-stdout: + a + end-1 + a + end-2 + a:x + end-a + b:x + end-b + c:x + end-c + end-3 +--- + +name: break-2 +description: + See if break breaks out of nested loops +stdin: + for i in a b c; do + for j in x y z; do + echo $i:$j + break 2 + echo bad-$i + done + echo end-$i + done + echo end +expected-stdout: + a:x + end +--- + + +name: break-3 +description: + What if break used outside of any loops + (ksh88,ksh93 don't print error messages here) +stdin: + break +expected-stderr-pattern: + /.*break.*/ +--- + + +name: break-4 +description: + What if break N used when only N-1 loops + (ksh88,ksh93 don't print error messages here) +stdin: + for i in a b c; do echo $i; break 2; echo bad-$i; done + echo end +expected-stdout: + a + end +expected-stderr-pattern: + /.*break.*/ +--- + + +name: break-5 +description: + Error if break argument isn't a number +stdin: + for i in a b c; do echo $i; break abc; echo more-$i; done + echo end +expected-stdout: + a +expected-exit: e != 0 +expected-stderr-pattern: + /.*break.*/ +--- + + +name: continue-1 +description: + See if continue continues loops +stdin: + for i in a b c; do echo $i; continue; echo bad-$i ; done + echo end-1 + for i in a b c; do echo $i; continue 1; echo bad-$i; done + echo end-2 + for i in a b c; do + for j in x y z; do + echo $i:$j + continue + echo bad-$i-$j + done + echo end-$i + done + echo end-3 +expected-stdout: + a + b + c + end-1 + a + b + c + end-2 + a:x + a:y + a:z + end-a + b:x + b:y + b:z + end-b + c:x + c:y + c:z + end-c + end-3 +--- + + +name: continue-2 +description: + See if continue breaks out of nested loops +stdin: + for i in a b c; do + for j in x y z; do + echo $i:$j + continue 2 + echo bad-$i-$j + done + echo end-$i + done + echo end +expected-stdout: + a:x + b:x + c:x + end +--- + + +name: continue-3 +description: + What if continue used outside of any loops + (ksh88,ksh93 don't print error messages here) +stdin: + continue +expected-stderr-pattern: + /.*continue.*/ +--- + + +name: continue-4 +description: + What if continue N used when only N-1 loops + (ksh88,ksh93 don't print error messages here) +stdin: + for i in a b c; do echo $i; continue 2; echo bad-$i; done + echo end +expected-stdout: + a + b + c + end +expected-stderr-pattern: + /.*continue.*/ +--- + + +name: continue-5 +description: + Error if continue argument isn't a number +stdin: + for i in a b c; do echo $i; continue abc; echo more-$i; done + echo end +expected-stdout: + a +expected-exit: e != 0 +expected-stderr-pattern: + /.*continue.*/ +--- + + diff --git a/tests/cdhist.t b/tests/cdhist.t new file mode 100644 index 0000000..5c5d4f2 --- /dev/null +++ b/tests/cdhist.t @@ -0,0 +1,162 @@ +name: cd-history +description: + Test someone's CD history package (uses arrays) +# Fails on OS/2, since directory names are prepended with drive letter. +category: !os:os2 +stdin: + # go to known place before doing anything + cd / + + alias cd=_cd + function _cd + { + typeset -i cdlen i + typeset t + + if [ $# -eq 0 ] + then + set -- $HOME + fi + + if [ "$CDHISTFILE" -a -r "$CDHISTFILE" ] # if directory history exists + then + typeset CDHIST + i=-1 + while read -r t # read directory history file + do + CDHIST[i=i+1]=$t + done <$CDHISTFILE + fi + + if [ "${CDHIST[0]}" != "$PWD" -a "$PWD" != "" ] + then + _cdins # insert $PWD into cd history + fi + + cdlen=${#CDHIST[*]} # number of elements in history + + case "$@" in + -) # cd to new dir + if [ "$OLDPWD" = "" ] && ((cdlen>1)) + then + 'print' ${CDHIST[1]} + 'cd' ${CDHIST[1]} + _pwd + else + 'cd' $@ + _pwd + fi + ;; + -l) # print directory list + typeset -R3 num + ((i=cdlen)) + while (((i=i-1)>=0)) + do + num=$i + 'print' "$num ${CDHIST[i]}" + done + return + ;; + -[0-9]|-[0-9][0-9]) # cd to dir in list + if (((i=${1#-})=cdlen)) + then + 'cd' $@ + _pwd + fi + ;; + *) # cd to new dir + 'cd' $@ + _pwd + ;; + esac + + _cdins # insert $PWD into cd history + + if [ "$CDHISTFILE" ] + then + cdlen=${#CDHIST[*]} # number of elements in history + + i=0 + while ((i$CDHISTFILE + fi + } + + function _cdins # insert $PWD into cd history + { # meant to be called only by _cd + typeset -i i + + ((i=0)) + while ((i<${#CDHIST[*]})) # see if dir is already in list + do + if [ "${CDHIST[$i]}" = "$PWD" ] + then + break + fi + ((i=i+1)) + done + + if ((i>22)) # limit max size of list + then + i=22 + fi + + while (((i=i-1)>=0)) # bump old dirs in list + do + CDHIST[i+1]=${CDHIST[i]} + done + + CDHIST[0]=$PWD # insert new directory in list + } + + + function _pwd + { + if [ -n "$ECD" ] + then + pwd 1>&6 + fi + } + # Start of test + cd /tmp + cd /bin + cd /etc + cd - + cd -2 + cd -l +expected-stdout: + /bin + /tmp + 3 / + 2 /etc + 1 /bin + 0 /tmp +--- diff --git a/tests/eglob.t b/tests/eglob.t new file mode 100644 index 0000000..9b1b4d7 --- /dev/null +++ b/tests/eglob.t @@ -0,0 +1,145 @@ +name: eglob-bad-1 +description: + Check that globbing isn't done when glob has syntax error +file-setup: file 644 "abcx" +file-setup: file 644 "abcz" +file-setup: file 644 "bbc" +stdin: + echo !([*)* + echo +(a|b[)* +expected-stdout: + !([*)* + +(a|b[)* +--- + +name: eglob-bad-2 +description: + Check that globbing isn't done when glob has syntax error + (at&t ksh fails this test) +file-setup: file 644 "abcx" +file-setup: file 644 "abcz" +file-setup: file 644 "bbc" +stdin: + echo [a*(]*)z +expected-stdout: + [a*(]*)z +--- + +name: eglob-infinite-plus +description: + Check that shell doesn't go into infinite loop expanding +(...) + expressions. +file-setup: file 644 "abc" +time-limit: 3 +stdin: + echo +()c + echo +()x + echo +(*)c + echo +(*)x +expected-stdout: + +()c + +()x + abc + +(*)x +--- + +name: eglob-subst-1 +description: + Check that eglobbing isn't done on substitution results +file-setup: file 644 "abc" +stdin: + x='@(*)' + echo $x +expected-stdout: + @(*) +--- + +name: eglob-nomatch-1 +description: + Check that the pattern doesn't match +stdin: + echo 1: no-file+(a|b)stuff + echo 2: no-file+(a*(c)|b)stuff + echo 3: no-file+((((c)))|b)stuff +expected-stdout: + 1: no-file+(a|b)stuff + 2: no-file+(a*(c)|b)stuff + 3: no-file+((((c)))|b)stuff +--- + +name: eglob-match-1 +description: + Check that the pattern matches correctly +file-setup: file 644 "abd" +file-setup: file 644 "acd" +file-setup: file 644 "abac" +stdin: + echo 1: a+(b|c)d + echo 2: a!(@(b|B))d + echo 3: *(a(b|c)) # (...|...) can be used within X(..) + echo 4: a[b*(foo|bar)]d # patterns not special inside [...] +expected-stdout: + 1: abd acd + 2: acd + 3: abac + 4: abd +--- + +name: eglob-case-1 +description: + Simple negation tests +stdin: + case foo in !(foo|bar)) echo yes;; *) echo no;; esac + case bar in !(foo|bar)) echo yes;; *) echo no;; esac +expected-stdout: + no + no +--- + +name: eglob-case-2 +description: + Simple kleene tests +stdin: + case foo in *(a|b[)) echo yes;; *) echo no;; esac + case foo in *(a|b[)|f*) echo yes;; *) echo no;; esac + case '*(a|b[)' in *(a|b[)) echo yes;; *) echo no;; esac +expected-stdout: + no + yes + yes +--- + +name: eglob-trim-1 +description: + Eglobing in trim expressions... + (at&t ksh fails this - docs say # matches shortest string, ## matches + longest...) +stdin: + x=abcdef + echo 1: ${x#a|abc} + echo 2: ${x##a|abc} + echo 3: ${x%def|f} + echo 4: ${x%%f|def} +expected-stdout: + 1: bcdef + 2: def + 3: abcde + 4: abc +--- + +name: eglob-trim-2 +description: + Check eglobing works in trims... +stdin: + x=abcdef + echo 1: ${x#*(a|b)cd} + echo 2: "${x#*(a|b)cd}" + echo 3: ${x#"*(a|b)cd"} + echo 4: ${x#a(b|c)} +expected-stdout: + 1: ef + 2: ef + 3: abcdef + 4: cdef +--- + diff --git a/tests/glob.t b/tests/glob.t new file mode 100644 index 0000000..b8a383c --- /dev/null +++ b/tests/glob.t @@ -0,0 +1,99 @@ +name: glob-bad-1 +description: + Check that globbing isn't done when glob has syntax error +file-setup: dir 755 "[x" +file-setup: file 644 "[x/foo" +stdin: + echo [* + echo *[x + echo [x/* +expected-stdout: + [* + *[x + [x/foo +--- + +name: glob-bad-2 +description: + Check that symbolic links aren't stat()'d +category: !os:os2 +file-setup: dir 755 "dir" +file-setup: symlink 644 "dir/abc" + non-existant-file +stdin: + echo d*/* + echo d*/abc +expected-stdout: + dir/abc + dir/abc +--- + +name: glob-range-1 +description: + Test range matching +file-setup: file 644 ".bc" +file-setup: file 644 "abc" +file-setup: file 644 "bbc" +file-setup: file 644 "cbc" +file-setup: file 644 "-bc" +stdin: + echo [ab-]* + echo [-ab]* + echo [!-ab]* + echo [!ab]* + echo []ab]* +expected-stdout: + -bc abc bbc + -bc abc bbc + cbc + -bc cbc + abc bbc +--- + +name: glob-range-2 +description: + Test range matching + (at&t ksh fails this; POSIX says invalid) +file-setup: file 644 "abc" +stdin: + echo [a--]* +expected-stdout: + [a--]* +--- + +name: glob-range-3 +description: + Check that globbing matches the right things... +file-setup: file 644 "aÂc" +stdin: + echo a[Á-Ú]* +expected-stdout: + aÂc +--- + +name: glob-range-4 +description: + Results unspecified according to POSIX +file-setup: file 644 ".bc" +stdin: + echo [a.]* +expected-stdout: + [a.]* +--- + +name: glob-range-5 +description: + Results unspecified according to POSIX + (at&t ksh treats this like [a-cc-e]*) +file-setup: file 644 "abc" +file-setup: file 644 "bbc" +file-setup: file 644 "cbc" +file-setup: file 644 "dbc" +file-setup: file 644 "ebc" +file-setup: file 644 "-bc" +stdin: + echo [a-c-e]* +expected-stdout: + -bc abc bbc cbc ebc +--- + diff --git a/tests/heredoc.t b/tests/heredoc.t new file mode 100644 index 0000000..084638c --- /dev/null +++ b/tests/heredoc.t @@ -0,0 +1,331 @@ +name: heredoc-1 +description: + Check ordering/content of redundent here documents. +stdin: + cat << EOF1 << EOF2 + hi + EOF1 + there + EOF2 +expected-stdout: + there +--- + +name: heredoc-2 +description: + Check quoted here-doc is protected. +stdin: + a=foo + cat << 'EOF' + hi\ + there$a + stuff + EO\ + F + EOF +expected-stdout: + hi\ + there$a + stuff + EO\ + F +--- + +name: heredoc-3 +description: + Check that newline isn't needed after heredoc-delimiter marker. +stdin: ! + cat << EOF + hi + there + EOF +expected-stdout: + hi + there +--- + +name: heredoc-4 +description: + Check that an error occurs if the heredoc-delimiter is missing. +stdin: ! + cat << EOF + hi + there +expected-exit: e > 0 +expected-stderr-pattern: /.*/ +--- + +name: heredoc-5 +description: + Check that backslash quotes a $, ` and \ and kills a \newline +stdin: + a=BAD + b=ok + cat << EOF + h\${a}i + h\\${b}i + th\`echo not-run\`ere + th\\`echo is-run`ere + fol\\ks + more\\ + last \ + line + EOF +expected-stdout: + h${a}i + h\oki + th`echo not-run`ere + th\is-runere + fol\ks + more\ + last line +--- + +name: heredoc-6 +description: + Check that \newline in initial here-delim word doesn't imply + a quoted here-doc. +stdin: + a=i + cat << EO\ + F + h$a + there + EOF +expected-stdout: + hi + there +--- + +name: heredoc-7 +description: + Check that double quoted $ expressions in here delimiters are + not expanded and match the delimiter. + POSIX says only quote removal is applied to the delimiter. +stdin: + a=b + cat << "E$a" + hi + h$a + hb + E$a + echo done +expected-stdout: + hi + h$a + hb + done +--- + +name: heredoc-8 +description: + Check that double quoted escaped $ expressions in here + delimiters are not expanded and match the delimiter. + POSIX says only quote removal is applied to the delimiter + (\ counts as a quote). +stdin: + a=b + cat << "E\$a" + hi + h$a + h\$a + hb + h\b + E$a + echo done +expected-stdout: + hi + h$a + h\$a + hb + h\b + done +--- + +name: heredoc-tmpfile-1 +description: + Check that heredoc temp files aren't removed too soon or too late. + Heredoc in simple command. +stdin: + TMPDIR=$PWD + eval ' + cat <<- EOF + hi + EOF + for i in a b ; do + cat <<- EOF + more + EOF + done + ' & + sleep 1 + echo Left overs: * +expected-stdout: + hi + more + more + Left overs: * +--- + +name: heredoc-tmpfile-2 +description: + Check that heredoc temp files aren't removed too soon or too late. + Heredoc in function, multiple calls to function. +stdin: + TMPDIR=$PWD + eval ' + foo() { + cat <<- EOF + hi + EOF + } + foo + foo + ' & + sleep 1 + echo Left overs: * +expected-stdout: + hi + hi + Left overs: * +--- + +name: heredoc-tmpfile-3 +description: + Check that heredoc temp files aren't removed too soon or too late. + Heredoc in function in loop, multiple calls to function. +stdin: + TMPDIR=$PWD + eval ' + foo() { + cat <<- EOF + hi + EOF + } + for i in a b; do + foo + foo() { + cat <<- EOF + folks $i + EOF + } + done + foo + ' & + sleep 1 + echo Left overs: * +expected-stdout: + hi + folks b + folks b + Left overs: * +--- + +name: heredoc-tmpfile-4 +description: + Check that heredoc temp files aren't removed too soon or too late. + Backgrounded simple command with here doc +stdin: + TMPDIR=$PWD + eval ' + cat <<- EOF & + hi + EOF + ' & + sleep 1 + echo Left overs: * +expected-stdout: + hi + Left overs: * +--- + +name: heredoc-tmpfile-5 +description: + Check that heredoc temp files aren't removed too soon or too late. + Backgrounded subshell command with here doc +stdin: + TMPDIR=$PWD + eval ' + ( + sleep 1 # so parent exits + echo A + cat <<- EOF + hi + EOF + echo B + ) & + ' & + sleep 2 + echo Left overs: * +expected-stdout: + A + hi + B + Left overs: * +--- + +name: heredoc-tmpfile-6 +description: + Check that heredoc temp files aren't removed too soon or too late. + Heredoc in pipeline. +stdin: + TMPDIR=$PWD + eval ' + cat <<- EOF | sed "s/hi/HI/" + hi + EOF + ' & + sleep 1 + echo Left overs: * +expected-stdout: + HI + Left overs: * +--- + +name: heredoc-tmpfile-7 +description: + Check that heredoc temp files aren't removed too soon or too late. + Heredoc in backgrounded pipeline. +stdin: + TMPDIR=$PWD + eval ' + cat <<- EOF | sed 's/hi/HI/' & + hi + EOF + ' & + sleep 1 + echo Left overs: * +expected-stdout: + HI + Left overs: * +--- + +name: heredoc-tmpfile-8 +description: + Check that heredoc temp files aren't removed too soon or too late. + Heredoc in function, backgrounded call to function. +stdin: + TMPDIR=$PWD + # Background eval so main shell doesn't do parsing + eval ' + foo() { + cat <<- EOF + hi + EOF + } + foo + # sleep so eval can die + (sleep 1; foo) & + (sleep 1; foo) & + foo + ' & + sleep 2 + echo Left overs: * +expected-stdout: + hi + hi + hi + hi + Left overs: * +--- + diff --git a/tests/history.t b/tests/history.t new file mode 100644 index 0000000..cb94df8 --- /dev/null +++ b/tests/history.t @@ -0,0 +1,559 @@ +# $OpenBSD: history.t,v 1.5 2001/01/28 23:04:56 niklas Exp $ + +# Not tested yet: +# - commands in history file are not numbered negatively +# (and a few hundred other things) + +name: history-basic +description: + See if we can test history at all +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo hi + fc -l +expected-stdout: + hi + 1 echo hi +expected-stderr-pattern: + /^X*$/ +--- + +name: history-e-minus-1 +description: + Check if more recent command is executed +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo hi + echo there + fc -e - +expected-stdout: + hi + there + there +expected-stderr-pattern: + /^X*echo there\nX*$/ +--- + +name: history-e-minus-2 +description: + Check that repeated command is printed before command + is re-executed. +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + exec 2>&1 + echo hi + echo there + fc -e - +expected-stdout-pattern: + /X*hi\nX*there\nX*echo there\nthere\nX*/ +expected-stderr-pattern: + /^X*$/ +--- + +name: history-e-minus-3 +description: + fc -e - fails when there is no history + (ksh93 has a bug that causes this to fail) + (ksh88 loops on this) +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + fc -e - + echo ok +expected-stdout: + ok +expected-stderr-pattern: + /^X*.*:.*history.*\nX*$/ +--- + +name: history-e-minus-4 +description: + Check if "fc -e -" command output goes to stdout. +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc + fc -e - | (read x; echo "A $x") + echo ok +expected-stdout: + abc + A abc + ok +expected-stderr-pattern: + /^X*echo abc\nX*/ +--- + +name: history-e-minus-5 +description: + fc is replaced in history by new command. +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + echo ghi jkl + fc -e - echo + fc -l 2 4 +expected-stdout: + abc def + ghi jkl + ghi jkl + 2 echo ghi jkl + 3 echo ghi jkl + 4 fc -l 2 4 +expected-stderr-pattern: + /^X*echo ghi jkl\nX*$/ +--- + +name: history-list-1 +description: + List lists correct range + (ksh88 fails 'cause it lists the fc command) +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + fc -l -- -2 +expected-stdout: + line 1 + line 2 + line 3 + 2 echo line 2 + 3 echo line 3 +expected-stderr-pattern: + /^X*$/ +--- + +name: history-list-2 +description: + Lists oldest history if given pre-historic number + (ksh93 has a bug that causes this to fail) + (ksh88 fails 'cause it lists the fc command) +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + fc -l -- -40 +expected-stdout: + line 1 + line 2 + line 3 + 1 echo line 1 + 2 echo line 2 + 3 echo line 3 +expected-stderr-pattern: + /^X*$/ +--- + +name: history-list-3 +description: + Can give number `options' to fc +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + fc -l -3 -2 +expected-stdout: + line 1 + line 2 + line 3 + line 4 + 2 echo line 2 + 3 echo line 3 +expected-stderr-pattern: + /^X*$/ +--- + +name: history-list-4 +description: + -1 refers to previous command +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + fc -l -1 -1 +expected-stdout: + line 1 + line 2 + line 3 + line 4 + 4 echo line 4 +expected-stderr-pattern: + /^X*$/ +--- + +name: history-list-5 +description: + List command stays in history +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + fc -l -1 -1 + fc -l -2 -1 +expected-stdout: + line 1 + line 2 + line 3 + line 4 + 4 echo line 4 + 4 echo line 4 + 5 fc -l -1 -1 +expected-stderr-pattern: + /^X*$/ +--- + +name: history-list-6 +description: + HISTSIZE limits about of history kept. + (ksh88 fails 'cause it lists the fc command) +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + echo line 5 + fc -l +expected-stdout: + line 1 + line 2 + line 3 + line 4 + line 5 + 4 echo line 4 + 5 echo line 5 +expected-stderr-pattern: + /^X*$/ +--- + +name: history-list-7 +description: + fc allows too old/new errors in range specification +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + echo line 5 + fc -l 1 30 +expected-stdout: + line 1 + line 2 + line 3 + line 4 + line 5 + 4 echo line 4 + 5 echo line 5 + 6 fc -l 1 30 +expected-stderr-pattern: + /^X*$/ +--- + +name: history-list-r-1 +description: + test -r flag in history +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + echo line 5 + fc -l -r 2 4 +expected-stdout: + line 1 + line 2 + line 3 + line 4 + line 5 + 4 echo line 4 + 3 echo line 3 + 2 echo line 2 +expected-stderr-pattern: + /^X*$/ +--- + +name: history-list-r-2 +description: + If first is newer than last, -r is implied. +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + echo line 5 + fc -l 4 2 +expected-stdout: + line 1 + line 2 + line 3 + line 4 + line 5 + 4 echo line 4 + 3 echo line 3 + 2 echo line 2 +expected-stderr-pattern: + /^X*$/ +--- + +name: history-list-r-3 +description: + If first is newer than last, -r is cancelled. +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 + echo line 3 + echo line 4 + echo line 5 + fc -l -r 4 2 +expected-stdout: + line 1 + line 2 + line 3 + line 4 + line 5 + 2 echo line 2 + 3 echo line 3 + 4 echo line 4 +expected-stderr-pattern: + /^X*$/ +--- + +name: history-subst-1 +description: + Basic substitution +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + echo ghi jkl + fc -e - abc=AB 'echo a' +expected-stdout: + abc def + ghi jkl + AB def +expected-stderr-pattern: + /^X*echo AB def\nX*$/ +--- + +name: history-subst-2 +description: + Does subst find previous command? +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + echo ghi jkl + fc -e - jkl=XYZQRT 'echo g' +expected-stdout: + abc def + ghi jkl + ghi XYZQRT +expected-stderr-pattern: + /^X*echo ghi XYZQRT\nX*$/ +--- + +name: history-subst-3 +description: + Does subst find previous command when no arguments given +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + echo ghi jkl + fc -e - jkl=XYZQRT +expected-stdout: + abc def + ghi jkl + ghi XYZQRT +expected-stderr-pattern: + /^X*echo ghi XYZQRT\nX*$/ +--- + +name: history-subst-4 +description: + Global substitutions work + (ksh88 and ksh93 do not have -g option) +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def asjj sadjhasdjh asdjhasd + fc -e - -g a=FooBAR +expected-stdout: + abc def asjj sadjhasdjh asdjhasd + FooBARbc def FooBARsjj sFooBARdjhFooBARsdjh FooBARsdjhFooBARsd +expected-stderr-pattern: + /^X*echo FooBARbc def FooBARsjj sFooBARdjhFooBARsdjh FooBARsdjhFooBARsd\nX*$/ +--- + +name: history-subst-5 +description: + Make sure searches don't find current (fc) command + (ksh88/ksh93 don't have the ? prefix thing so they fail this test) +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + echo ghi jkl + fc -e - abc=AB \?abc +expected-stdout: + abc def + ghi jkl + AB def +expected-stderr-pattern: + /^X*echo AB def\nX*$/ +--- + +name: history-ed-1 +description: + Basic (ed) editing works (assumes you have generic ed editor + that prints no prompts). +# No ed on os/2 (yet?). +category: !os:os2 +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + fc echo + s/abc/FOOBAR/ + w + q +expected-stdout: + abc def + 13 + 16 + FOOBAR def +expected-stderr-pattern: + /^X*echo FOOBAR def\nX*$/ +--- + +name: history-ed-2 +description: + Correct command is edited when number given +category: !os:os2 +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo line 1 + echo line 2 is here + echo line 3 + echo line 4 + fc 2 + s/is here/is changed/ + w + q +expected-stdout: + line 1 + line 2 is here + line 3 + line 4 + 20 + 23 + line 2 is changed +expected-stderr-pattern: + /^X*echo line 2 is changed\nX*$/ +--- + +name: history-ed-3 +description: + Newly created multi line commands show up as single command + in history. + (NOTE: will fail if using COMPLEX HISTORY compile time option) + (ksh88 fails 'cause it lists the fc command) +category: !os:os2 +arguments: !-i! +env-setup: !ENV=./Env!HISTFILE=hist.file! +file-setup: file 644 "Env" + PS1=X +stdin: + echo abc def + fc echo + s/abc/FOOBAR/ + $a + echo a new line + . + w + q + fc -l +expected-stdout: + abc def + 13 + 32 + FOOBAR def + a new line + 1 echo abc def + 2 echo FOOBAR def + echo a new line +expected-stderr-pattern: + /^X*echo FOOBAR def\necho a new line\nX*$/ +--- diff --git a/tests/ifs.t b/tests/ifs.t new file mode 100644 index 0000000..a927aa3 --- /dev/null +++ b/tests/ifs.t @@ -0,0 +1,162 @@ +name: IFS-space-1 +description: + Simple test, default IFS +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + set -- A B C + showargs 1 $* + showargs 2 "$*" + showargs 3 $@ + showargs 4 "$@" +expected-stdout: + <1> + <2> + <3> + <4> +--- + +name: IFS-colon-1 +description: + Simple test, IFS=: +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS=: + set -- A B C + showargs 1 $* + showargs 2 "$*" + showargs 3 $@ + showargs 4 "$@" +expected-stdout: + <1> + <2> + <3> + <4> +--- + +name: IFS-null-1 +description: + Simple test, IFS="" +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS="" + set -- A B C + showargs 1 $* + showargs 2 "$*" + showargs 3 $@ + showargs 4 "$@" +expected-stdout: + <1> + <2> + <3> + <4> +--- + +name: IFS-space-colon-1 +description: + Simple test, IFS=: +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS="IFS:" + set -- + showargs 1 $* + showargs 2 "$*" + showargs 3 $@ + showargs 4 "$@" + showargs 5 : "$@" +expected-stdout: + <1> + <2> <> + <3> + <4> + <5> <:> +--- + +name: IFS-space-colon-2 +description: + Simple test, IFS=: + At&t ksh fails this, POSIX says the test is correct. +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS="IFS:" + set -- + showargs :"$@" +expected-stdout: + <:> +--- + +name: IFS-space-colon-3 +description: + Simple test, IFS=: + pdksh fails both of these tests +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS="IFS:" + x= + set -- + showargs "$x$@" + showargs "$@$x" +expected-fail: yes +expected-stdout: + <> + <> +--- + +name: IFS-space-colon-4 +description: + Simple test, IFS=: +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS="IFS:" + set -- + showargs "$@$@" +expected-stdout: + +--- + +name: IFS-space-colon-5 +description: + Simple test, IFS=: + Don't know what POSIX thinks of this. at&t ksh does not do this. +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS="IFS:" + set -- + showargs "${@:-}" +expected-stdout: + <> +--- + +name: IFS-subst-1 +description: + Simple test, IFS=: +stdin: + showargs() { for i; do echo -n " <$i>"; done; echo; } + IFS="$IFS:" + x=":b: :" + echo -n '1:'; for i in $x ; do echo -n " [$i]" ; done ; echo + echo -n '2:'; for i in :b:: ; do echo -n " [$i]" ; done ; echo + showargs 3 $x + showargs 4 :b:: + x="a:b:" + echo -n '5:'; for i in $x ; do echo -n " [$i]" ; done ; echo + showargs 6 $x + x="a::c" + echo -n '7:'; for i in $x ; do echo -n " [$i]" ; done ; echo + showargs 8 $x + echo -n '9:'; for i in ${FOO-`echo -n h:i`th:ere} ; do echo -n " [$i]" ; done ; echo + showargs 10 ${FOO-`echo -n h:i`th:ere} + showargs 11 "${FOO-`echo -n h:i`th:ere}" +expected-stdout: + 1: [] [b] [] [] + 2: [:b::] + <3> <> <> <> + <4> <:b::> + 5: [a] [b] [] + <6> <> + 7: [a] [] [c] + <8> <> + 9: [h] [ith] [ere] + <10> + <11> +--- + diff --git a/tests/integer.t b/tests/integer.t new file mode 100644 index 0000000..7e39612 --- /dev/null +++ b/tests/integer.t @@ -0,0 +1,218 @@ +name: integer-base-err-1 +description: + Can't have 0 base (causes shell to exit) +expected-exit: e != 0 +stdin: + typeset -i i + i=3 + i=0#4 + echo $i +expected-stderr-pattern: + /^.*:.*0#4.*\n$/ +--- + +name: integer-base-err-2 +description: + Can't have multiple bases in a `constant' (causes shell to exit) + (ksh88 fails this test) +expected-exit: e != 0 +stdin: + typeset -i i + i=3 + i=2#110#11 + echo $i +expected-stderr-pattern: + /^.*:.*2#110#11.*\n$/ +--- + +name: integer-base-err-3 +description: + Syntax errors in expressions and effects on bases + (interactive so errors don't cause exits) + (ksh88 fails this test - shell exits, even with -i) +arguments: !-i! +stdin: + PS1= # minimize prompt hassles + typeset -i4 a=10 + typeset -i a=2+ + echo $a + typeset -i4 a=10 + typeset -i2 a=2+ + echo $a +expected-stderr-pattern: + /^([#\$] )?.*:.*2+.*\n.*:.*2+.*\n$/ +expected-stdout: + 4#22 + 4#22 +--- + +name: integer-base-err-4 +description: + Are invalid digits (according to base) errors? + (ksh93 fails this test) +expected-exit: e != 0 +stdin: + typeset -i i; + i=3#4 +expected-stderr-pattern: + /^([#\$] )?.*:.*3#4.*\n$/ +--- + + +name: integer-base-1 +description: + Missing number after base is treated as 0. +stdin: + typeset -i i + i=3 + i=2# + echo $i +expected-stdout: + 0 +--- + +name: integer-base-2 +description: + Check `stickyness' of base in various situations +stdin: + typeset -i i=8 + echo $i + echo ---------- A + typeset -i4 j=8 + echo $j + echo ---------- B + typeset -i k=8 + typeset -i4 k=8 + echo $k + echo ---------- C + typeset -i4 l + l=3#10 + echo $l + echo ---------- D + typeset -i m + m=3#10 + echo $m + echo ---------- E + n=2#11 + typeset -i n + echo $n + n=10 + echo $n + echo ---------- F + typeset -i8 o=12 + typeset -i4 o + echo $o + echo ---------- G + typeset -i p + let p=8#12 + echo $p +expected-stdout: + 8 + ---------- A + 4#20 + ---------- B + 4#20 + ---------- C + 4#3 + ---------- D + 3#10 + ---------- E + 2#11 + 2#1010 + ---------- F + 4#30 + ---------- G + 8#12 +--- + +name: integer-base-3 +description: + More base parsing (hmm doesn't test much..) +stdin: + typeset -i aa + aa=1+12#10+2 + echo $aa + typeset -i bb + bb=1+$aa + echo $bb + typeset -i bb + bb=$aa + echo $bb + typeset -i cc + cc=$aa + echo $cc +expected-stdout: + 15 + 16 + 15 + 15 +--- + +name: integer-base-4 +description: + Check that things not declared as integers are not made integers, + also, check if base is not reset by -i with no arguments. + (ksh93 fails - prints 10#20 - go figure) +stdin: + xx=20 + let xx=10 + typeset -i | grep '^xx=' + typeset -i4 a=10 + typeset -i a=20 + echo $a +expected-stdout: + 4#110 +--- + +name: integer-base-5 +description: + More base stuff +stdin: + typeset -i4 a=3#10 + echo $a + echo -- + typeset -i j=3 + j=~3 + echo $j + echo -- + typeset -i k=1 + x[k=k+1]=3 + echo $k + echo -- + typeset -i l + for l in 1 2+3 4; do echo $l; done +expected-stdout: + 4#3 + -- + -4 + -- + 2 + -- + 1 + 5 + 4 +--- + +name: integer-base-6 +description: + Even more base stuff + (ksh93 fails this test - prints 0) +stdin: + typeset -i7 i + i= + echo $i +expected-stdout: + 7#0 +--- + +name: integer-base-7 +description: + Check that non-integer parameters don't get bases assigned +stdin: + echo $(( zz = 8#100 )) + echo $zz +expected-stdout: + 64 + 64 +--- + diff --git a/tests/lineno.t b/tests/lineno.t new file mode 100644 index 0000000..eb7eab9 --- /dev/null +++ b/tests/lineno.t @@ -0,0 +1,111 @@ +name: lineno-stdin +description: + See if $LINENO is updated and can be modified. +stdin: + echo A $LINENO + echo B $LINENO + LINENO=20 + echo C $LINENO +expected-stdout: + A 1 + B 2 + C 20 +--- + +name: lineno-inc +description: + See if $LINENO is set for .'d files. +file-setup: file 644 "dotfile" + echo dot A $LINENO + echo dot B $LINENO + LINENO=20 + echo dot C $LINENO +stdin: + echo A $LINENO + echo B $LINENO + . ./dotfile +expected-stdout: + A 1 + B 2 + dot A 1 + dot B 2 + dot C 20 +--- + + +name: lineno-func +description: + See if $LINENO is set for commands in a function. +stdin: + echo A $LINENO + echo B $LINENO + bar() { + echo func A $LINENO + echo func B $LINENO + } + bar + echo C $LINENO +expected-stdout: + A 1 + B 2 + func A 4 + func B 5 + C 8 +--- + +name: lineno-unset +description: + See if unsetting LINENO makes it non-magic. +file-setup: file 644 "dotfile" + echo dot A $LINENO + echo dot B $LINENO +stdin: + unset LINENO + echo A $LINENO + echo B $LINENO + bar() { + echo func A $LINENO + echo func B $LINENO + } + bar + . ./dotfile + echo C $LINENO +expected-stdout: + A + B + func A + func B + dot A + dot B + C +--- + +name: lineno-unset-use +description: + See if unsetting LINENO makes it non-magic even + when it is re-used. +file-setup: file 644 "dotfile" + echo dot A $LINENO + echo dot B $LINENO +stdin: + unset LINENO + LINENO=3 + echo A $LINENO + echo B $LINENO + bar() { + echo func A $LINENO + echo func B $LINENO + } + bar + . ./dotfile + echo C $LINENO +expected-stdout: + A 3 + B 3 + func A 3 + func B 3 + dot A 3 + dot B 3 + C 3 +--- + diff --git a/tests/read.t b/tests/read.t new file mode 100644 index 0000000..5f1b28e --- /dev/null +++ b/tests/read.t @@ -0,0 +1,58 @@ +# $OpenBSD: read.t,v 1.3 2003/03/10 03:48:16 david Exp $ + +# +# To test: +# POSIX: +# - if no -r, \ is escape character +# - \newline disappear +# - \ -> don't break here +# - \ -> +# - if -r, backslash is not special +# - if stdin is tty and shell interactive +# - prompt for continuation if \newline (prompt to stderr) +# - a here-document isn't terminated after newline ???? +# - remaining vars set to empty string (not null) +# - check field splitting +# - left over fields and their separators assigned to last var +# - exit status is normally 0 +# - exit status is > 0 on eof +# - exit status > 0 on error +# - signals interrupt reads +# extra: +# - can't change read-only variables +# - error if var name bogus +# - set -o allexport effects read +# ksh: +# x check default variable: REPLY +# - check -p, -s, -u options +# - check var?prompt stuff +# - "echo a b | read x y" sets x,y in parent shell (at&t) +# +name: read-IFS-1 +description: + Simple test, default IFS +stdin: + echo "A B " > IN + unset x y z + read x y z < IN + echo 1: "x[$x] y[$y] z[$z]" + echo 1a: ${z-z not set} + read x < IN + echo 2: "x[$x]" +expected-stdout: + 1: x[A] y[B] z[] + 1a: + 2: x[A B] +--- + +name: read-ksh-1 +description: + If no var specified, REPLY is used +stdin: + echo "abc" > IN + read < IN + echo "[$REPLY]"; +expected-stdout: + [abc] +--- + diff --git a/tests/regress.t b/tests/regress.t new file mode 100644 index 0000000..c40976d --- /dev/null +++ b/tests/regress.t @@ -0,0 +1,1093 @@ +# $OpenBSD: regress.t,v 1.11 2001/01/28 23:04:56 niklas Exp $ + +# +# The first 39 of these tests are from the old Bugs script. +# + +name: regression-1 +description: + Lex array code had problems with this. +stdin: + echo foo[ + n=bar + echo "hi[ $n ]=1" +expected-stdout: + foo[ + hi[ bar ]=1 +--- + + +name: regression-2 +description: + When PATH is set before running a command, the new path is + not used in doing the path search + $ echo echo hi > /tmp/q ; chmod a+rx /tmp/q + $ PATH=/tmp q + q: not found + $ + in comexec() the two lines + while (*vp != NULL) + (void) typeset(*vp++, xxx, 0); + need to be moved out of the switch to before findcom() is + called - I don't know what this will break. +stdin: + : ${PWD:-`pwd 2> /dev/null`} + : ${PWD:?"PWD not set - can't do test"} + mkdir Y + cat > Y/xxxscript << EOF + #!/bin/sh + # Need to restore path so echo can be found (some shells don't have + # it as a built-in) + PATH=\$OLDPATH + echo hi + exit 0 + EOF + chmod a+rx Y/xxxscript + export OLDPATH="$PATH" + PATH=$PWD/Y xxxscript + exit $? +expected-stdout: + hi +--- + + +# +# 3. Sun OS 4.0.x (This seems to be a problem with sun's PENDIN not being done +# properly) +# sleep 5^J ls^J ls^J ls [only first ls runs] +# vi ... ZZ (while waiting type) [some of the input gets eaten] +# [not present in SunOS 4.1.x] +#echo " [No automatic test for bug 3 - interactive]" + + +# +# 4. (fixed) +# +#echo " [Don't know what bug 4 was]" + + +# +# 5. Everywhere +# File name completion (^X,*) does not mesh well with cd and +# symbolic links. cd does path simplification wrt $PWD before +# doing the actual chdir(), while file name completion does +# not do the simplification. E.g., you are in directory A +# which has a symbolic link to directory B, you create a file +# called foobar and you then cd to the symlink to B, and type +# $ echo ../foo^X +# and the shell beeps at you. Would be more consistent to +# do the completion after simplifing the `$PWD/..'. +#echo " [No automatic test for bug 5 - interactive]" + + +name: regression-6 +description: + Parsing of $(..) expressions is non-optimal. It is + impossible to have any parentheses inside the expression. + I.e., + $ ksh -c 'echo $(echo \( )' + no closing quote + $ ksh -c 'echo $(echo "(" )' + no closing quote + $ + The solution is to hack the parsing clode in lex.c, the + question is how to hack it: should any parentheses be + escaped by a backslash, or should recursive parsing be done + (so quotes could also be used to hide hem). The former is + easier, the later better... +stdin: + echo $(echo \() +expected-stdout: + ( +--- + + +# +# 7. (fixed) +# +#echo " [Don't know what bug 7 was]" + + +# +# 8. Everywhere - NOT A BUG - this is what at&t ksh88 does +# Strange typset -x behaviour in functions. The following function +# does not set the environment variable BLAH outside the function: +# function blah +# { +# typeset -x BLAH=foobar +# } +# This function does work: +# function blah +# { BLAH=foobar; export BLAH +# } +#echo ' [Bug 8 was bogus]' + + +name: regression-9 +description: + Continue in a for loop does not work right: + for i in a b c ; do + if [ $i = b ] ; then + continue + fi + echo $i + done + Prints a forever... +stdin: + first=yes + for i in a b c ; do + if [ $i = b ] ; then + if [ $first = no ] ; then + echo 'continue in for loop broken' + break # hope break isn't broken too :-) + fi + first=no + continue + fi + done + echo bye +expected-stdout: + bye +--- + + +name: regression-10 +description: + The following: + set -- `false` + echo $? + shoud not print 0. (according to /bin/sh, at&t ksh88, and the + getopt(1) man page - not according to POSIX) +stdin: + set -- `false` + echo $? +expected-stdout: + 1 +--- + + +name: regression-11 +description: + The following: + x=/foo/bar/blah + echo ${x##*/} + should echo blah but on some machines echos /foo/bar/blah. +stdin: + x=/foo/bar/blah + echo ${x##*/} +expected-stdout: + blah +--- + + +name: regression-12 +description: + Both of the following echos produce the same output under sh/ksh.att: + #!/bin/sh + x="foo bar" + echo "`echo \"$x\"`" + echo "`echo "$x"`" + pdksh produces different output for the former (foo instead of foo\tbar) +stdin: + x="foo bar" + echo "`echo \"$x\"`" + echo "`echo "$x"`" +expected-stdout: + foo bar + foo bar +--- + + +name: regression-13 +description: + The following command hangs forever: + $ (: ; cat /etc/termcap) | sleep 2 + This is because the shell forks a shell to run the (..) command + and this shell has the pipe open. When the sleep dies, the cat + doesn't get a SIGPIPE 'cause a process (ie, the second shell) + still has the pipe open. + + NOTE: this test provokes a bizarre bug in ksh93 (shell starts reading + commands from /etc/termcap..) +time-limit: 10 +stdin: + echo A line of text that will be duplicated quite a number of times.> t1 + cat t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 > t2 + cat t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 > t1 + cat t1 t1 t1 t1 > t2 + (: ; cat t2) | sleep 1 +--- + + +name: regression-14 +description: + The command + $ (foobar) 2> /dev/null + generates no output under /bin/sh, but pdksh produces the error + foobar: not found + Also, the command + $ foobar 2> /dev/null + generates an error under /bin/sh and pdksh, but at&t ksh88 produces + no error (redirected to /dev/null). +stdin: + (you/should/not/see/this/error/1) 2> /dev/null + you/should/not/see/this/error/2 2> /dev/null + true +--- + + +name: regression-15 +description: + The command + $ whence foobar + generates a blank line under pdksh and sets the exit status to 0. + at&t ksh88 generates no output and sets the exit status to 1. Also, + the command + $ whence foobar cat + generates no output under at&t ksh88 (pdksh generates a blank line + and /bin/cat). +stdin: + whence does/not/exist > /dev/null + echo 1: $? + echo 2: $(whence does/not/exist | wc -l) + echo 3: $(whence does/not/exist cat | wc -l) +expected-stdout: + 1: 1 + 2: 0 + 3: 0 +--- + + +name: regression-16 +description: + ${var%%expr} seems to be broken in many places. On the mips + the commands + $ read line < /etc/passwd + $ echo $line + root:0:1:... + $ echo ${line%%:*} + root + $ echo $line + root + $ + change the value of line. On sun4s & pas, the echo ${line%%:*} doesn't + work. Haven't checked elsewhere... +script: + read x + y=$x + echo ${x%%:*} + echo $x +stdin: + root:asdjhasdasjhs:0:1:Root:/:/bin/sh +expected-stdout: + root + root:asdjhasdasjhs:0:1:Root:/:/bin/sh +--- + + +name: regression-17 +description: + The command + . /foo/bar + should set the exit status to non-zero (sh and at&t ksh88 do). + XXX doting a non existant file is a fatal error for a script +stdin: + . does/not/exist +expected-exit: e != 0 +expected-stderr-pattern: /.?/ +--- + + +# +# 18. Everywhere +# In vi mode ^X (and *) can dump core: +# $ ab[cd^XMemory fault (core dumped) +#echo " [No automatic test for bug 18 - interactive]" + + +name: regression-19 +description: + Both of the following echos should produce the same thing, but don't: + $ x=foo/bar + $ echo ${x%/*} + foo + $ echo "${x%/*}" + foo/bar +stdin: + x=foo/bar + echo "${x%/*}" +expected-stdout: + foo +--- + + +# +# 20. (same as 18) +# + + +name: regression-21 +description: + backslash does not work as expected in case labels: + $ x='-x' + $ case $x in + -\?) echo hi + esac + hi + $ x='-?' + $ case $x in + -\\?) echo hi + esac + hi + $ +stdin: + case -x in + -\?) echo fail + esac +--- + + +name: regression-22 +description: + Quoting backquotes inside backquotes doesn't work: + $ echo `echo hi \`echo there\` folks` + asks for more info. sh and at&t ksh88 both echo + hi there folks +stdin: + echo `echo hi \`echo there\` folks` +expected-stdout: + hi there folks +--- + + +name: regression-23 +description: + )) is not treated `correctly': + $ (echo hi ; (echo there ; echo folks)) + missing (( + $ + instead of (as sh and ksh.att) + $ (echo hi ; (echo there ; echo folks)) + hi + there + folks + $ +stdin: + ( : ; ( : ; echo hi)) +expected-stdout: + hi +--- + + +# +# 24. strangeness with file name completion involving symlinks to nowhere +# $ mkdir foo foo/bar +# $ ln -s /stuff/junk foo/bar/xx +# $ echo foo/*/xx  +# (beep) +# $ +#echo " [No automatic test for bug 24 - interactive]" + + +name: regression-25 +description: + Check reading stdin in a while loop. The read should only read + a single line, not a whole stdio buffer; the cat should get + the rest. +stdin: + (echo a; echo b) | while read x ; do + echo $x + cat > /dev/null + done +expected-stdout: + a +--- + + +name: regression-26 +description: + Check reading stdin in a while loop. The read should read both + lines, not just the first. +script: + a= + while [ "$a" != xxx ] ; do + last=$x + read x + cat /dev/null | sed 's/x/y/' + a=x$a + done + echo $last +stdin: + a + b +expected-stdout: + b +--- + + +name: regression-27 +description: + The command + . /does/not/exist + should cause a script to exit. +stdin: + . does/not/exist + echo hi +expected-exit: e != 0 +expected-stderr-pattern: /does\/not\/exist/ +--- + + +name: regression-28 +description: + variable assignements not detected well +stdin: + a.x=1 echo hi +expected-exit: e != 0 +expected-stderr-pattern: /a\.x=1/ +--- + + +name: regression-29 +description: + alias expansion different from at&t ksh88 +stdin: + alias a='for ' b='i in' + a b hi ; do echo $i ; done +expected-stdout: + hi +--- + + +name: regression-30 +description: + strange characters allowed inside ${...} +stdin: + echo ${a{b}} +expected-exit: e != 0 +expected-stderr-pattern: /.?/ +--- + + +name: regression-31 +description: + Does read handle partial lines correctly +script: + a= ret= + while [ "$a" != xxx ] ; do + read x y z + ret=$? + a=x$a + done + echo "[$x]" + echo $ret +stdin: ! + a A aA + b B Bb + c +expected-stdout: + [c] + 1 +--- + + +name: regression-32 +description: + Does read set variables to null at eof? +script: + a= + while [ "$a" != xxx ] ; do + read x y z + a=x$a + done + echo 1: ${x-x not set} ${y-y not set} ${z-z not set} + echo 2: ${x:+x not null} ${y:+y not null} ${z:+z not null} +stdin: + a A Aa + b B Bb +expected-stdout: + 1: + 2: +--- + + +name: regression-33 +description: + Does umask print a leading 0 when umask is 3 digits? +stdin: + umask 222 + umask +expected-stdout: + 0222 +--- + + +# +# +# Does umask print a umask of 0 sanely? +# There is lots of variety here (0, 00, 000, and 0000 have all been +# seen in various shells...) +# +#echo ' [Bug 34 was bogus]' + + +name: regression-35 +description: + Tempory files used for here-docs in functions get trashed after + the function is parsed (before it is executed) +stdin: + f1() { + cat <<- EOF + F1 + EOF + f2() { + cat <<- EOF + F2 + EOF + } + } + f1 + f2 + unset -f f1 + f2 +expected-stdout: + F1 + F2 + F2 +--- + + +name: regression-36 +description: + Command substitution breaks reading in while loop + (test from ) +stdin: + (echo abcdef; echo; echo 123) | + while read line + do + # the following line breaks it + c=`echo $line | wc -c` + echo $c + done +expected-stdout: + 7 + 1 + 4 +--- + + +name: regression-37 +description: + Machines with broken times() (reported by ) + time does not report correct real time +stdin: + time sleep 1 +expected-stderr-pattern: !/^\s*0\.0[\s\d]+real|^\s*real[\s]+0+\.0/ +--- + + +name: regression-38 +description: + set -e doesn't ignore exit codes for if/while/until/&&/||/!. +arguments: !-e! +stdin: + if false; then echo hi ; fi + false || true + false && true + while false; do echo hi; done + echo ok +expected-stdout: + ok +--- + + +name: regression-39 +description: + set -e: errors in command substitutions aren't ignored + Not clear if they should be or not... +expected-fail: yes +arguments: !-e! +stdin: + echo `false; echo hi` +expected-stdout: + hi +--- + +name: regression-40 +description: + This used to cause a core dump +env-setup: !RANDOM=12! +stdin: + echo hi +expected-stdout: + hi +--- + +name: regression-41 +description: + foo should be set to bar (should not be empty) +stdin: + foo=` + echo bar` + echo "($foo)" +expected-stdout: + (bar) +--- + +name: regression-42 +description: + Can't use command line assignments to assign readonly parameters. +stdin: + foo=bar + readonly foo + foo=stuff env | grep '^foo' +expected-exit: e != 0 +expected-stderr-pattern: + /.*read *only.*/ +--- + +name: regression-43 +description: + Can subshells be prefixed by redirections (historical shells allow + this) +stdin: + < /dev/null (sed 's/^/X/') +--- + +name: regression-44 +description: + getopts sets OPTIND correctly for unparsed option +stdin: + set -- -a -a -x + while getopts :a optc; do + echo "OPTARG=$OPTARG, OPTIND=$OPTIND, optc=$optc." + done + echo done +expected-stdout: + OPTARG=, OPTIND=2, optc=a. + OPTARG=, OPTIND=3, optc=a. + OPTARG=x, OPTIND=3, optc=?. + done +--- + +name: regression-45 +description: + Parameter assignments with [] recognized correctly +stdin: + FOO=*[12] + BAR=abc[ + MORE=[abc] + JUNK=a[bc + echo "<$FOO>" + echo "<$BAR>" + echo "<$MORE>" + echo "<$JUNK>" +expected-stdout: + <*[12]> + + <[abc]> + +--- + +name: regression-46 +description: + Check that alias expansion works in command substitutions and + at the end of file. +stdin: + alias x='echo hi' + FOO="`x` " + echo "[$FOO]" + x +expected-stdout: + [hi ] + hi +--- + +name: regression-47 +description: + Check that aliases are fully read. +stdin: + alias x='echo hi; + echo there' + x + echo done +expected-stdout: + hi + there + done +--- + +name: regression-48 +description: + Check that (here doc) temp files are not left behind after an exec. +stdin: + mkdir foo || exit 1 + TMPDIR=$PWD/foo $0 <<- 'EOF' + x() { + sed 's/^/X /' << E_O_F + hi + there + folks + E_O_F + echo "done ($?)" + } + echo=echo; [ -x /bin/echo ] && echo=/bin/echo + exec $echo subtest-1 hi + EOF + echo subtest-1 foo/* + TMPDIR=$PWD/foo $0 <<- 'EOF' + echo=echo; [ -x /bin/echo ] && echo=/bin/echo + sed 's/^/X /' << E_O_F; exec $echo subtest-2 hi + a + few + lines + E_O_F + EOF + echo subtest-2 foo/* +expected-stdout: + subtest-1 hi + subtest-1 foo/* + X a + X few + X lines + subtest-2 hi + subtest-2 foo/* +--- + +name: regression-49 +description: + Check that unset params with attributes are reported by set, those + sans attributes are not. +stdin: + unset FOO BAR + echo X$FOO + export BAR + typeset -i BLAH + set | grep FOO + set | grep BAR + set | grep BLAH +expected-stdout: + X + BAR + BLAH +--- + +name: regression-50 +description: + Check that aliases do not use continuation prompt after trailing + semi-colon. +file-setup: file 644 "env" + PS1=Y + PS2=X +env-setup: !ENV=./env! +arguments: !-i! +stdin: + alias foo='echo hi ; ' + foo + foo echo there +expected-stdout: + hi + hi + there +expected-stderr: ! + YYYY +--- + +name: regression-51 +description: + Check that set allows both +o and -o options on same command line. +stdin: + set a b c + set -o noglob +o allexport + echo A: $*, * +expected-stdout: + A: a b c, * +--- + +name: regression-52 +description: + Check that globing works in pipelined commands +file-setup: file 644 "env" + PS1=P +file-setup: file 644 "abc" + stuff +env-setup: !ENV=./env! +arguments: !-i! +stdin: + sed 's/^/X /' < ab* + echo mark 1 + sed 's/^/X /' < ab* | sed 's/^/Y /' + echo mark 2 +expected-stdout: + X stuff + mark 1 + Y X stuff + mark 2 +expected-stderr: ! + PPPPP +--- + +name: regression-53 +description: + Check that getopts works in functions +stdin: + #!/bin/ksh + + bfunc() { + echo bfunc: enter "(args: $*; OPTIND=$OPTIND)" + while getopts B oc; do + case $oc in + (B) + echo bfunc: B option + ;; + (*) + echo bfunc: odd option "($oc)" + ;; + esac + done + echo bfunc: leave + } + + function kfunc { + echo kfunc: enter "(args: $*; OPTIND=$OPTIND)" + while getopts K oc; do + case $oc in + (K) + echo kfunc: K option + ;; + (*) + echo bfunc: odd option "($oc)" + ;; + esac + done + echo kfunc: leave + } + + set -- -f -b -k -l + echo "line 1: OPTIND=$OPTIND" + getopts kbfl optc + echo "line 2: ret=$?, optc=$optc, OPTIND=$OPTIND" + bfunc -BBB blah + echo "line 3: OPTIND=$OPTIND" + getopts kbfl optc + echo "line 4: ret=$?, optc=$optc, OPTIND=$OPTIND" + kfunc -KKK blah + echo "line 5: OPTIND=$OPTIND" + getopts kbfl optc + echo "line 6: ret=$?, optc=$optc, OPTIND=$OPTIND" + echo + + OPTIND=1 + set -- -fbkl + echo "line 10: OPTIND=$OPTIND" + getopts kbfl optc + echo "line 20: ret=$?, optc=$optc, OPTIND=$OPTIND" + bfunc -BBB blah + echo "line 30: OPTIND=$OPTIND" + getopts kbfl optc + echo "line 40: ret=$?, optc=$optc, OPTIND=$OPTIND" + kfunc -KKK blah + echo "line 50: OPTIND=$OPTIND" + getopts kbfl optc + echo "line 60: ret=$?, optc=$optc, OPTIND=$OPTIND" +expected-stdout: + line 1: OPTIND=1 + line 2: ret=0, optc=f, OPTIND=2 + bfunc: enter (args: -BBB blah; OPTIND=2) + bfunc: B option + bfunc: B option + bfunc: leave + line 3: OPTIND=2 + line 4: ret=0, optc=b, OPTIND=3 + kfunc: enter (args: -KKK blah; OPTIND=1) + kfunc: K option + kfunc: K option + kfunc: K option + kfunc: leave + line 5: OPTIND=3 + line 6: ret=0, optc=k, OPTIND=4 + + line 10: OPTIND=1 + line 20: ret=0, optc=f, OPTIND=2 + bfunc: enter (args: -BBB blah; OPTIND=2) + bfunc: B option + bfunc: B option + bfunc: leave + line 30: OPTIND=2 + line 40: ret=1, optc=?, OPTIND=2 + kfunc: enter (args: -KKK blah; OPTIND=1) + kfunc: K option + kfunc: K option + kfunc: K option + kfunc: leave + line 50: OPTIND=2 + line 60: ret=1, optc=?, OPTIND=2 +--- + + +name: regression-54 +description: + Check that ; is not required before the then in if (( ... )) then ... +stdin: + if (( 1 )) then + echo ok dparen + fi + if [[ -n 1 ]] then + echo ok dbrackets + fi +expected-stdout: + ok dparen + ok dbrackets +--- + + +name: regression-55 +description: + Check ${foo:%bar} is allowed (ksh88 allows it...) +stdin: + x=fooXbarXblah + echo 1 ${x%X*} + echo 2 ${x:%X*} + echo 3 ${x%%X*} + echo 4 ${x:%%X*} + echo 5 ${x#*X} + echo 6 ${x:#*X} + echo 7 ${x##*X} + echo 8 ${x:##*X} +expected-stdout: + 1 fooXbar + 2 fooXbar + 3 foo + 4 foo + 5 barXblah + 6 barXblah + 7 blah + 8 blah +--- + + +name: regression-56 +description: + Check eval vs substitution exit codes + (this is what ksh88 does) +stdin: + eval $(false) + echo A $? + eval ' $(false)' + echo B $? + eval " $(false)" + echo C $? + eval "eval $(false)" + echo D $? + eval 'eval '"$(false)" + echo E $? + IFS="$IFS:" + eval $(echo :; false) + echo F $? +expected-stdout: + A 1 + B 1 + C 1 + D 0 + E 0 + F 1 +--- + +name: regression-57 +description: + Check if typeset output is correct for + uninitialized array elements. +stdin: + typeset -i xxx[4] + echo A + typeset -i | grep xxx | sed 's/^/ /' + echo B + typeset | grep xxx | sed 's/^/ /' + + xxx[1]=2+5 + echo M + typeset -i | grep xxx | sed 's/^/ /' + echo N + typeset | grep xxx | sed 's/^/ /' +expected-stdout: + A + xxx + B + typeset -i xxx + M + xxx[1]=7 + N + typeset -i xxx +--- + +name: regression-58 +description: + Check if trap exit is ok (exit not mistaken for signal name) +stdin: + trap 'echo hi' exit + trap exit 1 +expected-stdout: + hi +--- + +name: regression-59 +description: + Check if ${#array[*]} is calculated correctly. +stdin: + a[12]=hi + a[8]=there + echo ${#a[*]} +expected-stdout: + 2 +--- + +name: regression-60 +description: + Check if default exit status is previous command +stdin: + (true; exit) + echo A $? + (false; exit) + echo B $? + ( (exit 103) ; exit) + echo C $? +expected-stdout: + A 0 + B 1 + C 103 +--- + +name: regression-61 +description: + Check if EXIT trap is executed for sub shells. +stdin: + trap 'echo parent exit' EXIT + echo start + (echo A; echo A last) + echo B + (echo C; trap 'echo sub exit' EXIT; echo C last) + echo parent last +expected-stdout: + start + A + A last + B + C + C last + sub exit + parent last + parent exit +--- + +name: regression-62 +description: + Check if test -nt/-ot succeeds if second(first) file is missing. +stdin: + touch a + test a -nt b && echo nt OK || echo nt BAD + test b -ot a && echo ot OK || echo ot BAD +expected-stdout: + nt OK + ot OK +--- + diff --git a/tests/syntax.t b/tests/syntax.t new file mode 100644 index 0000000..3fe2c81 --- /dev/null +++ b/tests/syntax.t @@ -0,0 +1,10 @@ +name: syntax-1 +description: + Check that lone ampersand is a syntax error +stdin: + & +expected-exit: e != 0 +expected-stderr-pattern: + /syntax error/ +--- + diff --git a/tests/th b/tests/th new file mode 100644 index 0000000..a8ef7a9 --- /dev/null +++ b/tests/th @@ -0,0 +1,1206 @@ +#!/usr/local/bin/perl +# $OpenBSD: th,v 1.9 2003/03/10 03:48:16 david Exp $ + + +# +# Test harness for pdksh tests. +# +# Example test: +# name: a-test +# description: +# a test to show how tests are done +# arguments: !-x!-f! +# stdin: +# echo -n * +# false +# expected-stdout: ! +# * +# expected-stderr: +# + echo -n * +# + false +# expected-exit: 1 +# --- +# This runs the test-program (eg, pdksh) with the arguments -x and -f, +# standard input is a file containing "echo hi*\nfalse\n". The program +# is expected to produce "hi*" (no trailing newline) on standard output, +# "+ echo hi*\n+false\n" on standard error, and an exit code of 1. +# +# +# Format of test files: +# - blank lines and lines starting with # are ignored +# - a test file contains a series of tests +# - a test is a series of tag:value pairs ended with a "---" line +# (leading/trailing spaces are stripped from the first line of value) +# - test tags are: +# Tag Flag Description +# ----- ---- ----------- +# name r The name of the test; should be unique +# description m What test does +# arguments M Arguments to pass to the program; +# default is no arguments. +# script m Value is written to a file which +# is passed as an argument to the program +# (after the arguments arguments) +# stdin m Value is written to a file which is +# used as standard-input for the program; +# default is to use /dev/null. +# perl-setup m Value is a perl script which is executed +# just before the test is run. Try to +# avoid using this... +# perl-cleanup m Value is a perl script which is executed +# just after the test is run. Try to +# avoid using this... +# env-setup M Value is a list of NAME=VALUE elements +# which are put in the environment before +# the test is run. If the =VALUE is +# missing, NAME is removed from the +# environment. Programs are run with +# the following minimal environment: +# USER, LOGNAME, HOME, PATH, SHELL +# (values taken from the environment of +# the test harness). +# file-setup mps Used to create files, directories +# and symlinks. First word is either +# file, dir or symlink; second word is +# permissions; this is followed by a +# quoted word that is the name of the +# file; the end-quote should be followed +# by a newline, then the file data +# (if any). The first word may be +# preceeded by a ! to strip the trailing +# newline in a symlink. +# file-result mps Used to verify a file, symlink or +# directory is created correctly. +# The first word is either +# file, dir or symlink; second word is +# expected permissions; third word +# is user-id; fourth is group-id; +# fifth is "exact" or "pattern" +# indicating whether the file contents +# which follow is to be matched exactly +# or if it is a regular expression. +# The fifth argument is the quoted name +# of the file that should be created. +# The end-quote should be followed +# by a newline, then the file data +# (if any). The first word may be +# preceeded by a ! to strip the trailing +# newline in the file contents. +# The permissions, user and group fields +# may be * meaning accept any value. +# time-limit Time limit - the program is sent a +# SIGKILL N seconds. Default is no +# limit. +# expected-fail `yes' if the test is expected to fail. +# expected-exit expected exit code. Can be a number, +# or a C expression using the variables +# e, s and w (exit code, termination +# signal, and status code). +# expected-stdout m What the test should generate on stdout; +# default is to expect no output. +# expected-stdout-pattern m A perl pattern which matches the +# expected output. +# expected-stderr m What the test should generate on stderr; +# default is to expect no output. +# expected-stderr-pattern m A perl pattern which matches the +# expected standard error. +# category m Specify a comma separated list of +# `categories' of program that the test +# is to be run for. A category can be +# negated by prefixing the name with a !. +# The idea is that some tests in a +# test suite may apply to a particular +# program version and shouldn't be run +# on other versions. The category(s) of +# the program being tested can be +# specified on the command line. +# One category os:XXX is predefined +# (XXX is the operating system name, +# eg, linux, dec_osf). +# Flag meanings: +# r tag is required (eg, a test must have a name tag). +# m value can be multiple lines. Lines must be prefixed with +# a tab. If the value part of the initial tag:value line is +# - empty: the initial blank line is stripped. +# - a lone !: the last newline in the value is stripped; +# M value can be multiple lines (prefixed by a tab) and consists +# of multiple fields, delimited by a field separator character. +# The value must start and end with the f-s-c. +# p tag takes parameters (used with m). +# s tag can be used several times. +# + +$os = defined $^O ? $^O : 'unknown'; + +require 'signal.ph' unless $os eq 'os2'; +require 'errno.ph' unless $os eq 'os2'; +require 'getopts.pl'; + +($prog = $0) =~ s#.*/##; + +$Usage = < 0): $opt_t\n" + if $opt_t !~ /^\d+$/ || $opt_t <= 0; + $default_time_limit = $opt_t; +} +$program_kludge = defined $opt_P ? $opt_P : 0; + +if (defined $opt_C) { + foreach $c (split(',', $opt_C)) { + $c =~ s/\s+//; + die "$prog: categories can't be negated on the command line\n" + if ($c =~ /^!/); + $categories{$c} = 1; + } +} + +# Note which tests are to be run. +%do_test = (); +grep($do_test{$_} = 1, @ARGV); +$all_tests = @ARGV == 0; + +# Set up a very minimal environment +%new_env = (); +foreach $env (('USER', 'LOGNAME', 'HOME', 'PATH', 'SHELL')) { + $new_env{$env} = $ENV{$env} if defined $ENV{$env}; +} +if (defined $opt_e) { + # XXX need a way to allow many -e arguments... + if ($opt_e =~ /^([a-zA-Z_]\w*)(|=(.*))$/) { + $new_env{$1} = $2 eq '' ? $ENV{$1} : $3; + } else { + die "$0: bad -e argument: $opt_e\n"; + } +} +%old_env = %ENV; + +# The following doesn't work with perl5... Need to do it explicitly - yuck. +#%ENV = %new_env; +foreach $k (keys(%ENV)) { + delete $ENV{$k}; +} +$ENV{$k} = $v while ($k,$v) = each %new_env; + +die "$prog: couldn't make directory $tempdir - $!\n" if !mkdir($tempdir, 0777); + +chop($pwd = `pwd 2> /dev/null`); +die "$prog: couldn't get current working directory\n" if $pwd eq ''; +die "$prog: couldn't cd to $pwd - $!\n" if !chdir($pwd); + +if (!$program_kludge) { + $test_prog = "$pwd/$test_prog" if substr($test_prog, 0, 1) ne '/'; + die "$prog: $test_prog is not executable - bye\n" + if (! -x $test_prog && $os ne 'os2'); +} + +@trap_sigs = ('TERM', 'QUIT', 'INT', 'PIPE', 'HUP'); +@SIG{@trap_sigs} = ('cleanup_exit') x @trap_sigs; +$child_kill_ok = 0; +$SIG{'ALRM'} = 'catch_sigalrm'; + +$| = 1; + +if (-d $test_set) { + $file_prefix_skip = length($test_set) + 1; + $ret = &process_test_dir($test_set); +} else { + $file_prefix_skip = 0; + $ret = &process_test_file($test_set); +} +&cleanup_exit() if !defined $ret; + +$tot_failed = $nfailed + $nxfailed; +$tot_passed = $npassed + $nxpassed; +if ($tot_failed || $tot_passed) { + print "Total failed: $tot_failed"; + print " ($nxfailed unexpected)" if $nxfailed; + print " (as expected)" if $nfailed && !$nxfailed; + print "\nTotal passed: $tot_passed"; + print " ($nxpassed unexpected)" if $nxpassed; + print "\n"; +} + +&cleanup_exit('ok'); + +sub +cleanup_exit +{ + local($sig, $exitcode) = ('', 1); + + if ($_[0] eq 'ok') { + $exitcode = 0; + } elsif ($_[0] ne '') { + $sig = $_[0]; + } + + unlink($tempi, $tempo, $tempe, $temps); + &scrub_dir($tempdir) if defined $tempdir; + rmdir($tempdir) if defined $tempdir; + + if ($sig) { + $SIG{$sig} = 'DEFAULT'; + kill $sig, $$; + return; + } + exit $exitcode; +} + +sub +catch_sigalrm +{ + $SIG{'ALRM'} = 'catch_sigalrm'; + kill(9, $child_pid) if $child_kill_ok; + $child_killed = 1; +} + +sub +process_test_dir +{ + local($dir) = @_; + local($ret, $file); + local(@todo) = (); + + if (!opendir(DIR, $dir)) { + print STDERR "$prog: can't open directory $dir - $!\n"; + return undef; + } + while (defined ($file = readdir(DIR))) { + push(@todo, $file) if $file =~ /^[^.].*\.t$/; + } + closedir(DIR); + + foreach $file (@todo) { + $file = "$dir/$file"; + if (-d $file) { + $ret = &process_test_dir($file); + } elsif (-f _) { + $ret = &process_test_file($file); + } + last if !defined $ret; + } + + return $ret; +} + +sub +process_test_file +{ + local($file) = @_; + local($ret); + + if (!open(IN, $file)) { + print STDERR "$prog: can't open $file - $!\n"; + return undef; + } + while (1) { + $ret = &read_test($file, IN, *test); + last if !defined $ret || !$ret; + next if !$all_tests && !$do_test{$test{'name'}}; + next if !&category_check(*test); + $ret = &run_test(*test); + last if !defined $ret; + } + close(IN); + + return $ret; +} + +sub +run_test +{ + local(*test) = @_; + local($name) = $test{':full-name'}; + + #print "Running test $name...\n" if $verbose; + + if (defined $test{'stdin'}) { + return undef if !&write_file($tempi, $test{'stdin'}); + $ifile = $tempi; + } else { + $ifile = '/dev/null'; + } + + if (defined $test{'script'}) { + return undef if !&write_file($temps, $test{'script'}); + } + + return undef if !&scrub_dir($tempdir); + + if (!chdir($tempdir)) { + print STDERR "$prog: couldn't cd to $tempdir - $!\n"; + return undef; + } + + if (defined $test{'file-setup'}) { + local($i); + local($type, $perm, $rest, $c, $len, $name); + + for ($i = 0; $i < $test{'file-setup'}; $i++) { + $val = $test{"file-setup:$i"}; + # + # format is: type perm "name" + # + ($type, $perm, $rest) = + split(' ', $val, 3); + $c = substr($rest, 0, 1); + $len = index($rest, $c, 1) - 1; + $name = substr($rest, 1, $len); + $rest = substr($rest, 2 + $len); + $perm = oct($perm) if $perm =~ /^\d+$/; + if ($type eq 'file') { + return undef if !&write_file($name, $rest); + if (!chmod($perm, $name)) { + print STDERR + "$prog:$test{':long-name'}: can't chmod $perm $name - $!\n"; + return undef; + } + } elsif ($type eq 'dir') { + if (!mkdir($name, $perm)) { + print STDERR + "$prog:$test{':long-name'}: can't mkdir $perm $name - $!\n"; + return undef; + } + } elsif ($type eq 'symlink') { + local($oumask) = umask($perm); + local($ret) = symlink($rest, $name); + umask($oumask); + if (!$ret) { + print STDERR + "$prog:$test{':long-name'}: couldn't create symlink $name - $!\n"; + return undef; + } + } + } + } + + if (defined $test{'perl-setup'}) { + eval $test{'perl-setup'}; + if ($@ ne '') { + print STDERR "$prog:$test{':long-name'}: error running perl-setup - $@\n"; + return undef; + } + } + + $pid = fork; + if (!defined $pid) { + print STDERR "$prog: can't fork - $!\n"; + return undef; + } + if (!$pid) { + @SIG{@trap_sigs} = ('DEFAULT') x @trap_sigs; + $SIG{'ALRM'} = 'DEFAULT'; + if (defined $test{'env-setup'}) { + local($var, $val, $i); + + foreach $var (split(substr($test{'env-setup'}, 0, 1), + $test{'env-setup'})) + { + $i = index($var, '='); + next if $i == 0 || $var eq ''; + if ($i < 0) { + delete $ENV{$var}; + } else { + $ENV{substr($var, 0, $i)} = substr($var, $i + 1); + } + } + } + if (!open(STDIN, "< $ifile")) { + print STDERR "$prog: couldn't open $ifile in child - $!\n"; + kill('TERM', $$); + } + if (!open(STDOUT, "> $tempo")) { + print STDERR "$prog: couldn't open $tempo in child - $!\n"; + kill('TERM', $$); + } + if (!open(STDERR, "> $tempe")) { + print STDOUT "$prog: couldn't open $tempe in child - $!\n"; + kill('TERM', $$); + } + if ($program_kludge) { + @argv = split(' ', $test_prog); + } else { + @argv = ($test_prog); + } + if (defined $test{'arguments'}) { + push(@argv, + split(substr($test{'arguments'}, 0, 1), + substr($test{'arguments'}, 1))); + } + push(@argv, $temps) if defined $test{'script'}; + exec(@argv); + print STDERR "$prog: couldn't execute $test_prog - $!\n"; + kill('TERM', $$); + exit(95); + } + $child_pid = $pid; + $child_killed = 0; + $child_kill_ok = 1; + alarm($test{'time-limit'}) if defined $test{'time-limit'}; + while (1) { + $xpid = waitpid($pid, 0); + $child_kill_ok = 0; + if ($xpid < 0) { + next if $! == &EINTR; + print STDERR "$prog: error waiting for child - $!\n"; + return undef; + } + last; + } + $status = $?; + alarm(0) if defined $test{'time-limit'}; + + $failed = 0; + $why = ''; + + if ($child_killed) { + $failed = 1; + $why .= "\ttest timed out (limit of $test{'time-limit'} seconds)\n"; + } + + $ret = &eval_exit($test{'long-name'}, $status, $test{'expected-exit'}); + return undef if !defined $ret; + if (!$ret) { + local($expl); + + $failed = 1; + if (($status & 0xff) == 0x7f) { + $expl = "stopped"; + } elsif (($status & 0xff)) { + $expl = "signal " . ($status & 0x7f); + } else { + $expl = "exit-code " . (($status >> 8) & 0xff); + } + $why .= + "\tunexpected exit status $status ($expl), expected $test{'expected-exit'}\n"; + } + + $tmp = &check_output($test{'long-name'}, $tempo, 'stdout', + $test{'expected-stdout'}, $test{'expected-stdout-pattern'}); + return undef if !defined $tmp; + if ($tmp ne '') { + $failed = 1; + $why .= $tmp; + } + + $tmp = &check_output($test{'long-name'}, $tempe, 'stderr', + $test{'expected-stderr'}, $test{'expected-stderr-pattern'}); + return undef if !defined $tmp; + if ($tmp ne '') { + $failed = 1; + $why .= $tmp; + } + + $tmp = &check_file_result(*test); + return undef if !defined $tmp; + if ($tmp ne '') { + $failed = 1; + $why .= $tmp; + } + + if (defined $test{'perl-cleanup'}) { + eval $test{'perl-cleanup'}; + if ($@ ne '') { + print STDERR "$prog:$test{':long-name'}: error running perl-cleanup - $@\n"; + return undef; + } + } + + if (!chdir($pwd)) { + print STDERR "$prog: couldn't cd to $pwd - $!\n"; + return undef; + } + + if ($failed) { + if (!$test{'expected-fail'}) { + print "FAIL $name\n"; + $nxfailed++; + } else { + print "fail $name (as expected)\n"; + $nfailed++; + } + $why = "\tDescription" + . &wrap_lines($test{'description'}, " (missing)\n") + . $why; + } elsif ($test{'expected-fail'}) { + print "PASS $name (unexpectedly)\n"; + $nxpassed++; + } else { + print "pass $name\n"; + $npassed++; + } + print $why if $verbose; + return 0; +} + +sub +category_check +{ + local(*test) = @_; + local($c); + + return 1 if (!defined $test{'category'}); + local($ok) = 0; + foreach $c (split(',', $test{'category'})) { + $c =~ s/\s+//; + if ($c =~ /^!/) { + $c = $'; + return 0 if (defined $categories{$c}); + } else { + $ok = 1 if (defined $categories{$c}); + } + } + return $ok; +} + +sub +scrub_dir +{ + local($dir) = @_; + local(@todo) = (); + local($file); + + if (!opendir(DIR, $dir)) { + print STDERR "$prog: couldn't open directory $dir - $!\n"; + return undef; + } + while (defined ($file = readdir(DIR))) { + push(@todo, $file) if $file ne '.' && $file ne '..'; + } + closedir(DIR); + foreach $file (@todo) { + $file = "$dir/$file"; + if (-d $file) { + return undef if !&scrub_dir($file); + if (!rmdir($file)) { + print STDERR "$prog: couldn't rmdir $file - $!\n"; + return undef; + } + } else { + if (!unlink($file)) { + print STDERR "$prog: couldn't unlink $file - $!\n"; + return undef; + } + } + } + return 1; +} + +sub +write_file +{ + local($file, $str) = @_; + + if (!open(TEMP, "> $file")) { + print STDERR "$prog: can't open $file - $!\n"; + return undef; + } + print TEMP $str; + if (!close(TEMP)) { + print STDERR "$prog: error writing $file - $!\n"; + return undef; + } + return 1; +} + +sub +check_output +{ + local($name, $file, $what, $expect, $expect_pat) = @_; + local($got) = ''; + local($why) = ''; + local($ret); + + if (!open(TEMP, "< $file")) { + print STDERR "$prog:$name($what): couldn't open $file after running program - $!\n"; + return undef; + } + while () { + $got .= $_; + } + close(TEMP); + return compare_output($name, $what, $expect, $expect_pat, $got); +} + +sub +compare_output +{ + local($name, $what, $expect, $expect_pat, $got) = @_; + local($why) = ''; + + if (defined $expect_pat) { + $_ = $got; + $ret = eval "$expect_pat"; + if ($@ ne '') { + print STDERR "$prog:$name($what): error evaluating $what pattern: $expect_pat - $@\n"; + return undef; + } + if (!$ret) { + $why = "\tunexpected $what - wanted pattern"; + $why .= &wrap_lines($expect_pat); + $why .= "\tgot"; + $why .= &wrap_lines($got); + } + } else { + $expect = '' if !defined $expect; + if ($got ne $expect) { + $why .= "\tunexpected $what - " . &first_diff($expect, $got) . "\n"; + $why .= "\twanted"; + $why .= &wrap_lines($expect); + $why .= "\tgot"; + $why .= &wrap_lines($got); + } + } + return $why; +} + +sub +wrap_lines +{ + local($str, $empty) = @_; + local($nonl) = substr($str, -1, 1) ne "\n"; + + return (defined $empty ? $empty : " nothing\n") if $str eq ''; + substr($str, 0, 0) = ":\n"; + $str =~ s/\n/\n\t\t/g; + if ($nonl) { + $str .= "\n\t[incomplete last line]\n"; + } else { + chop($str); + chop($str); + } + return $str; +} + +sub +first_diff +{ + local($exp, $got) = @_; + local($lineno, $char) = (1, 1); + local($i, $exp_len, $got_len); + local($ce, $cg); + + $exp_len = length($exp); + $got_len = length($got); + if ($exp_len != $got_len) { + if ($exp_len < $got_len) { + if (substr($got, 0, $exp_len) eq $exp) { + return "got too much output"; + } + } elsif (substr($exp, 0, $got_len) eq $got) { + return "got too little output"; + } + } + for ($i = 0; $i < $exp_len; $i++) { + $ce = substr($exp, $i, 1); + $cg = substr($got, $i, 1); + last if $ce ne $cg; + $char++; + if ($ce eq "\n") { + $lineno++; + $char = 1; + } + } + return "first difference: line $lineno, char $char (wanted '" + . &format_char($ce) . "', got '" + . &format_char($cg) . "'"; +} + +sub +format_char +{ + local($ch, $s); + + $ch = ord($_[0]); + if ($ch == 10) { + return '\n'; + } elsif ($ch == 13) { + return '\r'; + } elsif ($ch == 8) { + return '\b'; + } elsif ($ch == 9) { + return '\t'; + } elsif ($ch > 127) { + $ch -= 127; + $s = "M-"; + } else { + $s = ''; + } + if ($ch < 32) { + $s .= '^'; + $ch += ord('@'); + } elsif ($ch == 127) { + return $s . "^?"; + } + return $s . sprintf("%c", $ch); +} + +sub +eval_exit +{ + local($name, $status, $expect) = @_; + local($expr); + local($w, $e, $s) = ($status, ($status >> 8) & 0xff, $status & 0x7f); + + $e = -1000 if $status & 0xff; + $s = -1000 if $s == 0x7f; + if (!defined $expect) { + $expr = '$w == 0'; + } elsif ($expect =~ /^(|-)\d+$/) { + $expr = "\$e == $expect"; + } else { + $expr = $expect; + $expr =~ s/\b([wse])\b/\$$1/g; + $expr =~ s/\b(SIG[A-Z0-9]+)\b/&$1/g; + } + $w = eval $expr; + if ($@ ne '') { + print STDERR "$prog:$test{':long-name'}: bad expected-exit expression: $expect ($@)\n"; + return undef; + } + return $w; +} + +sub +read_test +{ + local($file, $in, *test) = @_; + local($field, $val, $flags, $do_chop, $need_redo, $start_lineno); + local(%cnt, $sfield); + + %test = (); + %cnt = (); + while (<$in>) { + next if /^\s*$/; + next if /^ *#/; + last if /^\s*---\s*$/; + $start_lineno = $. if !defined $start_lineno; + if (!/^([-\w]+):\s*(|\S|\S.*\S)\s*$/) { + print STDERR "$prog:$file:$.: unrecognized line\n"; + return undef; + } + ($field, $val) = ($1, $2); + $sfield = $field; + $flags = $test_fields{$field}; + if (!defined $flags) { + print STDERR "$prog:$file:$.: unrecognized field \"$field\"\n"; + return undef; + } + if ($flags =~ /s/) { + local($cnt) = $cnt{$field}++; + $test{$field} = $cnt{$field}; + $cnt = 0 if $cnt eq ''; + $sfield .= ":$cnt"; + } elsif (defined $test{$field}) { + print STDERR "$prog:$file:$.: multiple \"$field\" fields\n"; + return undef; + } + $do_chop = $flags !~ /m/; + $need_redo = 0; + if ($val eq '' || $val eq '!' || $flags =~ /p/) { + if ($flags =~ /[Mm]/) { + if ($flags =~ /p/) { + if ($val =~ /^!/) { + $do_chop = 1; + $val = $'; + } else { + $do_chop = 0; + } + if ($val eq '') { + print STDERR + "$prog:$file:$.: no parameters given for field \"$field\"\n"; + return undef; + } + } else { + if ($val eq '!') { + $do_chop = 1; + } + $val = ''; + } + while (<$in>) { + last if !/^\t/; + $val .= $'; + } + chop $val if $do_chop; + $do_chop = 1; + $need_redo = 1; + # + # Syntax check on fields that can several instances + # (can give useful line numbers this way) + # + if ($field eq 'file-setup') { + local($type, $perm, $rest, $c, $len, $name); + # + # format is: type perm "name" + # + if ($val !~ /^[ \t]*(\S+)[ \t]+(\S+)[ \t]+([^ \t].*)/) { + print STDERR + "$prog:$file:$.: bad paramter line for file-setup field\n"; + return undef; + } + ($type, $perm, $rest) = ($1, $2, $3); + if ($type !~ /^(file|dir|symlink)$/) { + print STDERR + "$prog:$file:$.: bad file type for file-setup: $type\n"; + return undef; + } + if ($perm !~ /^\d+$/) { + print STDERR + "$prog:$file:$.: bad permissions for file-setup: $type\n"; + return undef; + } + $c = substr($rest, 0, 1); + if (($len = index($rest, $c, 1) - 1) <= 0) { + print STDERR + "$prog:$file:$.: missing end quote for file name in file-setup: $rest\n"; + return undef; + } + $name = substr($rest, 1, $len); + if ($name =~ /^\// || $name =~ /(^|\/)\.\.(\/|$)/) { + # Note: this is not a security thing - just a sanity + # check - a test can still use symlinks to get at files + # outside the test directory. + print STDERR +"$prog:$file:$.: file name in file-setup is absolute or contains ..: $name\n"; + return undef; + } + } + if ($field eq 'file-result') { + local($type, $perm, $uid, $gid, $matchType, + $rest, $c, $len, $name); + # + # format is: type perm uid gid matchType "name" + # + if ($val !~ /^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S.*)/) { + print STDERR + "$prog:$file:$.: bad paramter line for file-result field\n"; + return undef; + } + ($type, $perm, $uid, $gid, $matchType, $rest) + = ($1, $2, $3, $4, $5, $6); + if ($type !~ /^(file|dir|symlink)$/) { + print STDERR + "$prog:$file:$.: bad file type for file-result: $type\n"; + return undef; + } + if ($perm !~ /^\d+$/ && $perm ne '*') { + print STDERR + "$prog:$file:$.: bad permissions for file-result: $perm\n"; + return undef; + } + if ($uid !~ /^\d+$/ && $uid ne '*') { + print STDERR + "$prog:$file:$.: bad user-id for file-result: $uid\n"; + return undef; + } + if ($gid !~ /^\d+$/ && $gid ne '*') { + print STDERR + "$prog:$file:$.: bad group-id for file-result: $gid\n"; + return undef; + } + if ($matchType !~ /^(exact|pattern)$/) { + print STDERR + "$prog:$file:$.: bad match type for file-result: $matchType\n"; + return undef; + } + $c = substr($rest, 0, 1); + if (($len = index($rest, $c, 1) - 1) <= 0) { + print STDERR + "$prog:$file:$.: missing end quote for file name in file-result: $rest\n"; + return undef; + } + $name = substr($rest, 1, $len); + if ($name =~ /^\// || $name =~ /(^|\/)\.\.(\/|$)/) { + # Note: this is not a security thing - just a sanity + # check - a test can still use symlinks to get at files + # outside the test directory. + print STDERR +"$prog:$file:$.: file name in file-result is absolute or contains ..: $name\n"; + return undef; + } + } + } elsif ($val eq '') { + print STDERR + "$prog:$file:$.: no value given for field \"$field\"\n"; + return undef; + } + } + $val .= "\n" if !$do_chop; + $test{$sfield} = $val; + redo if $need_redo; + } + if ($_ eq '') { + if (%test) { + print STDERR + "$prog:$file:$start_lineno: end-of-file while reading test\n"; + return undef; + } + return 0; + } + + while (($field, $val) = each %test_fields) { + if ($val =~ /r/ && !defined $test{$field}) { + print STDERR + "$prog:$file:$start_lineno: required field \"$field\" missing\n"; + return undef; + } + } + + $test{':full-name'} = substr($file, $file_prefix_skip) . ":$test{'name'}"; + $test{':long-name'} = "$file:$start_lineno:$test{'name'}"; + + # Syntax check on specific fields + if (defined $test{'expected-fail'}) { + if ($test{'expected-fail'} !~ /^(yes|no)$/) { + print STDERR + "$prog:$test{':long-name'}: bad value for expected-fail field\n"; + return undef; + } + $test{'expected-fail'} = $1 eq 'yes'; + } else { + $test{'expected-fail'} = 0; + } + if (defined $test{'arguments'}) { + local($firstc) = substr($test{'arguments'}, 0, 1); + + if (substr($test{'arguments'}, -1, 1) ne $firstc) { + print STDERR "$prog:$test{':long-name'}: arguments field doesn't start and end with the same character\n"; + return undef; + } + } + if (defined $test{'env-setup'}) { + local($firstc) = substr($test{'env-setup'}, 0, 1); + + if (substr($test{'env-setup'}, -1, 1) ne $firstc) { + print STDERR "$prog:$test{':long-name'}: env-setup field doesn't start and end with the same character\n"; + return undef; + } + } + if (defined $test{'expected-exit'}) { + local($val) = $test{'expected-exit'}; + + if ($val =~ /^(|-)\d+$/) { + if ($val < 0 || $val > 255) { + print STDERR "$prog:$test{':long-name'}: expected-exit value $val not in 0..255\n"; + return undef; + } + } elsif ($val !~ /^([\s<>+-=*%\/&|!()]|\b[wse]\b|\bSIG[A-Z0-9]+\b)+$/) { + print STDERR "$prog:$test{':long-name'}: bad expected-exit expression: $val\n"; + return undef; + } + } else { + $test{'expected-exit'} = 0; + } + if (defined $test{'expected-stdout'} + && defined $test{'expected-stdout-pattern'}) + { + print STDERR "$prog:$test{':long-name'}: can't use both expected-stdout and expected-stdout-pattern\n"; + return undef; + } + if (defined $test{'expected-stderr'} + && defined $test{'expected-stderr-pattern'}) + { + print STDERR "$prog:$test{':long-name'}: can't use both expected-stderr and expected-stderr-pattern\n"; + return undef; + } + if (defined $test{'time-limit'}) { + if ($test{'time-limit'} !~ /^\d+$/ || $test{'time-limit'} == 0) { + print STDERR + "$prog:$test{':long-name'}: bad value for time-limit field\n"; + return undef; + } + } elsif (defined $default_time_limit) { + $test{'time-limit'} = $default_time_limit; + } + + if (defined $known_tests{$test{'name'}}) { + print STDERR "$prog:$test{':long-name'}: warning: duplicate test name ${test{'name'}}\n"; + } + $known_tests{$test{'name'}} = 1; + + return 1; +} + +sub +tty_msg +{ + local($msg) = @_; + + open(TTY, "> /dev/tty") || return 0; + print TTY $msg; + close(TTY); + return 1; +} + +sub +never_called_funcs +{ + return 0; + &tty_msg("hi\n"); + &never_called_funcs(); + &catch_sigalrm(); + $old_env{'foo'} = 'bar'; + $internal_test_fields{'foo'} = 'bar'; +} + +sub +check_file_result +{ + local(*test) = @_; + + return '' if (!defined $test{'file-result'}); + + local($why) = ''; + local($i); + local($type, $perm, $uid, $gid, $rest, $c, $len, $name); + local(@stbuf); + + for ($i = 0; $i < $test{'file-result'}; $i++) { + $val = $test{"file-result:$i"}; + # + # format is: type perm "name" + # + ($type, $perm, $uid, $gid, $matchType, $rest) = + split(' ', $val, 6); + $c = substr($rest, 0, 1); + $len = index($rest, $c, 1) - 1; + $name = substr($rest, 1, $len); + $rest = substr($rest, 2 + $len); + $perm = oct($perm) if $perm =~ /^\d+$/; + + @stbuf = lstat($name); + if (!@stbuf) { + $why .= "\texpected $type \"$name\" not created\n"; + next; + } + if ($perm ne '*' && ($stbuf[2] & 07777) != $perm) { + $why .= "\t$type \"$name\" has unexpected permissions\n"; + $why .= sprintf("\t\texpected 0%o, found 0%o\n", + $perm, $stbuf[2] & 07777); + } + if ($uid ne '*' && $stbuf[4] != $uid) { + $why .= "\t$type \"$name\" has unexpected user-id\n"; + $why .= sprintf("\t\texpected %d, found %d\n", + $uid, $stbuf[4]); + } + if ($gid ne '*' && $stbuf[5] != $gid) { + $why .= "\t$type \"$name\" has unexpected group-id\n"; + $why .= sprintf("\t\texpected %d, found %d\n", + $gid, $stbuf[5]); + } + + if ($type eq 'file') { + if (-l _ || ! -f _) { + $why .= "\t$type \"$name\" is not a regular file\n"; + } else { + local $tmp = &check_output($test{'long-name'}, $name, + "$type contents in \"$name\"", + $matchType eq 'exact' ? $rest : undef + $matchType eq 'pattern' ? $rest : undef); + return undef if (!defined $tmp); + $why .= $tmp; + } + } elsif ($type eq 'dir') { + if ($rest !~ /^\s*$/) { + print STDERR "$prog:$test{':long-name'}: file-result test for directory $name should not have content specified\n"; + return undef; + } + if (-l _ || ! -d _) { + $why .= "\t$type \"$name\" is not a directory\n"; + } + } elsif ($type eq 'symlink') { + if (!-l _) { + $why .= "\t$type \"$name\" is not a symlink\n"; + } else { + local $content = readlink($name); + if (!defined $content) { + print STDERR "$prog:$test{':long-name'}: file-result test for $type $name failed - could not readlink - $!\n"; + return undef; + } + local $tmp = &compare_output($test{'long-name'}, + "$type contents in \"$name\"", + $matchType eq 'exact' ? $rest : undef + $matchType eq 'pattern' ? $rest : undef); + return undef if (!defined $tmp); + $why .= $tmp; + } + } + } + + return $why; +} diff --git a/tests/th-sh b/tests/th-sh new file mode 100644 index 0000000..fef8750 --- /dev/null +++ b/tests/th-sh @@ -0,0 +1,30 @@ +#!/bin/sh +# $OpenBSD: th-sh,v 1.2 2001/01/28 23:04:57 niklas Exp $ + + +# +# Simple script to find perl and run it +# + +# Avoid common problems with ENV (though perl shouldn't let it through) +# (can you believe some shells don't have an unset???) +unset ENV + +x=x +[ -x /bin/sh ] 2> /dev/null || x=f + +IFS=:$IFS +perl= +for i in $PATH; do + [ X"$i" = X ] && i=. + for j in perl perl4 perl5 ; do + [ -$x "$i/$j" ] && perl=$i/$j && break 2 + done +done + +[ X"$perl" = X ] && { + echo "$0: can't find perl - bye\n" 1>&2 + exit 1 + } + +exec $perl "$@" diff --git a/tests/th.sh b/tests/th.sh new file mode 100644 index 0000000..93b0758 --- /dev/null +++ b/tests/th.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# $OpenBSD: th.sh,v 1.4 2001/01/28 23:04:57 niklas Exp $ + + +# +# Simple script to find perl and run it +# + +# Avoid common problems with ENV (though perl shouldn't let it through) +# (can you believe some shells don't have an unset???) +unset ENV + +x=x +[ -x /bin/sh ] 2> /dev/null || x=f + +IFS=:$IFS +perl= +for i in $PATH; do + [ X"$i" = X ] && i=. + for j in perl perl4 perl5 ; do + [ -$x "$i/$j" ] && perl=$i/$j && break 2 + done +done + +[ X"$perl" = X ] && { + echo "$0: can't find perl - bye\n" 1>&2 + exit 1 + } + +exec $perl "$@" diff --git a/tests/unclass1.t b/tests/unclass1.t new file mode 100644 index 0000000..258c206 --- /dev/null +++ b/tests/unclass1.t @@ -0,0 +1,99 @@ +name: xxx-quoted-newline-1 +description: + Check that \ works inside of ${} +stdin: + abc=2 + echo ${ab\ + c} +expected-stdout: + 2 +--- + +name: xxx-quoted-newline-2 +description: + Check that \ works at the start of a here document +stdin: + cat << EO\ + F + hi + EOF +expected-stdout: + hi +--- + +name: xxx-quoted-newline-3 +description: + Check that \ works at the end of a here document +stdin: + cat << EOF + hi + EO\ + F +expected-stdout: + hi +--- + +name: xxx-multi-assignment-cmd +description: + Check that assignments in a command affect subsequent assignments + in the same command +stdin: + FOO=abc + FOO=123 BAR=$FOO + echo $BAR +expected-stdout: + 123 +--- + +name: xxx-exec-environment-1 +description: + Check to see if exec sets it's environment correctly +stdin: + FOO=bar exec env +expected-stdout-pattern: + /(^|.*\n)FOO=bar\n/ +--- + +name: xxx-exec-environment-2 +description: + Check to make sure exec doesn't change environment if a program + isn't exec-ed +# Under os/2, _emx_sig environment variable changes. +category: !os:os2 +stdin: + env > bar1 + FOO=bar exec; env > bar2 + cmp -s bar1 bar2 +--- + +name: xxx-what-do-you-call-this-1 +stdin: + echo "${foo:-"a"}*" +expected-stdout: + a* +--- + +name: xxx-prefix-strip-1 +stdin: + foo='a cdef' + echo ${foo#a c} +expected-stdout: + def +--- + +name: xxx-prefix-strip-2 +stdin: + set a c + x='a cdef' + echo ${x#$*} +expected-stdout: + def +--- + +name: xxx-variable-syntax-1 +stdin: + echo ${:} +expected-stderr-pattern: + /bad substitution/ +expected-exit: 1 +--- diff --git a/tests/unclass2.t b/tests/unclass2.t new file mode 100644 index 0000000..56be751 --- /dev/null +++ b/tests/unclass2.t @@ -0,0 +1,163 @@ +name: xxx-subsitution-eval-order +description: + Check order of evaluation of expressions +stdin: + i=1 x= y= + set -A A abc def GHI j G k + echo ${A[x=(i+=1)]#${A[y=(i+=2)]}} + echo $x $y +expected-stdout: + HI + 2 4 +--- + +name: xxx-set-option-1 +description: + Check option parsing in set +stdin: + set -vsA foo -- A 1 3 2 + echo ${foo[*]} +expected-stderr: + echo ${foo[*]} +expected-stdout: + 1 2 3 A +--- + +name: xxx-exec-1 +description: + Check that exec exits for built-ins +arguments: !-i! +stdin: + exec print hi + echo still herre +expected-stdout: + hi +expected-stderr-pattern: /.*/ +--- + +name: xxx-while-1 +description: + Check the return value of while loops + XXX need to do same for for/select/until loops +stdin: + i=x + while [ $i != xxx ] ; do + i=x$i + if [ $i = xxx ] ; then + false + continue + fi + done + echo loop1=$? + + i=x + while [ $i != xxx ] ; do + i=x$i + if [ $i = xxx ] ; then + false + break + fi + done + echo loop2=$? + + i=x + while [ $i != xxx ] ; do + i=x$i + false + done + echo loop3=$? +expected-stdout: + loop1=0 + loop2=0 + loop3=1 +--- + +name: xxx-status-1 +description: + Check that blank lines don't clear $? +arguments: !-i! +stdin: + (exit 1) + echo $? + (exit 1) + + echo $? + true +expected-stdout: + 1 + 1 +expected-stderr-pattern: /.*/ +--- + +name: xxx-status-2 +description: + Check that $? is preserved in subshells, includes, traps. +stdin: + (exit 1) + + echo blank: $? + + (exit 2) + (echo subshell: $?) + + echo 'echo include: $?' > foo + (exit 3) + . ./foo + + trap 'echo trap: $?' ERR + (exit 4) + echo exit: $? +expected-stdout: + blank: 1 + subshell: 2 + include: 3 + trap: 4 + exit: 4 +--- + +name: xxx-clean-chars-1 +description: + Check MAGIC character is stuffed correctly +stdin: + echo `echo [£` +expected-stdout: + [£ +--- + +name: xxx-param-subst-qmark-1 +description: + Check suppresion of error message with null string. According to + POSIX, it shouldn't print the error as `word' isn't ommitted. +stdin: + unset foo + x= + echo x${foo?$x} +expected-exit: 1 +expected-fail: yes +expected-stderr-pattern: !/not set/ +--- + +name: xxx-param-_-1 +description: + Check c flag is set. +arguments: !-c!echo "[$-]"! +expected-stdout-pattern: /^\[.*c.*\]$/ +--- + +name: env-prompt +description: + Check that prompt not printed when processing ENV +env-setup: !ENV=./foo! +file-setup: file 644 "foo" + XXX=_ + PS1=X + false && echo hmmm +arguments: !-i! +stdin: + echo hi${XXX}there +expected-stdout: + hi_there +expected-stderr: ! + XX +--- + diff --git a/tests/version.t b/tests/version.t new file mode 100644 index 0000000..cb70c66 --- /dev/null +++ b/tests/version.t @@ -0,0 +1,9 @@ +name: version-1 +description: + Check version of shell. +category: pdksh +stdin: + echo $KSH_VERSION +expected-stdout: + @(#)PD KSH v5.2.14 99/07/13.2 +--- diff --git a/trap.c b/trap.c new file mode 100644 index 0000000..010e79e --- /dev/null +++ b/trap.c @@ -0,0 +1,451 @@ +/* $OpenBSD: trap.c,v 1.13 2003/02/28 09:45:09 jmc Exp $ */ + +/* + * signal handling + */ + +/* Kludge to avoid bogus re-declaration of sigtraps[] error on AIX 3.2.5 */ +#define FROM_TRAP_C +#include "sh.h" + +/* Table is indexed by signal number + * + * The script siglist.sh generates siglist.out, which is a sorted, complete + * list of signals + */ +Trap sigtraps[SIGNALS+1] = { + { SIGEXIT_, "EXIT", "Signal 0" }, +#include "siglist.out" /* generated by siglist.sh */ + { SIGERR_, "ERR", "Error handler" }, + }; + +static struct sigaction Sigact_ign, Sigact_trap; + +void +inittraps() +{ +#ifdef HAVE_SYS_SIGLIST +# ifndef SYS_SIGLIST_DECLARED + extern char *sys_siglist[]; +# endif + int i; + + /* Use system description, if available, for unknown signals... */ + for (i = 0; i < NSIG; i++) + if (!sigtraps[i].name && sys_siglist[i] && sys_siglist[i][0]) + sigtraps[i].mess = sys_siglist[i]; +#endif /* HAVE_SYS_SIGLIST */ + + sigemptyset(&Sigact_ign.sa_mask); + Sigact_ign.sa_flags = KSH_SA_FLAGS; + 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); +} + +#ifdef KSH +static RETSIGTYPE alarm_catcher ARGS((int sig)); + +void +alarm_init() +{ + sigtraps[SIGALRM].flags |= TF_SHELL_USES; + setsig(&sigtraps[SIGALRM], alarm_catcher, + SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP); +} + +static RETSIGTYPE +alarm_catcher(sig) + 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_; + return RETSIGVAL; +} +#endif /* KSH */ + +Trap * +gettrap(name, igncase) + const char *name; + int igncase; +{ + int i; + register Trap *p; + + if (digit(*name)) { + int n; + + if (getn(name, &n) && 0 <= n && n < SIGNALS) + return &sigtraps[n]; + return NULL; + } + for (p = sigtraps, i = SIGNALS+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 + */ +RETSIGTYPE +trapsig(i) + 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); +#ifdef V7_SIGNALS + if (sigtraps[i].cursig == trapsig) /* this for SIGCHLD,SIGALRM */ + sigaction(i, &Sigact_trap, (struct sigaction *) 0); +#endif /* V7_SIGNALS */ + errno = errno_; + return RETSIGVAL; +} + +/* called when we want to allow the user to ^C out of something - won't + * work if user has trapped SIGINT. + */ +void +intrcheck() +{ + 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() +{ + int i; + Trap *p; + + /* todo: should check if signal is fatal, not the TF_DFL_INTR flag */ + for (p = sigtraps, i = SIGNALS+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() +{ + int i; + Trap *p; + + for (p = sigtraps, i = SIGNALS+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(flag) + int flag; +{ + int i; + register Trap *p; + +#ifdef KSH + 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; +#endif /* KSH */ + if (!flag) + trap = 0; + if (flag & TF_DFL_INTR) + intrsig = 0; + if (flag & TF_FATAL) + fatal_trap = 0; + for (p = sigtraps, i = SIGNALS+1; --i >= 0; p++) + if (p->set && (!flag + || ((p->flags & flag) && p->trap == (char *) 0))) + runtrap(p); +} + +void +runtrap(p) + Trap *p; +{ + int i = p->signal; + char *trapstr = p->trap; + int oexstat; + int UNINITIALIZED(old_changed); + + 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); + 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() +{ + int i; + Trap *p; + + trap = 0; + intrsig = 0; + fatal_trap = 0; + for (i = SIGNALS+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() +{ + int i; + Trap *p; + + for (i = SIGNALS+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(p, s) + Trap *p; + char *s; +{ + handler_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() +{ + 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(restore_dfl) + 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(p, f, flags) + Trap *p; + handler_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 = (handler_t) 0; + if (flags & SS_SHTRAP) { + p->shtrap = f; + f = trapsig; + } + + if (p->cursig != f) { + p->cursig = f; + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = KSH_SA_FLAGS; + sigact.sa_handler = f; + sigaction(p->signal, &sigact, (struct sigaction *) 0); + } + + return 1; +} + +/* control what signal is set to before an exec() */ +void +setexecsig(p, restore) + 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 new file mode 100644 index 0000000..e412790 --- /dev/null +++ b/tree.c @@ -0,0 +1,760 @@ +/* $OpenBSD: tree.c,v 1.10 2002/02/27 19:37:09 dhartmei Exp $ */ + +/* + * command tree climbing + */ + +#include "sh.h" + +#define INDENT 4 + +#define tputc(c, shf) shf_putchar(c, shf); +static void ptree ARGS((struct op *t, int indent, struct shf *f)); +static void pioact ARGS((struct shf *f, int indent, struct ioword *iop)); +static void tputC ARGS((int c, struct shf *shf)); +static void tputS ARGS((char *wp, struct shf *shf)); +static void vfptreef ARGS((struct shf *shf, int indent, const char *fmt, va_list va)); +static struct ioword **iocopy ARGS((struct ioword **iow, Area *ap)); +static void iofree ARGS((struct ioword **iow, Area *ap)); + +/* + * print a command tree + */ + +static void +ptree(t, indent, shf) + register struct op *t; + int indent; + register struct shf *shf; +{ + register 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; + } +#ifdef KSH + case TSELECT: + fptreef(shf, indent, "select %s ", t->str); + /* fall through */ +#endif /* KSH */ + 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, ""); + 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(shf, indent, iop) + register struct shf *shf; + int indent; + register 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(c, shf) + register int c; + register 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(wp, shf) + register char *wp; + register struct shf *shf; +{ + register 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; +#ifdef KSH + case OPAT: + tputc(*wp++, shf); + tputc('(', shf); + break; + case SPAT: + tputc('|', shf); + break; + case CPAT: + tputc(')', shf); + break; +#endif /* KSH */ + } +} + +/* + * this is the _only_ way to reliably handle + * variable args with an ANSI compiler + */ +/* VARARGS */ +int +#ifdef HAVE_PROTOTYPES +fptreef(struct shf *shf, int indent, const char *fmt, ...) +#else +fptreef(shf, indent, fmt, va_alist) + struct shf *shf; + int indent; + const char *fmt; + va_dcl +#endif +{ + va_list va; + + SH_VA_START(va, fmt); + + vfptreef(shf, indent, fmt, va); + va_end(va); + return 0; +} + +/* VARARGS */ +char * +#ifdef HAVE_PROTOTYPES +snptreef(char *s, int n, const char *fmt, ...) +#else +snptreef(s, n, fmt, va_alist) + char *s; + int n; + const char *fmt; + va_dcl +#endif +{ + va_list va; + struct shf shf; + + shf_sopen(s, n, SHF_WR | (s ? 0 : SHF_DYNAMIC), &shf); + + SH_VA_START(va, fmt); + vfptreef(&shf, 0, fmt, va); + va_end(va); + + return shf_sclose(&shf); /* null terminates */ +} + +static void +vfptreef(shf, indent, fmt, va) + register struct shf *shf; + int indent; + const char *fmt; + register va_list va; +{ + register int c; + + while ((c = *fmt++)) + if (c == '%') { + register long n; + register 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(t, ap) + register struct op *t; + Area *ap; +{ + register struct op *r; + register 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(wp, ap) + 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(wp, c) + register const char *wp; + register int c; +{ + register 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; +#ifdef KSH + case OPAT: + nest++; + wp++; + break; + case SPAT: + case CPAT: + if (c == wp[-1] && nest == 0) + return (char *) wp; + if (wp[-1] == CPAT) + nest--; + break; +#endif /* KSH */ + 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(wp) + 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; +#ifdef KSH + case OPAT: + shf_putchar(*wp++, &shf); + shf_putchar('(', &shf); + break; + case SPAT: + shf_putchar('|', &shf); + break; + case CPAT: + shf_putchar(')', &shf); + break; +#endif /* KSH */ + } +} + +static struct ioword ** +iocopy(iow, ap) + register struct ioword **iow; + Area *ap; +{ + register struct ioword **ior; + register int i; + + for (ior = iow; *ior++ != NULL; ) + ; + ior = (struct ioword **) alloc((ior - iow + 1) * sizeof(*ior), ap); + + for (i = 0; iow[i] != NULL; i++) { + register 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(t, ap) + register struct op *t; + Area *ap; +{ + register 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(iow, ap) + struct ioword **iow; + Area *ap; +{ + register struct ioword **iop; + register 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); + } +} diff --git a/tree.h b/tree.h new file mode 100644 index 0000000..f6c13f1 --- /dev/null +++ b/tree.h @@ -0,0 +1,142 @@ +/* $OpenBSD: tree.h,v 1.7 1999/07/14 13:37:24 millert 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) /* timeing TCOM command */ +#define XINTACT BIT(11) /* OS2: proc started from interactive session */ + +/* + * 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 new file mode 100644 index 0000000..00a94cd --- /dev/null +++ b/tty.c @@ -0,0 +1,179 @@ +/* $OpenBSD: tty.c,v 1.2 1996/10/01 02:05:51 downsj Exp $ */ + +#include "sh.h" +#include "ksh_stat.h" +#define EXTERN +#include "tty.h" +#undef EXTERN + +int +get_tty(fd, ts) + int fd; + TTY_state *ts; +{ + int ret; + +# ifdef HAVE_TERMIOS_H + ret = tcgetattr(fd, ts); +# else /* HAVE_TERIOS_H */ +# ifdef HAVE_TERMIO_H + ret = ioctl(fd, TCGETA, ts); +# else /* HAVE_TERMIO_H */ + ret = ioctl(fd, TIOCGETP, &ts->sgttyb); +# ifdef TIOCGATC + if (ioctl(fd, TIOCGATC, &ts->lchars) < 0) + ret = -1; +# else + if (ioctl(fd, TIOCGETC, &ts->tchars) < 0) + ret = -1; +# ifdef TIOCGLTC + if (ioctl(fd, TIOCGLTC, &ts->ltchars) < 0) + ret = -1; +# endif /* TIOCGLTC */ +# endif /* TIOCGATC */ +# endif /* HAVE_TERMIO_H */ +# endif /* HAVE_TERIOS_H */ + return ret; +} + +int +set_tty(fd, ts, flags) + int fd; + TTY_state *ts; + int flags; +{ + int ret = 0; + +# ifdef HAVE_TERMIOS_H + ret = tcsetattr(fd, TCSADRAIN, ts); +# else /* HAVE_TERIOS_H */ +# ifdef HAVE_TERMIO_H +# ifndef TCSETAW /* e.g. Cray-2 */ + /* first wait for output to drain */ +# ifdef TCSBRK + if (ioctl(tty_fd, TCSBRK, 1) < 0) + ret = -1; +# else /* the following kludge is minimally intrusive, but sometimes fails */ + if (flags & TF_WAIT) + sleep((unsigned)1); /* fake it */ +# endif +# endif /* !TCSETAW */ +# if defined(_BSD_SYSV) || !defined(TCSETAW) +/* _BSD_SYSV must force TIOCSETN instead of TIOCSETP (preserve type-ahead) */ + if (ioctl(tty_fd, TCSETA, ts) < 0) + ret = -1; +# else + if (ioctl(tty_fd, TCSETAW, ts) < 0) + ret = -1; +# endif +# else /* HAVE_TERMIO_H */ +# if defined(__mips) && (defined(_SYSTYPE_BSD43) || defined(__SYSTYPE_BSD43)) + /* Under RISC/os 5.00, bsd43 environment, after a tty driver + * generated interrupt (eg, INTR, TSTP), all output to tty is + * lost until a SETP is done (there must be a better way of + * doing this...). + */ + if (flags & TF_MIPSKLUDGE) + ret = ioctl(fd, TIOCSETP, &ts->sgttyb); + else +# endif /* _SYSTYPE_BSD43 */ + ret = ioctl(fd, TIOCSETN, &ts->sgttyb); +# ifdef TIOCGATC + if (ioctl(fd, TIOCSATC, &ts->lchars) < 0) + ret = -1; +# else + if (ioctl(fd, TIOCSETC, &ts->tchars) < 0) + ret = -1; +# ifdef TIOCGLTC + if (ioctl(fd, TIOCSLTC, &ts->ltchars) < 0) + ret = -1; +# endif /* TIOCGLTC */ +# endif /* TIOCGATC */ +# endif /* HAVE_TERMIO_H */ +# endif /* HAVE_TERIOS_H */ + return ret; +} + + +/* Initialize tty_fd. Used for saving/reseting tty modes upon + * foreground job completion and for setting up tty process group. + */ +void +tty_init(init_ttystate) + int init_ttystate; +{ + int do_close = 1; + int tfd; + + if (tty_fd >= 0) { + close(tty_fd); + tty_fd = -1; + } + tty_devtty = 1; + + /* SCO can't job control on /dev/tty, so don't try... */ +#if !defined(__SCO__) + if ((tfd = open("/dev/tty", O_RDWR, 0)) < 0) { +#ifdef __NeXT + /* rlogin on NeXT boxes does not set up the controlling tty, + * so force it to be done here... + */ + { + extern char *ttyname ARGS((int)); + char *s = ttyname(isatty(2) ? 2 : 0); + int fd; + + if (s && (fd = open(s, O_RDWR, 0)) >= 0) { + close(fd); + tfd = open("/dev/tty", O_RDWR, 0); + } + } +#endif /* __NeXT */ + +/* X11R5 xterm on mips doesn't set controlling tty properly - temporary hack */ +# if !defined(__mips) || !(defined(_SYSTYPE_BSD43) || defined(__SYSTYPE_BSD43)) + if (tfd < 0) { + tty_devtty = 0; + warningf(FALSE, + "No controlling tty (open /dev/tty: %s)", + strerror(errno)); + } +# endif /* __mips */ + } +#else /* !__SCO__ */ + tfd = -1; +#endif /* __SCO__ */ + + 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 = ksh_dupbase(tfd, FDBASE)) < 0) { + warningf(FALSE, "j_ttyinit: dup of tty fd failed: %s", + strerror(errno)); + } else if (fd_clexec(tty_fd) < 0) { + warningf(FALSE, "j_ttyinit: can't set close-on-exec flag: %s", + strerror(errno)); + close(tty_fd); + tty_fd = -1; + } else if (init_ttystate) + get_tty(tty_fd, &tty_state); + if (do_close) + close(tfd); +} + +void +tty_close() +{ + if (tty_fd >= 0) { + close(tty_fd); + tty_fd = -1; + } +} diff --git a/tty.h b/tty.h new file mode 100644 index 0000000..a6cd2da --- /dev/null +++ b/tty.h @@ -0,0 +1,109 @@ +/* $OpenBSD: tty.h,v 1.2 1996/11/21 07:59:36 downsj 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 + +/* Don't know of a system on which including sys/ioctl.h with termios.h + * causes problems. If there is one, these lines need to be deleted and + * aclocal.m4 needs to have stuff un-commented. + */ +#ifdef SYS_IOCTL_WITH_TERMIOS +# define SYS_IOCTL_WITH_TERMIOS +#endif /* SYS_IOCTL_WITH_TERMIOS */ +#ifdef SYS_IOCTL_WITH_TERMIO +# define SYS_IOCTL_WITH_TERMIO +#endif /* SYS_IOCTL_WITH_TERMIO */ + +#ifdef HAVE_TERMIOS_H +# include +# ifdef SYS_IOCTL_WITH_TERMIOS +# if !(defined(sun) && !defined(__svr4__)) /* too many warnings on sunos */ + /* Need to include sys/ioctl.h on some systems to get the TIOCGWINSZ + * stuff (eg, digital unix). + */ +# include +# endif /* !(sun && !__svr4__) */ +# endif /* SYS_IOCTL_WITH_TERMIOS */ +typedef struct termios TTY_state; +#else +# ifdef HAVE_TERMIO_H +# include +# ifdef SYS_IOCTL_WITH_TERMIO +# include /* see comment above in termios stuff */ +# endif /* SYS_IOCTL_WITH_TERMIO */ +# if _BSD_SYSV /* BRL UNIX System V emulation */ +# ifndef NTTYDISC +# define TIOCGETD _IOR( 't', 0, int ) +# define TIOCSETD _IOW( 't', 1, int ) +# define NTTYDISC 2 +# endif +# ifndef TIOCSTI +# define TIOCSTI _IOW( 't', 114, char ) +# endif +# ifndef TIOCSPGRP +# define TIOCSPGRP _IOW( 't', 118, int ) +# endif +# endif /* _BSD_SYSV */ +typedef struct termio TTY_state; +# else /* HAVE_TERMIO_H */ +/* Assume BSD tty stuff. Uses TIOCGETP, TIOCSETN; uses TIOCGATC/TIOCSATC if + * available, otherwise it uses TIOCGETC/TIOCSETC (also uses TIOCGLTC/TIOCSLTC + * if available) + */ +# ifdef _MINIX +# include +# define TIOCSETN TIOCSETP +# else +# include +# endif +typedef struct { + struct sgttyb sgttyb; +# ifdef TIOCGATC + struct lchars lchars; +# else /* TIOCGATC */ + struct tchars tchars; +# ifdef TIOCGLTC + struct ltchars ltchars; +# endif /* TIOCGLTC */ +# endif /* TIOCGATC */ +} TTY_state; +# endif /* HAVE_TERMIO_H */ +#endif /* HAVE_TERMIOS_H */ + +/* Flags for set_tty() */ +#define TF_NONE 0x00 +#define TF_WAIT 0x01 /* drain output, even it requires sleep() */ +#define TF_MIPSKLUDGE 0x02 /* kludge to unwedge RISC/os 5.0 tty driver */ + +EXTERN int tty_fd I__(-1); /* dup'd tty file descriptor */ +EXTERN int tty_devtty; /* true if tty_fd is from /dev/tty */ +EXTERN TTY_state tty_state; /* saved tty state */ + +extern int get_tty ARGS((int fd, TTY_state *ts)); +extern int set_tty ARGS((int fd, TTY_state *ts, int flags)); +extern void tty_init ARGS((int init_ttystate)); +extern void tty_close ARGS((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 new file mode 100644 index 0000000..98faa2c --- /dev/null +++ b/var.c @@ -0,0 +1,1236 @@ +/* $OpenBSD: var.c,v 1.13 2003/03/13 09:03:07 deraadt Exp $ */ + +#include "sh.h" +#include "ksh_time.h" +#include "ksh_limval.h" +#include "ksh_stat.h" +#include + +/* + * 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 ARGS((struct tbl *vp, const char *s)); +static void export ARGS((struct tbl *vp, const char *val)); +static int special ARGS((const char *name)); +static void unspecial ARGS((const char *name)); +static void getspec ARGS((struct tbl *vp)); +static void setspec ARGS((struct tbl *vp)); +static void unsetspec ARGS((struct tbl *vp)); +static struct tbl *arraysearch ARGS((struct tbl *, int)); + +/* + * create a new block for function calls and simple commands + * assume caller has allocated and set up e->loc + */ +void +newblock() +{ + register 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; + tinit(&l->vars, &l->area, 0); + tinit(&l->funs, &l->area, 0); + l->next = e->loc; + e->loc = l; +} + +/* + * pop a block handling special variables + */ +void +popblock() +{ + register struct block *l = e->loc; + register struct tbl *vp, **vpp = l->vars.tbls, *vq; + register 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() +{ + 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 */ +#ifdef KSH + { "MAIL", V_MAIL }, + { "MAILCHECK", V_MAILCHECK }, + { "MAILPATH", V_MAILPATH }, + { "RANDOM", V_RANDOM }, + { "SECONDS", V_SECONDS }, + { "TMOUT", V_TMOUT }, +#endif /* KSH */ + { "LINENO", V_LINENO }, + { (char *) 0, 0 } + }; + int i; + struct tbl *tp; + + tinit(&specials, APERM, 32); /* must be 2^n (currently 17 specials) */ + for (i = 0; names[i].name; i++) { + tp = tenter(&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. + */ +const char * +array_index_calc(n, arrayp, valp) + const char *n; + bool_t *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); + if (rval < 0 || rval > ARRAYMAX) + errorf("%s: subscript out of range", n); + *valp = rval; + afree(sub, ATEMP); + } + return n; +} + +/* + * Search for variable, if not found create globally. + */ +struct tbl * +global(n) + register const char *n; +{ + register struct block *l = e->loc; + register struct tbl *vp; + register int c; + unsigned h; + bool_t array; + int val; + + /* Check to see if this is an array */ + n = array_index_calc(n, &array, &val); + h = hash(n); + c = 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)) { + for (c = 0; digit(*n); n++) + c = c*10 + *n-'0'; + if (c <= l->argc) + /* setstr can't fail here */ + setstr(vp, l->argv[c], 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 = tsearch(&l->vars, n, h); + if (vp != NULL) { + if (array) + return arraysearch(vp, val); + else + return vp; + } + if (l->next == NULL) + break; + } + vp = tenter(&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(n, copy) + register const char *n; + bool_t copy; +{ + register struct block *l = e->loc; + register struct tbl *vp; + unsigned h; + bool_t 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 = tenter(&l->vars, n, h); + if (copy && !(vp->flag & DEFINED)) { + struct block *ll = l; + struct tbl *vq = (struct tbl *) 0; + + while ((ll = ll->next) && !(vq = tsearch(&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(vp) + register 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 */ + static char strbuf[1 + 2 + 1 + BITS(long) + 1]; + const char *digits = (vp->flag & UCASEV_AL) ? + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + : "0123456789abcdefghijklmnopqrstuvwxyz"; + register unsigned long n; + register 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; + + *--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); + } + return s; +} + +/* get variable integer value, with error checking */ +long +intval(vp) + register struct tbl *vp; +{ + long num; + int base; + + base = getint(vp, &num); + 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(vq, s, error_ok) + register struct tbl *vq; + const char *s; + int error_ok; +{ + 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))) + s = formatstr(vq, s); + if ((vq->flag&EXPORT)) + export(vq, s); + else { + vq->val.s = str_save(s, vq->areap); + if (vq->val.s) /* don't lie */ + vq->flag |= ALLOC; + } + } else /* integer dest */ + if (!v_evaluate(vq, s, error_ok)) + return 0; + vq->flag |= ISSET; + if ((vq->flag&SPECIAL)) + setspec(vq); + return 1; +} + +/* set variable to integer */ +void +setint(vq, n) + register struct tbl *vq; + long n; +{ + if (!(vq->flag&INTEGER)) { + register 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(vp, nump) + struct tbl *vp; + long *nump; +{ + register char *s; + register 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; + for (c = *s++; c ; c = *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(vq, vp) + register struct tbl *vq, *vp; +{ + int base; + long num; + + if ((base = getint(vp, &num)) == -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(vp, s) + 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(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(*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(*q)) + *q = toupper(*q); + } else if (vp->flag & LCASEV) { + for (q = p; *q; q++) + if (isupper(*q)) + *q = tolower(*q); + } + + return p; +} + +/* + * make vp->val.s be "name=value" for quick exporting. + */ +static void +export(vp, val) + register struct tbl *vp; + const char *val; +{ + register 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(var, set, clr, field, base) + register const char *var; + Tflag clr, set; + int field, base; +{ + register 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 UNINITIALIZED(*s); + char UNINITIALIZED(*free_me); + + 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(vp, array_ref) + register 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(s, aok) + 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(s, aok) + 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(s, aok) + 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(s) + 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() +{ + struct block *l = e->loc; + XPtrV env; + register struct tbl *vp, **vpp; + register 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)) { + register struct block *l2; + register struct tbl *vp2; + unsigned h = hash(vp->name); + + /* unexport any redefined instances */ + for (l2 = l->next; l2 != NULL; l2 = l2->next) { + vp2 = tsearch(&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() +{ + rand(); +} + +/* + * handle special variables with side effects - PATH, SECONDS. + */ + +/* Test if name is a special parameter */ +static int +special(name) + register const char * name; +{ + register struct tbl *tp; + + tp = tsearch(&specials, name, hash(name)); + return tp && (tp->flag & ISSET) ? tp->type : V_NONE; +} + +/* Make a variable non-special */ +static void +unspecial(name) + register const char * name; +{ + register struct tbl *tp; + + tp = tsearch(&specials, name, hash(name)); + if (tp) + tdelete(tp); +} + +#ifdef KSH +static time_t seconds; /* time SECONDS last set */ +#endif /* KSH */ +static int user_lineno; /* what user set $LINENO to */ + +static void +getspec(vp) + register struct tbl *vp; +{ + switch (special(vp->name)) { +#ifdef KSH + 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((time_t *)0) - seconds)); + vp->flag |= SPECIAL; + break; + case V_RANDOM: + vp->flag &= ~SPECIAL; + setint(vp, (long) (rand() & 0x7fff)); + vp->flag |= SPECIAL; + break; +#endif /* KSH */ +#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(vp) + register 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 (ISABSPATH(s) && eaccess(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 */ +#ifdef KSH + 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((unsigned int)intval(vp)); + vp->flag |= SPECIAL; + break; + case V_SECONDS: + vp->flag &= ~SPECIAL; + seconds = time((time_t*) 0) - intval(vp); + 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; +#endif /* KSH */ + 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(vp) + register 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; +#ifdef KSH + case V_MAIL: + mbset((char *) 0); + break; + case V_MAILPATH: + mpset((char *) 0); + break; +#endif /* KSH */ + + case V_LINENO: +#ifdef KSH + 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 */ +#endif /* KSH */ + 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(vp, val) + struct tbl *vp; + int val; +{ + struct tbl *prev, *curr, *new; + + vp->flag |= ARRAY|DEFINED; + + /* The table entry is always [0] */ + if (val == 0) { + vp->index = 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)+strlen(vp->name)+1, + vp->areap); + strcpy(new->name, vp->name); + 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(cp) + 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(str) + 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(var, reset, vals) + 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 new file mode 100644 index 0000000..12945d9 --- /dev/null +++ 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 new file mode 100644 index 0000000..82392a2 --- /dev/null +++ b/vi.c @@ -0,0 +1,2190 @@ +/* $OpenBSD: vi.c,v 1.11 2003/03/13 09:03:07 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 +#include "ksh_stat.h" /* completion */ +#include "edit.h" + +#define CMDLEN 1024 +#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 ARGS((int ch)); +static void vi_reset ARGS((char *buf, size_t len)); +static int nextstate ARGS((int ch)); +static int vi_insert ARGS((int ch)); +static int vi_cmd ARGS((int argcnt, const char *cmd)); +static int domove ARGS((int argcnt, const char *cmd, int sub)); +static int redo_insert ARGS((int count)); +static void yank_range ARGS((int a, int b)); +static int bracktype ARGS((int ch)); +static void save_cbuf ARGS((void)); +static void restore_cbuf ARGS((void)); +static void edit_reset ARGS((char *buf, size_t len)); +static int putbuf ARGS((const char *buf, int len, int repl)); +static void del_range ARGS((int a, int b)); +static int findch ARGS((int ch, int cnt, int forw, int incl)); +static int forwword ARGS((int argcnt)); +static int backword ARGS((int argcnt)); +static int endword ARGS((int argcnt)); +static int Forwword ARGS((int argcnt)); +static int Backword ARGS((int argcnt)); +static int Endword ARGS((int argcnt)); +static int grabhist ARGS((int save, int n)); +static int grabsearch ARGS((int save, int start, int fwd, char *pat)); +static void redraw_line ARGS((int newline)); +static void refresh ARGS((int leftside)); +static int outofwin ARGS((void)); +static void rewindow ARGS((void)); +static int newcol ARGS((int ch, int col)); +static void display ARGS((char *wb1, char *wb2, int leftside)); +static void ed_mov_opt ARGS((int col, char *wb)); +static int expand_word ARGS((int command)); +static int complete_word ARGS((int command, int count)); +static int print_expansions ARGS((struct edstate *e, int command)); +static int char_len ARGS((int c)); +static void x_vi_zotc ARGS((int c)); +static void vi_pprompt ARGS((int full)); +static void vi_error ARGS((void)); +static void vi_macro_reset ARGS((void)); +static int x_vi_putbuf ARGS((const char *s, size_t len)); + +#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 ! " # $ % & ' */ + 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 /* ^V */ + +static char undocbuf[CMDLEN]; + +static struct edstate *save_edstate ARGS((struct edstate *old)); +static void restore_edstate ARGS((struct edstate *old, struct edstate *new)); +static void free_edstate ARGS((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(buf, len) + 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 = *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) + return -1; + + if (es->cbuf != buf) + memmove(buf, es->cbuf, es->linelen); + + buf[es->linelen++] = '\n'; + + return es->linelen; +} + +static int +vi_hook(ch) + int ch; +{ + static char curcmd[MAXVICMD]; + static char locpat[SRCHLEN]; + static int cmdlen; + static int argc1, argc2; + + switch (state) { + + case VNORMAL: + if (insert != 0) { + if (ch == Ctrl('v')) { + state = VLIT; + ch = '^'; + } + switch (vi_insert(ch)) { + case -1: +#ifdef OS2 + /* Arrow keys generate 0xe0X, where X is H.. */ + state = VCMD; + argc1 = 1; + switch (x_getc()) { + case 'H': + *curcmd='k'; + break; + case 'K': + *curcmd='h'; + break; + case 'P': + *curcmd='j'; + break; + case 'M': + *curcmd='l'; + break; + default: + vi_error(); + state = VNORMAL; + } + break; +#else /* OS2 */ + vi_error(); + state = VNORMAL; +#endif /* OS2 */ + 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) { + int i; + int n = srchlen; + + while (n > 0 && isspace(locpat[n - 1])) + n--; + while (n > 0 && !isspace(locpat[n - 1])) + n--; + 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)) { + es->cbuf[es->linelen++] = 'M'; + es->cbuf[es->linelen++] = '-'; + ch &= 0x7f; + } + if (ch < ' ' || ch == 0x7f) { + es->cbuf[es->linelen++] = '^'; + es->cbuf[es->linelen++] = ch ^ '@'; + } else + 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(buf, len) + 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(ch) + 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(ch) + 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) { + +#ifdef OS2 + case 224: /* function key prefix */ +#endif /* OS2 */ + 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(argcnt, 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 = tsearch(&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(es->cbuf[es->cursor])) { + while (isspace(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 + 1; + /* fall through */ + 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 + es->cbuf[es->cursor] = cmd[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) + 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; + /* fall through */ + 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((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(*p)) { + modified = 1; hnum = hlast; + *p = toupper(*p); + } else if (isupper(*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(argcnt, cmd, sub) + int argcnt; + const char *cmd; + int sub; +{ + int bcount, UNINITIALIZED(i), t; + int UNINITIALIZED(ncursor); + + 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(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(count) + 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(a, b) + int a, b; +{ + yanklen = b - a; + if (yanklen != 0) + memmove(ybuf, &es->cbuf[a], yanklen); +} + +static int +bracktype(ch) + 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() +{ + memmove(holdbuf, es->cbuf, es->linelen); + holdlen = es->linelen; + holdbuf[holdlen] = '\0'; +} + +static void +restore_cbuf() +{ + es->cursor = 0; + es->linelen = holdlen; + memmove(es->cbuf, holdbuf, holdlen); +} + +/* return a new edstate */ +static struct edstate * +save_edstate(old) + struct edstate *old; +{ + struct edstate *new; + + new = (struct edstate *)alloc(sizeof(struct edstate), APERM); + new->cbuf = alloc(old->cbufsize, APERM); + new->cbufsize = old->cbufsize; + strlcpy(new->cbuf, old->cbuf, new->cbufsize); + new->linelen = old->linelen; + new->cursor = old->cursor; + new->winleft = old->winleft; + return new; +} + +static void +restore_edstate(new, old) + struct edstate *old, *new; +{ + strncpy(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(old) + struct edstate *old; +{ + afree(old->cbuf, APERM); + afree((char *)old, APERM); +} + + + +static void +edit_reset(buf, len) + 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(s, len) + const char *s; + size_t len; +{ + return putbuf(s, len, 0); +} + +static int +putbuf(buf, len, repl) + 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(a, b) + int a, b; +{ + if (es->linelen != b) + memmove(&es->cbuf[a], &es->cbuf[b], es->linelen - b); + es->linelen -= b - a; +} + +static int +findch(ch, cnt, forw, incl) + 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(argcnt) + 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(es->cbuf[ncursor])) + while (!is_wordch(es->cbuf[ncursor]) && + !isspace(es->cbuf[ncursor]) && + ncursor < es->linelen) + ncursor++; + while (isspace(es->cbuf[ncursor]) && ncursor < es->linelen) + ncursor++; + } + return ncursor; +} + +static int +backword(argcnt) + int argcnt; +{ + int ncursor; + + ncursor = es->cursor; + while (ncursor > 0 && argcnt--) { + while (--ncursor > 0 && isspace(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(es->cbuf[ncursor])) + ; + ncursor++; + } + } + return ncursor; +} + +static int +endword(argcnt) + int argcnt; +{ + int ncursor; + + ncursor = es->cursor; + while (ncursor < es->linelen && argcnt--) { + while (++ncursor < es->linelen - 1 && + isspace(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(es->cbuf[ncursor])) + ; + ncursor--; + } + } + return ncursor; +} + +static int +Forwword(argcnt) + int argcnt; +{ + int ncursor; + + ncursor = es->cursor; + while (ncursor < es->linelen && argcnt--) { + while (!isspace(es->cbuf[ncursor]) && ncursor < es->linelen) + ncursor++; + while (isspace(es->cbuf[ncursor]) && ncursor < es->linelen) + ncursor++; + } + return ncursor; +} + +static int +Backword(argcnt) + int argcnt; +{ + int ncursor; + + ncursor = es->cursor; + while (ncursor > 0 && argcnt--) { + while (--ncursor >= 0 && isspace(es->cbuf[ncursor])) + ; + while (ncursor >= 0 && !isspace(es->cbuf[ncursor])) + ncursor--; + ncursor++; + } + return ncursor; +} + +static int +Endword(argcnt) + int argcnt; +{ + int ncursor; + + ncursor = es->cursor; + while (ncursor < es->linelen - 1 && argcnt--) { + while (++ncursor < es->linelen - 1 && + isspace(es->cbuf[ncursor])) + ; + if (ncursor < es->linelen - 1) { + while (++ncursor < es->linelen && + !isspace(es->cbuf[ncursor])) + ; + ncursor--; + } + } + return ncursor; +} + +static int +grabhist(save, n) + 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(save, start, fwd, pat) + int save, start, 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 FILECMP be strncmp? */ + if (start != 0 && fwd && FILECMP(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(newline) + 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(leftside) + 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() +{ + 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() +{ + register 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(ch, col) + int ch, col; +{ + if (ch == '\t') + return (col | 7) + 1; + return col + char_len(ch); +} + +static void +display(wb1, wb2, leftside) + char *wb1, *wb2; + int leftside; +{ + unsigned char ch; + char *twb1, *twb2, mc; + int cur, col, cnt; + int UNINITIALIZED(ncol); + 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(col, wb) + 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(command) + 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(command, count) + 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 && + FILECMP(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 && !ISDIRSEP(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(e, command) + 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(c) + 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(c) + 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(full) + int full; +{ + pprompt(prompt + (full ? 0 : prompt_skip), prompt_trunc); +} + +static void +vi_error() +{ + /* Beem out of any macros as soon as an error occurs */ + vi_macro_reset(); + x_putc(BEL); + x_flush(); +} + +static void +vi_macro_reset() +{ + if (macro.p) { + afree(macro.buf, APERM); + memset((char *) ¯o, 0, sizeof(macro)); + } +} + +#endif /* VI */ From 485fcb1158824b7e5aa609dbb108cbdaf1c97a33 Mon Sep 17 00:00:00 2001 From: tg Date: Thu, 10 Apr 2003 13:54:46 +0000 Subject: [PATCH 2/2] Import OpenBSD cvs as of roughly 11:11 UTC today, or CTM delta 3188/3189/3190. --- history.c | 7 ++++--- main.c | 4 ++-- misc.c | 14 ++++++++++++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/history.c b/history.c index 4ec7d6b..d7432d7 100644 --- a/history.c +++ b/history.c @@ -1,4 +1,4 @@ -/* $OpenBSD: history.c,v 1.17 2003/02/28 09:45:09 jmc Exp $ */ +/* $OpenBSD: history.c,v 1.18 2003/04/06 23:39:17 deraadt Exp $ */ /* * command history @@ -93,8 +93,9 @@ c_fc(wp) if (strcmp(p, "-") == 0) sflag++; else { - editor = str_nsave(p, strlen(p) + 4, ATEMP); - strcat(editor, " $_"); + size_t len = strlen(p) + 4; + editor = str_nsave(p, len, ATEMP); + strlcat(editor, " $_", len); } break; case 'g': /* non-at&t ksh */ diff --git a/main.c b/main.c index d6af946..2db79dc 100644 --- a/main.c +++ b/main.c @@ -1,4 +1,4 @@ -/* $OpenBSD: main.c,v 1.23 2003/03/10 03:48:16 david Exp $ */ +/* $OpenBSD: main.c,v 1.24 2003/04/04 23:12:02 deraadt Exp $ */ /* * startup, main loop, environments and error handling @@ -829,7 +829,7 @@ remove_temps(tp) APERM); memset(t, 0, sizeof(struct temp)); t->name = (char *) &t[1]; - strcpy(t->name, tp->name); + strlcpy(t->name, tp->name, strlen(tp->name) + 1); t->next = delayed_remove; delayed_remove = t; } diff --git a/misc.c b/misc.c index d916ec4..bf01495 100644 --- a/misc.c +++ b/misc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.14 2003/03/13 09:03:07 deraadt Exp $ */ +/* $OpenBSD: misc.c,v 1.15 2003/04/04 23:12:02 deraadt Exp $ */ /* * Miscellaneous functions @@ -83,7 +83,17 @@ str_save(s, ap) register const char *s; Area *ap; { - return s ? strcpy((char*) alloc((size_t)strlen(s)+1, ap), s) : NULL; + size_t len; + char *p; + + if (!s) + return NULL; + len = strlen(s)+1; + p = alloc(len, ap); + if (!p) + return NULL; + strlcpy(p, s, len+1); + return (p); } /* Allocate a string of size n+1 and copy upto n characters from the possibly