diff options
author | Jason Downs <downsj@cvs.openbsd.org> | 1996-08-14 06:19:13 +0000 |
---|---|---|
committer | Jason Downs <downsj@cvs.openbsd.org> | 1996-08-14 06:19:13 +0000 |
commit | 3039b959c7de1e35fe4ec27ae02f1c8fe8d4d4ac (patch) | |
tree | 09c6b5a4f13282e07afe153445fcfaa0d10651f4 /bin/pdksh | |
parent | 596c2317eee31ab9606c6b88085644407d4d2f0f (diff) |
Import pdksh 5.2.7.
Diffstat (limited to 'bin/pdksh')
65 files changed, 37700 insertions, 0 deletions
diff --git a/bin/pdksh/BUG-REPORTS b/bin/pdksh/BUG-REPORTS new file mode 100644 index 00000000000..2b98b829d48 --- /dev/null +++ b/bin/pdksh/BUG-REPORTS @@ -0,0 +1,928 @@ +$OpenBSD: BUG-REPORTS,v 1.1 1996/08/14 06:19:10 downsj 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 seperate 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 + looses the partially entered command (vi mode). + [see Mail.2:48] + +* 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, 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] + +* 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 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 + - Mail.7:7 partial sigwinch diffs + +* pdksh 5.2.3, - (reported by Mike Jetzer): in vi, <ESC>= on word with ~ + but no /, beeps (or prints final path comonent?). + [see Mail.9:66] + +* 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): echo ${foo[*]#/} generates + bad substsitution error, newer ksh's don't (older ones do); + error includes {...#@(/)}. + [see Mail.XXX:XXX] + +* 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.5, - (reported by Adrian M): 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] + +--------------------- 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 <limits.h> in sh.h to get the right value; added + depend target; print warning if ksh not in /etc/shells] + +x pdksh 5.0.6, - (reported by Michael Haardt): shell does not compile if JOBS + not defined. + [see Mail.2:32] + [fixed in 5.0.7 - added ifdefs to jobs.c(check_job)] + +x pdksh 5.0.6, - (reported by Nick Holloway): exit status of command + substitution is lost (known problem). + [from Mail.2:33]: + This is a variation on a theme of bug number 10 (and is one reason why + currently ksh can not be used for Linux's MAKEDEV). + + The exit status from command substitution is not available when used with + variable assignment. + + x=`false` && echo "Non-zero exit status lost". + [fixed in 5.0.7 - instead of faking :, set rv to subst_exstat] + +x pdksh 5.0.7 - (reported by Sean Hogan): CMASK redefined in emacs.c + [see Mail.2:44] + [fixed in 5.0.8 changed CMASK to CHARMASK] + +x pdksh 5.0.7 - (reported by Sean Hogan): "r" (fc -e -) doesn't work. + [see Mail.2:45] + [fixed in 5.0.8 - increment wp, change strcmp() test] + +x pdksh 5.0.7 - (reported by Thomas Gellekum): make install typeo. + [see Mail.2:46] + [fixed in 5.0.8 - added missing $] + +x pdksh 5.0.8 - (reported by Sean Hogan): "FOO=bar exec blah" does not + put FOO in environment. + [see Mail.2:50] + [fixed in 5.0.9 - re-arranged exec/command/builtin code in comexec()] + +x pdksh 5.0.8, QNX 4.2 (reported by Brian Campbell): "exec > /dev/null" + generates an error. + [see Mail.2:51] + [see Mail.2.58 - caused by ambitious compiler using same label for c_exec() + and c_builtin()] + [fixed in 5.0.9 - c_exec() no longer an empty function.] + +x pdksh 5.0.8, - (reported by Brian Campbell): "echo a{b," prints a "Missing }" + error - at&t ksh does not. at&t ksh always has brace-expansion on (unless + set -o nogolob). + [see Mail.2:51] + [fixed in 5.0.9 - brace expansion now compatible with at&t ksh] + +x pdksh 5.0.8, - (reported by Sean Hogan): ulimit output garbled; syntax error + in c_ulimit.c; no configure check for HAVE_SETRLIMIT. + [see Mail.2:64] + [fixed in 5.0.9 - use shprintf instead of shellf to print values; add + setrlimit() check to configure] + +x pdksh 5.0.7, - (reported by Jan Djarv): `echo > /foo/bar' causes a script to + exit - POSIX says it shouldn't. + [see Mail.2:60] + [fixed in 5.0.9 - iosetup returns error code, error messages cleaned up, etc] + +x pdksh 5.0.8, - : `more /etc/passwd &' followed by fg messes up tty settings. + [fixed in 5.0.9 - only save new tty settings if job originally started in fg] + +x pdksh 5.0.9, - (reported by Andrew Moore): a blank line causes $? to be + set to zero, newline after a here-document marker isn't read. + [see Mail.3:5,6] + [fixed in 5.0.10 - don't execute null trees, read the newline] + +x pdksh 5.0.9, - (reported by Michael Sullivan): mail checking reports you + have mail, when there is only old mail. + [fixed in 5.0.10 - use atime/mtime instead of size] + +x pdksh 5.0.9, - (reported by Chris Oates): if RANDOM is in ksh's environ + when it starts, the shell dumps core. + [see Mail.3:7,8] + [fixed in 5.0.10 - var.c(typeset): free t->val.s instead of + t->val.s + t->type] + +x pdksh 5.0.9, - (reported by Seah Hogan): ISC 3.01's make is confused by + a backslash followed by a blank line. + [see Mail.3:9,13] + [fixed in 5.0.10 - changed make depend target to change blank lines to sh.h] + +x pdksh 5.0.9, - (reported by Andrew Moore): commands without a newline cause + syntax errors - sh/ksh execute the commands. + [see Mail.3:15] + [fixed in 5.0.10 - have yyparse() accept newline and EOF] + +x pdksh 5.0.9, - (reported by Andrew Moore): empty arithmetic expressions not + accepted. + [see Mail.3:15,17] + [fixed in 5.0.10 - v_evaluate(): if first token is END, changed to literal 0] + +x pdksh 5.0.9, - (reported by Andrew Moore): nulls in input are not ignored. + [see Mail.3:15] + [fixed in 5.0.10 - added strip_nuls() function and calls to it] + +x pdksh 5.0.9, - (reported by Andrew Moore): \241 (M-!) not passed through + command substitutions. + [see Mail.3:15] + [fixed in 5.0.10 - evaluate(): cast c to a char before comparing to MAGIC] + +x pdksh 5.0.9, - (reported by Andrew Moore): newlines after here-documents + are read twice; shell reports an error if newline is missing. + [see Mail.3:25] + [fixed in 5.0.10 - fixed up readhere()] + +x pdksh 5.0.9, - (reported with fix by Mike Jetzer): 'r r' repeats the r + command forever. + [see Mail.3:38] + [fixed in 5.0.10 - start the search from the previous command] + +x pdksh 5.0.9, - (reported by Mike Jetzer): edit of multi-line commands + does not result in single history entry. + [see Mail.3:38] + [fixed in 5.0.10 - use hist_append() to add second+ lines] + +x pdksh 5.0.9, - (reported by Dale DePriest): ksh_times.h uses BROKEN_TIMES + [see Mail.3:43] + [fixed in 5.0.10 - changed ksh_times.h] + +x pdksh 5.0.9, - (reported by J. T. Conklin): using [ instead of test is slow. + [see Mail.3:46] + [fixed in 5.0.10 - put in kludgy check for [ in eval.c(glob)] + +x pdksh 5.0.9, - (reported by Michael Haardt): signals do not interrupt + read commands. + [see Mail.3:20] + [fixed in 5.0.10 - changed c_read() to check for fatal signals after EINTR] + +x pdksh 5.0.10, BSDI (reported by David Tamkin): use of _POSIX_VDISABLE in + tty.h causes compiler error. + [see Mail.3:67] + [fixed in 5.0.10.1 - new variable vdisable_c set/used in edit.c] + +x pdksh 5.0.8, - (reported by Donald Craig): on systems with both union wait + and waitpid(), waitpid() is passed a union wait pointer instead of an int + pointer. + [see Mail.2:54] + [fixed in 5.1 - added ksh_waitpid() define; cast status arg as needed.] + +x pdksh 5.0.10, - (reported by David Tamkin): space in vi command mode does + nothing. + [see Mail.3:76] + [fixed in 5.1 - vi.c(classify[]) table got changed by accident.] + +x pdksh 5.0.10, - (reported by Danial Quinlan): forward-word and + delete-word-forward functions in emacs don't go to the right place. + [see Mail.3:79] + [Fixed in 5.1 - changed order of loops in emacs.c(x_fword())] + +x pdksh 5.0.10, - (reported by David Tamkin): eof in multiline command + causes shell to exit, even if ignoreeof is set. + [see Mail.3:76] + [Fixed in 5.1 - reset eof after longjmp() in main.c(shell)] + +x pdksh 5.0.9, Ultrix 4.2 (reported by Matthew Nethook): type-ahead while + shell is waiting for a command to finish is temporarily lost until a + program that reads from stdin or goes a stty/gtty is run. + [see Mail.3:61,62] + [Fixed in 5.1 - changed aclocal.m4 to not define HAVE_TERMIOS_H on ultrix] + +x pdksh 5.0.10, - (reported by David Tamkin): if INT is trapped, ^C in + vi/emacs won't flush buffer/re-issue new prompt. + [see Mail.3:5,76] + [Fixed in 5.1 - use unwind() in vi/emacs to get back to shell()] + +x pdksh 5.0.10, - (reported by Dale DePriest): in emacs mode, file completions + resulting in long names (>256) cause core dumps + [see Mail.3:72] + [Fixed in 5.1 - use dynamically sized buffers in emacs code] + +x pdksh 5.0.10, - (reported by Dale DePriest): in emacs mode, command + completions (^[=) resulting in multiple hits caused internal memory error. + [see Mail.4:8] + [Fixed in 5.1 - don't call list_stash() twice in compl_command] + +x pdksh 5.0.10, - (reported by Dave Hatton): autoloading functions fail + on the first attempt, then work. + [see Mail.4:10] + [Fixed in 5.1 - in findcom(), check for include() returning non-0 (was 0)] + +x pdksh 5.0.10, - (reported by Art Pina via Dale DePriest): when SECONDS + parameter is assigned, it always acts as if 0 were assigned. + [see Mail.4:12] + [Fixed in 5.1 - set internal seconds variable to time - assigned value] + +x pdksh 5.1.0 - (reported by Larry Bouzane): for/select loops don't allow + {..} to be used instead of do...done. + [see Mail.4:16] + [Fixed in 5.1.1 - changed syn.c(dogroup) to allow {/} instead of do/done] + +x pdksh 5.1.0 - (reported by Andrew Moore and Larry Bouzane): a command ending + in ; or & that is not followed by a newline causes a syntax error. + [see Mail.4:126,128] + [Fixed in 5.1.1 - don't call syntaxerr() in get_command() if EOF is read] + +x pdksh 5.1.0, - (reported by Simon J. Gerraty): ksh died reading history + file (complex history, in hist_skip_backup()). + [see Mail.4:24] + [Fixed in 5.1.1 - hist_skip_back(): don't start past the end of the buffer] + +x pdksh 5.1.0 BSDI 1.1 (reported by Karl Denninger): after receipt of SIGHUP, + shell waits for foreground process to complete. + [see Mail.4:50,57] + [Fixed in 5.1.1 - added fatal_trap flag, check in jobs.c(j_waitj)] + +x pdksh 5.1.0 - (reported by Bob Manson): a leading non-white-space IFS + character does cause a field to be delimited. + [see Mail.4:68] + [Fixed in 5.1.2 - changed expand() to do the right thing.] + +x pdksh 5.1.2, -: ^c during $ENV or .profile kills shell; should just go + to prompt. + [see Mail.5:14] + [fixed in 5.2.4 - added intr_ok flag to main.c(include)] + +x pdksh 5.1.2, - (reported by Dan Quinlan): when shell prints out + execution trees (typeset -f), if botches elif statements. + [see Mail.5:17] + [fixed in 5.1.3 - changed tree.c(ptree) to deal with elif.] + +x pdksh 5.1.2, - (reported by Dale DePriest): fc -l -- -40 fails if there + are fewer than 40 commands. + [see Mail.5:19] + [fixed in 5.1.3 - changed history.c(histget) to allow out of range numbers] + +x pdksh 5.1.2, - (reported by Art Mills): file completion in command mode + doesn't work on a single character. + [see Mail.5:13] + [fixed in 5.1.3 - in vi.c(vi_cmd) call complete_word() with 1 not 0] + +x pdksh 5.1.2, - (reported by Dan Quinlan): an error in a let statement + causes shell to exit function/script. at&t ksh just prints error and + returns from let. + [see Mail.5:17] + [fixed in 5.2.3 - added error_ok arg to evaluate() and v_evaluate()] + +x pdksh 5.1.2, - (reported by Art Mills): if markdirs option is set, file + completion in vi adds two slashes to directories. + [see Mail.5:35] + [fixed in 5.1.3 - vi.c(complete_word), don't add / if file ends in one] + +x pdksh 5.1.2, - (reported by Dale DePriest): history read from history file + have negitive 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): <ESC>^H bound to del-back-char + not del-back-word + [see Mail.8:50-52] + [fixed in 5.2.1 - fixed x_emacs_keys] + +x pdksh 5.2.1, - (reported by David Tamkin): compile fails due to lack + of c_test.h + [see Mail.8:58] + [fixed in 5.2.2 - fixed put c_test.h in distribution] + +x pdksh 5.2.2, - (reported by Simon J. Gerraty): hist_source not being + initialized in complex history. + [see Mail.8:64] + [fixed in 5.2.3 - set it in second hist_init()] + +x pdksh 5.2.2, - (reported by Gabor Zahemszky): set -A does not reset + the array contents. + [see Mail.8:65] + [fixed in 5.2.3 - changed var.c(unset) to unset whole array if appropriate] + +x pdksh 5.2.2, - (reported by Gabor Zahemszky): getopts stops after an error; + at&t ksh carries on with next option. + [see Mail.8:65] + [fixed in 5.2.3 - remove GI_DONE flag from ksh_getopt()] + +x pdksh 5.2.2, - (reported by Gabor Zahemszky): getopts prints shell name + twice in error messages. + [see Mail.8:65] + [fixed in 5.2.3 - added GI_NONAME flag] + +x pdksh 5.2.2, - (reported by Gabor Zahemszky): pdksh's test doesn't know about + /dev/fd/n. + [see Mail.8:65] + [fixed in 5.2.3 - added test_stat() and test_eaccess()] + +x pdksh 5.2.2, - (reported by Thomas Gellekum): config test for memmove/bcopy + missing semi-colon + [see Mail.8:67] + [fixed in 5.2.3] + +x pdksh 5.2.2, - (reported by Donald Craig): fc string doesn't find string + if it is the most recent command. + [see Mail.8:76] + [fixed in 5.2.3 - fixed off by one error in history.c(hist_get)] + +x pdksh 5.2.2, - (reported by Gabor Zahemszky): pdksh doesn't do the + "You have running jobs" when user attempts to log out. + [see Mail.8:74] + [fixed in 5.2.3 - added set -o nohup option with supporting code] + +x pdksh 5.2.2, - (reported by Gabor Zahemszky): configure test for + broken memmove/bcopy doesn't work. + [see Mail.8:93] + [fixed in 5.2.3 - fixed test to copy overlapping buffers] + +x pdksh 5.1.3, - (reported by <wendt@sv5.mch.sni.de>): doesn't compile on + solaris 5.x with COMPLEX_HISTORY defined. + [see Mail.8:98] + [fixed in 5.2.3 - undef COMPLEX_HISTORY if flock not available] + +x pdksh 5.2.2, - (reported by Gabor Zahemszky): tilde expansion not preformed + in word part of ${foo[-+=?} substitution. + [see Mail.9:7] + [fixed in 5.2.3 - allow ~foo to end in a close brace] + +x pdksh 5.2.2, - (reported by Gabor Zahemszky): "fc 30" edits from 30 to + most recent history (should be just 30). + [see Mail.9:7] + [fixed in 5.2.3 - if !-l and no last given, use first] + +x pdksh 5.2.2, - (reported by Gabor Zahemszky): [many problems with man page] + [see Mail.9:12] + [fixed in 5.2.3 - fixed problems] + +x pdksh 5.2.2, - (reported by Gabor Zahemszky): #else followed by non-comment + in sigact.c. + [see Mail.9:13] + [fixed in 5.2.3 - turn it into a comment] + +x pdksh 5.2.2, - (reported with fix by Gabor Zahemszky): two argument form of + cd doesn't work. + [see Mail.9:14] + [fixed in 5.2.3 - in c_cd(), use current_wd not path] + +x pdksh 5.2.2, - (reported with fix by Gabor Zahemszky): command -V doesn't + report reserved words. + [see Mail.9:30] + [fixed in 5.2.3 - in c_whence(), look for reserved words if vflag set] + +x pdksh 5.2.3, - (reported by Dale DePriest): at&t's tbl wants space + between font specification and end of table descrption (ie, fB . not + fB.). + [see Mail.9:41] + [fixed in 5.2.4 - put spaces in] + +x pdksh 5.2.3, - (reported by David Tamkin & Claus L{gel Rasmussen): PS1 + isn't imported from environment anymore. + [see Mail.9:43,76] + [fixed in 5.2.4 - main: don't set PS1 if it is already set] + +x pdksh 5.2.3, - (reported by Gary Rafe): If PS1 contains newlines, vi + editing mode dones't redraw lines properly. + [see Mail.9:63] + [fixed in 5.2.4 - added prompt_skip stuff to vi/emacs] + +x pdksh 5.2.3, - (reported & fixed by Mike Jetzer): cd: error message if + directory didn't exist was wrong. + [see Mail.9:66] + [fixed in 5.2.4 - print correct string in error message] + +x pdksh 5.2.3, - (reported & fixed by Mike Jetzer): vi: <ESC>* shouldn't append + a * if word contains a $. + [see Mail.9:66] + [fixed in 5.2.4 - vi.c(glob_word): check for $ in word, check for null + expansion] + +x pdksh 5.2.3, - (reported & fixed by Mike Jetzer): vi: <ESC>= doesn't + list expansions in column form. + [see Mail.9:66] + [fixed in 5.2.4 - use pr_menu to print things nicely] + +x pdksh 5.2.3, - (reported Larry Bouzane): should be a way of installing + binary/man page as pdksh instead of ksh. + [see Mail.9:100] + [fixed in 5.2.4 - use the --enable-shell=pdksh option to configure] + +x pdksh 5.2.3, - (reported by Gabor Zahemszky): [many problems with man + page] + [see Mail.10:20] + [fixed in 5.2.4 - fixed problems] + +x pdksh 5.2.3, - (reported by Gabor Zahemszky): exec 1<&9 reports + error with ">&9" in it. + [see Mail.10:20] + [fixed in 5.2.4 - changed iosetup()] + +x pdksh 5.2.3, - (reported by Gabor Zahemszky): man page doesn't document + /dev/fd/N + [see Mail.10:20] + [fixed in 5.2.4 - updated manual] + +x pdksh 5.2.3, - (reported by Ted Coady): [[ foo/bar = foo* ]] + fails; should succeed. + [see Mail.10:32] + [fixed in 5.2.4 - fixed problem in exec.c(dbteste_getopnd)] + +x pdksh 5.2.3, - (reported by Ruei-wun Tu): make on NeXT/NeXTSTEP 3.3 + doesn't understand .PRECIOUS target and so does nothing. + [see Mail.10:43] + [fixed in 5.2.4 - moved .PRECIOUS after all in Makefile.in] + +x pdksh 5.2.3, - (reported & fixed by Paul Borman): shell doesn't kill + foreground process when SIGHUP received; Also, CONT sent before HUP. + [see Mail.10:44] + [fixed in 5.2.4 - j_exit now sends HUP to foreground process] + +x pdksh 5.2.3, AIX 3.2.5 (reported by Ian Portsmouth): C compiler compains + about sigtraps[] being re-declared in trap.c. + [see Mail.10:73] + [fixed in 5.2.4 - use cpp define to avoid bogus re-declaration error] + +x pdksh 5.2.3, - (reported by Michael Haardt): ENV should not be + included if shell is compiled as sh and posix option not set. + [see Mail.10:83] + [fixed in 5.2.4 - only include ENV if POSIX, if compiled as sh] + +x pdksh 5.2.3, - (reported & fixed by DaviD W. Sanderson): case statements + don't allow {/} in place of IN/ESAC. + [see Mail.10:77,78] + [fixed in 5.2.4 - allow {/} in case statements] + +x pdksh 5.2.3, - (reported by Larry Daffner): $? is incorrectly zero'd + at start of traps. + [see Mail.11:9] + [fixed in 5.2.4 - don't clear exstat in main.c(shell)] + +x pdksh 5.2.3, - (reported by Frank "Crash" Edwards): configure on linux XXX + doesn't detect the presence of lstat(). + [see Mail.11:36] + [fixed in 5.2.4 - change configure to include <sys/stat.h> in lstat() test] + +x pdksh 5.2.3, - (reported by Gabor Zahemszky): typeset -f dumps core + in the after using autoload functions. + [see Mail.11:74?] + [fixed in 5.2.4 - c_typeset no longer traverses the array link for functions] + +x pdksh 5.2.3, - (reported by Gabor Zahemszky): typeset -f does not report + undefined autoload functions + [see Mail.11:74?] + [fixed in 5.2.4 - c_typeset: don't ignore unset functions] + +x pdksh 5.2.3, - (reported by Dale DePriest): alias -t -r does not + reset aliases. + [see Mail.11:99] + [fixed in 5.2.4 - c_alias: call ksh_getopt_reset() before calling c_unalias] + +x pdksh 5.2.3, - (reported & fixed by Jason Tyler): 'echo abc^Jfc -e - a=b e' + echos b, not bbc. + [see Mail.11:100?] + [fixed in 5.2.4 - hist_replace: use s, not last] + +x pdksh 5.2.3, - (reported by Jason Tyler): 'fc -e -' when there is + no history causes infinite loop. + [see Mail.11:100?] + [fixed in 5.2.4 - histbackup: allow histptr to go below history] + +x pdksh 5.2.4, - (reported by David Tamkin): jmp_buf is used instead of + sigjmp_buf. + [see Mail.XXX:XXX] + [fixed in 5.2.5 - added ksh_jmp_buf and defined appropriately] + +x pdksh 5.2.4, - (reported by Stephen Coffin): /<RETURN> in vi mode does not + repeat last search. + [see Mail.XXX:XXX] + [fixed in 5.2.5 - vi.c(vi_hook) - make it repeat last search] + +x pdksh 5.2.4, - (reported by Gabor Zahemszky): functions containing select + commands aren't printed correctly by typeset. + [see Mail.XXX:XXX] + [fixed in 5.2.5 - tree.c(ptree) - add case for TSELECT] + +x pdksh 5.2.4, - (reported & fixed by Stefan Dalibor): COLUMNS isn't set on + shell start up (and window size is ignored) 'cause tty_fd isn't valid when + x_init() is called. + [see Mail.XXX:XXX] + [fixed in 5.2.5 - call x_init() after j_init() is called] + +x pdksh 5.2.4, - (reported by Will Renkel): "echo -" just prints a blank + line - should print the minus. + [see Mail.XXX:XXX] + [fixed in 5.2.5 - c_ksh.c(c_print): don't do argument parsing on lone -] + +x pdksh 5.1.3, - (reported by Gabor Zahemszky): emacs doesn't have ^[*. + [see Mail.7:87] + [fixed in 5.2.5] + +x pdksh 5.2.3, - (reported by Mike Jetzer): in vi, <ESC>= doesn't append + a / after directories. + [see Mail.9:66] + [fixed in 5.2.5] + +x pdksh 5.2.0, - (reported by Gabor Zahemszky): can set readonly variables + via command assignments (eg, "readonly x=y; x=z /bin/echo hi" should + fail and doesn't). + [see Mail.8:50,65] + [fixed in 5.2.5 - LOCAL_COPY flag passed from comexec() down to local()] + +x pdksh 5.2.4, - (reported by Tom Karches): history: "r old=new", with + no commands prefix given, prints "fc: too mnay arguments" - it should + do the subst on the previous command. + [see Mail.XXX:XXX] + [fixed in 5.2.5] + +x pdksh 5.2.3, - (reported by Vigen Pogosyan): assignments in $(( ... )) + remember the base that was assigned in pdksh - does not in at&t ksh. + [see Mail.10:54] + [fixed in 5.2.5: uset setint() in expr.c(evalexpr)] + +x pdksh 5.2.4, - (reported by Gabor Zahemszky): emacs: ^O steps down + two lines (should be 1). + [see Mail.XXX:XXX] + [fixed in 5.2.5: convert history line to command number, then convert back] + +x pdksh 5.2.3, - (reported by David Gast(? gast@twinsun.com)): fc -ln -1 -1 + reports the current command, not the previous command. + [see Mail.10:49] + [fixed in 5.2.5] + +x pdksh 5.2.3, - (reported by Matthew Green): foo=`^Jecho bar` doesn't + set foo to bar (foo is empty). + [see Mail.XXX:XXX] + [fixed in 5.2.5: syn.c: set multiline.on when source is SSTRING] + +x pdksh 5.2.5, - (reported by Gabor Zahemszky): continue/break: if n + is too big, shell prints internal error message. + [see Mail.XXX:XXX] + [fixed in 5.2.6: fix c_brkcont to use last loop if n is too big] + +x pdksh 5.2.5, - (reported by Gabor Zahemszky): set: +o in ksh93 + prints command that sets various options. + [see Mail.XXX:XXX] + [fixed in 5.2.6: changed misc.c(printoptions)] + +x pdksh 5.2.5, - (reported by Gabor Zahemszky): COLUMNS/LINES variables + are not exported. + [see Mail.XXX:XXX] + [fixed in 5.2.6: use typeset() in edit.c(x_init) to export COLUMNS/LINES] + +x pdksh 5.2.5, - (reported by Gabor Zahemszky): emacs: <ESC><ESC> puts + space after completed directories. + [see Mail.XXX:XXX] + [fixed in 5.2.6: check for single/non-directory match in emacs.c(do_complete)] + +x pdksh 5.2.5, - (reported by Gabor Zahemszky): vi: # removes comment + and executes if command already commented. + [see Mail.XXX:XXX] + [fixed in 5.2.6: added vi.c(do_comment)] diff --git a/bin/pdksh/CONTRIBUTORS b/bin/pdksh/CONTRIBUTORS new file mode 100644 index 00000000000..3b549ca6a3c --- /dev/null +++ b/bin/pdksh/CONTRIBUTORS @@ -0,0 +1,93 @@ +$OpenBSD: CONTRIBUTORS,v 1.1 1996/08/14 06:19:10 downsj 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 (<crash@azhrei.EEC.COM>): added macros to vi (@char). + * Fritz Heinrichmeyer (<Fritz.Heinrichmeyer@FernUni-Hagen.de>): fixes + to allow compile under Linux 1.4.3. + * Gabor Zahemszky (<zgabor@CoDe.hu>): SVR3_PGRP vs SYSV_PGRP, many + bug reports and man page fixes. + * Dave Kinchlea (<kinch@julian.uwo.ca>): DEFAULT_ENV patches. + * Paul Borman (<prb@bsdi.com>): j_exit: send HUP, then CONT; HUP fg process. + * DaviD W. Sanderson (<dws@ssec.wisc.edu>): patches to allow { .. } instead + of in .. esac in case statements. + * Jason Tyler (<jason@nc.bhpese.oz.au>): fixes for bugs in fc. + * Stefan Dalibor (<Stefan.Dalibor@informatik.uni-erlangen.de>): fix for + COLUMNS never being set in x_init(). + * Arnon Kanfi (<arnon@gilly.datatools.com>): fix for prompt. diff --git a/bin/pdksh/ChangeLog b/bin/pdksh/ChangeLog new file mode 100644 index 00000000000..430eaebb5dc --- /dev/null +++ b/bin/pdksh/ChangeLog @@ -0,0 +1,664 @@ +$OpenBSD: ChangeLog,v 1.1 1996/08/14 06:19:10 downsj Exp $ + +Tue Jun 4 08:41:19 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.2.6 distribution + + * vi.c(CMDLEN): changed from 16 back to 1024. + +Sun Jun 2 11:54:46 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.2.6 distribution + +Sun Jun 2 11:46:56 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c(search_access): changed ordering of xsuffixes[], rsuffixes[]; + removed code that used xsuffixes[] when suffix is present. + * lex.c(getsc_line): set O_TEXT/O_BINARY if os/2. + * main.c(remove_temps): added os2 ifdefs. + [Changes from Dale DePriest.] + +Tue May 21 14:18:22 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * vi.c(vi_cmd): case '#': call do_comment() to do work. + * vi.c(do_comment): new function. + * vi.c(putbuf,grabhist,grabsearch): fixed pesimestic off-by-1 error + (cbufsize - 1 -> cbufsize). + * vi.c(vi_hook): case VCMD: case -1: added refresh(0). + * vi.c(vi_cmd): case 'P': don't move cursor back if nothing added. + +Tue May 21 12:03:34 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * emacs.c(do_complete): don't add space if single match and + it doesn't end with a /. + +Tue May 21 11:51:36 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * edit.c(x_init): use typeset to set EXPORT attribute for + COLUMNS/LINES. + +Tue May 21 11:40:12 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * misc.c(parseargs): option setting: ignore context if option + isn't being changed. + * misc.c(printoptions): for non-verbose mode: print a set command + (eg, set -o vi -o ...) instead of just the option names. + +Tue May 21 11:14:27 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * c_sh.c(c_brkcont): if n is too big, use last enclosing loop. + +Fri May 10 09:27:47 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * sh.h(Getopt): changed field p from int to unsigned. + +Tue May 7 12:10:47 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.2.5 distribution + +Tue May 7 11:45:37 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * syn.c(compile): set multiline if source is SSTRING. + * syn.c(yyparse): don't peek before calling c_list() - build + TEOF if c_list() fails and c is 0. + * syn.c(c_list): remove SSTRING test. + * syn.c(get_command): if EOF is reached, free iops,args,vars. + * syn.c(syntaxerr): set multiline.on to false when it is used; + don't use multiline.on if start token is 0. + +Tue May 7 10:11:41 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * history.c(c_fc,hist_execute): moved calls to histbackup() from + c_fc() to hist_execute(). + * history.c(hist_get): number: took out +1 correction as histbackup + hasn't been done yet; string: added -1 correction to ensure + current fc command isn't searched. + * history.c(hist_get_newest,hist_get_oldest): don't find the + current (fc) command; removed print_err argument (was always + true). + * history.c(hist_get,hist_get_newest): added allow_cur argument; + changed all calls. + +Mon May 6 09:55:29 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * emacs.c(x_nextcmdp): renamed to x_nextcmd, changed from + char ** to int. + * emacs.c(x_nl_next_com): save absolute command number, not + relative position in history array (which changes). + * emacs.c(x_emacs): convert x_nextcmd back to relative position. + * emacs.c(x_init_emacs): initialize x_nextcmd to -1. + +Sun May 5 13:10:48 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * expr.c(evalexpr): when assigning a non-integer, call setint() + (not setstr(..., strval(...))). + +Sun May 5 12:16:11 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * mail.c(maddmsg): changed name to mprintit(); now prints message + directly instead of saving in a linked list; changed all calls. + * mail.c(mprint): deleted; deleted all calls. + * mail.c(mmsgs,struct mailmsg): deleted. + +Sun May 5 11:52:05 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * lex.h(SF_TTY): new flag. + * lex.h(STTY): deleted. + * main.c(main): if tty, use SSTDIN, set SF_TTY. + * main.c(shell): check SF_TTY instead of STTY. + * lex.c(getsc_): call getsc_line for SSTDIN/SFILE. + * lex.c(getsc_line): new function (merged old STTY/SSTDIN/SFILE code). + +Fri May 3 11:24:17 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * main.c(shell): changed exit_atend to toplevel. Changed interactive + to be falking&toplevel (was talking&s->type==STTY). + +Fri May 3 10:59:22 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * var.c(getint): only allow one base (ie, disallow 2#4#5). + +Thu May 2 21:31:23 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * var.c(array_index_calc): new function + * var.c(global): call array_index_calc(); moved $2 code into + if (!letter(c))... + * var.c(local): call array_index_calc(); added copy argument & code; + changed all calls. + * table.h(LOCAL_COPY): new define. + * exec.c(comexec): maybe pass LOCAL_COPY to typeset(). + +Thu May 2 16:34:29 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * emacs.c: command completion changes. + * emacs.c(Comp_type,CT_LIST,CT_COMPLETE,CT_COMPLIST): new type. + * emacs.c(x_ins): return type changed to int; return -1 if + string can't be inserted. + * emacs.c(x_do_ins): new function. + * emacs.c(add_stash,list_stash,compl_dec,compl_file,compl_command, + str_match): deleted; changed callers to use do_complete(). + * emacs.c(do_complete,x_expand): new functions. + * emacs.c(x_ftab[],x_defbindings[]): added entry for file-expand; + bound to <ESC>*. + +Thu May 2 15:31:32 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * lex.c(set_prompt): pass strlen() + 1 to shf_sopen. + (fix from Arnon Kanfi). + +Wed Apr 24 11:50:52 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * history.c(c_fc): -e -: don't increment wp past null; allow + pat=replace arg with "-1" type argument. + (based on fix from Jason Tyler). + +Mon Apr 15 11:58:34 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * table.c(tenter),alloc.c(alloc): changed use of offsetof() so field + parameter is a constant expression. + * sh.h: took out undef of offsetof on CRAYs. + +Fri Apr 12 16:01:40 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * jobs.c(JF_USETTYMODE): renamed JR_ORIGFG to JF_USETTYMODE. + * jobs.c(j_waitj): clear JF_USETTYMODE if fg job is stopped. + +Sun Apr 7 12:35:30 NDT 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * c_ksh(c_print): echo: don't treat a lone minus as an option. + +Sat Apr 6 00:09:37 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * c_ulimit.c(c_ulimit.c): always pass 2 args to ulimit(). + * ksh_sigsetjmp(): changed all uses to be simple expressions - seems + to be required by the cray C compiler. + * sh.h(offsetof): undef if on a cray. + (based on fixes from Dave Kinchlea) + +Sat Mar 23 13:58:12 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * siglist.in: added WAITING,LWP,FREEZE,THAW,CANCEL + +Thu Mar 7 23:26:37 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * edit.c(x_init): set LINES if possible. + +Thu Mar 7 23:01:55 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * main.c(main): call x_init() after j_init() + (based on fix from Stefan Dalibor). + +Thu Mar 7 16:13:10 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * aclocal.m4(KSH_OS_TYPE): check for TitanOS (use cc -43). + * aclocal.m4(KSH_SIGNAL_TYPE): for bsd41 signals, check if signal + interrupt read(). + +Thu Mar 7 13:59:29 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * sh.h(strstr),missing.c(strstr): changed args to const. + +Wed Mar 6 17:21:36 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * io.c(errorf,bi_errorf): changed null pointer string check to + empty string; changed all calls (due to new error gcc warnings). + +Wed Mar 6 17:15:58 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c(search_access): files aren't executable if they don't + have any execute bits. + * ksh_stat.h: added S_IXUSR,S_IXGRP,S_IXOTH. + * exec.c(search_access,search_access1): OS2: changed the meaning + of these two functions (search_access1 now called from search_access). + +Wed Mar 6 16:23:23 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * tree.c(ptree): add case for TSELECT. + +Wed Mar 6 12:40:34 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * vi.c(Z_,is_zeroarg): new defines. + * vi.c(classify): use Z_ for G, g, _, |, v, ^I, ^F. + * vi.c(vi_cmd): use is_zerocount(). + * vi.c(complete_word): if command prefixed by a count, complete + to count'th expansion (as reported by print_expansions()). + +Tue Mar 5 14:43:48 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * eval.c(GF_NONE,GF_EXCHECK,GF_GLOBBED,GF_MARKDIR): new defines. + * eval.c(glob_str): added markdirs argument; changed all calls; + made function non-static. + * eval.c(glob): added markdirs argument; changed all calls. + * tree.h(DOMARKDIRS): new define. + * eval.c(expand): set DOMARKDIRS if FMARKDIRS. + * edit.c(x_complete_word,x_print_expansions,x_file_glob,x_command_glob, + x_locate_word,x_cf_glob,x_add_glob,x_longest_prefix,x_free_words): + new functions. + * proto,edit.h: moved functions defined in edit.c to edit.h. + * vi.c(struct edstate): moved to top of file. + * vi.c(print_expansions): added struct edstate argument; changed all + calls. + * vi.c(struct glob,Glob,globstr,glob_word,): deleted + * vi.c(vi_pprompt): new function; changed all calls of pprompt() in + vi.c to use vi_pprompt(). + * vi.c(x_vi): moved to top of file. + * vi.c(expand_word,complete_word): free buf if it is not null. + * vi.c(expand_word,complete_word,print_expansions): changed + to use new edit.c functions. + +Tue Feb 20 11:02:05 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * table.c(twalk,tnext,struct tstate),table.h(struct tstate): moved + struct tstate from table.c to table.h; changed twalk,tnext to take + struct tstate* argument; changed all calls; deleted static tstate + variable. + +Sat Feb 17 12:28:11 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * vi.c(vi_hook): case VSEARCH: if new pattern is empty, repeat last + search. + +Sat Feb 10 15:59:28 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * table.h(struct arg_info): new struct. + * table.h(struct block): changed argv, argc fields to argi. + +Sat Feb 10 15:12:47 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + ANSI C name space requirements: + + * vi.c(isbad,iscmd,islong,ismove,issrch,isundoable,iswordch): changed + to is_bad,is_cmd,is_long,is_move,is_srch,is_undoable,is_wordch. + * emacs.c(iscfs,ismfs): changed to is_cfs, is_mfs. + * emacs.c(strmatch): changed to str_match. + * sh.h(strchr_dirsep,strrchr_dirsep): changed to ksh_strchr_dirsep, + ksh_strtchr_dirsep; changed all calls. + * missing.c(strichars[]): changed to ichars[]. + * var.c(strint,strval): changed to setint_v, str_val. + * missing.c(strsave,strnsave): changed to str_save,str_nsave. + +Fri Feb 9 11:30:15 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * main.c(main): remove envp parameter; declare and use environ. + + * c_ksh.c(c_print): octal digit escape sequences must start with \0. + +Sat Feb 3 15:35:41 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * vi.c(vi_cmd,classify[]): made ^I a command. + +Fri Feb 2 10:40:32 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * lex.h(struct source): added u.freeme field. + * lex.c(getsc_): case SREREAD: free u.freeme iff start isn't u.ugbuf. + +Thu Feb 1 15:27:06 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * c_test.h(Test_env): added end union. + * c_test.c(c_test): keep track of end postition 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 ', " seperators. + (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): seperate 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 redundent "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/bin/pdksh/ChangeLog.0 b/bin/pdksh/ChangeLog.0 new file mode 100644 index 00000000000..23dc38eb028 --- /dev/null +++ b/bin/pdksh/ChangeLog.0 @@ -0,0 +1,3589 @@ +$OpenBSD: ChangeLog.0,v 1.1 1996/08/14 06:19:10 downsj Exp $ + +Thu Jun 15 11:02:06 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.2.3 distribution + + * c_ksh.c(c_whence): search keyword table if vflag set. + + * tree.h(DOVACHECK): new define. + * eval.c(expand): check DOVACHECK flag. + * exec.c(execute): when calling eval(), or in t->evalflags. + * syn.c(get_command): set evalflags to DOVACHECK instead of DOASNTILDE. + +Wed Jun 14 09:27:19 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * c_ksh.c(c_cd): two argument format: use current_wd, not path + when appending elen bytes. + (fix from Gabor Zahemszky). + +Tue Jun 13 15:54:11 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * history.c(c_fc): if last not specified and !-l, use first as last. + + * eval.c(maybe_expand_tilde): allow CSUBST to end tilde word. + + * misc.c(gmatch): made sc and pc unsigned. + +Fri Jun 2 11:55:40 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * configure.in: added flock to AC_CHECK_FUNCS call. + * conf-end.h: undef COMPLEX_HISTORY if !HAVE_FLOCK. + +Tue May 30 20:38:47 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * sh.h(SEEK_SET,SEEK_CUR,SEEK_END): define if not defined. + * history.c: change L_XTND to SEEK_END. + +Tue May 30 17:01:34 NDT 1995 John Rochester (jr@panda.cs.mun.ca) + + * shf.c(shf_seek): new function. + * shf.h(shf_seek): new prototype. + +Tue May 30 16:42:41 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * aclocal.m4(KSH_DEV_FD): new test. + * acconfig.h(HAVE_DEV_FD): new define. + * configure.in: call KSH_DEV_FD. + + * c_test.h(TO_FILAXST): new enum. + * c_test.c(test_stat,test_eaccess): new functions for /dev/fd/n + handling. + * c_test.c(test_evalop): call test_stat() and test_eaccess() + instead of stat() and eaccess() in most places; added case + for TO_FILAXST. + +Tue May 30 16:06:21 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * aclocal.m4(KSH_MEMMOVE): fixed test so copies overlap. + +Sun May 28 11:11:03 NDT 1995 John Rochester (jr@panda.cs.mun.ca) + + * sh.h(safe_prompt): new variable. + * main.c(initsubs): removed PS1. + * main.c(main): initialize safe_prompt; initialize PS1 from + safe_prompt. + * lex.c(set_prompt): create new env while expanding PS1 - if expansion + fails, use safe_prompt. + +Sat May 27 20:59:02 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c: put comments around token after #endif. + +Thu May 25 10:10:45 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * c_test.c(test_eval): case TO_OPTION: negate test if option starts + with a !, always fail if option doesn't exist. + + * sh.h(FNOHUP): new define. + * misc.c(options[]): "nohup" new option. + * jobs.c(j_stopped,j_stopped_running): name of j_stopped changed + to j_stopped_running; changed all calls; check for/warn about + running jobs if appropriate. + * jobs.c(j_exit): check for/kill running jobs if appropriate. + * main.c(shell),c_sh.c(c_exit): un-ifdef JOBS the j_stopped_running() + call and really_exit initialization/clearing. + +Wed May 24 10:06:14 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * options.h(DEFAULT_ENV): new define. + * main.c(main): if ENV isn't set and DEFAULT_ENV is defined, include + the later. + (based on patches from Dave Kinchlea). + + * sh.h(LAEXPR): new define. + * expr.c(evaluate): changed return type to error indicator; added + rval and error_ok arguments; changed all calls (c_sh.c(c_shift), + c_ulimit.c(c_ulimit),eval.c(expand),var.c(global,local)). + * expr.c(v_evaluate): added error_ok argument; changed return value + to error indicator; call unwind() if !error_ok. + * expr.c(evalerr): changed errorf() to warningf(); call unwind(LAEXPR). + * c_test.c(test_eval): merged code for integer operations to have + two calls to evaluate(). + + * io.c(warningf): print trailing newline; changed all calls. + + * history.c(hist_get): string search: use histptr, not histptr - 1. + +Tue May 23 11:07:50 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * sh.h(GI_NONAME): new define. + * misc.c(ksh_getopts): honour GI_NONAME flag. + * c_ksh.c(getopts_reset): set GI_NONAME flag. + + * exec.c(comexec): don't change $0 if FPOSIX flag set. + + * misc.c(ksh_getopt): don't use GI_DONE to allow parsing past + bad options. + * sh.h(GI_DONE): deleted define. + + * var.c(unset): added array_ref parameter; unset/free whole array + if not an array_reference; changed all calls. + * c_sh.c(c_unset): set array_ref parameter if there is a [ in the name. + +Mon May 22 10:33:14 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * history.c(hist_init): complex version: initialize hist_source + (fix from Simon J. Gerraty). + +Sat May 20 11:06:15 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.2.2 distribution + + * Makefile.in: added c_test.h to HDRS. + +Fri May 19 12:35:18 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.2.1 distribution + + * emacs.c(v_version): ignore typed character if it is a space. + * emacs.c(x_emacs_keys): bind <ESC>erase-char to delete-back-word + (was delete-back-char). + * emacs.c(x_defkeybindings[]): bound list-file to ^X^Y and + newline-and-next to ^O, as per man page. + + * c_ksh.c(c_whence): changed "is a keyword" to "is a reserved word". + + * sh.h: changed SVSV_PGRP to SYSV_PGRP. + + * vi.c(vi_cmd): uncommented case for ^[ to make it easy to enable + completion. + +Mon May 15 15:25:22 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * history.c(c_fc): accept -40 as -- -40. + * main.c(initcoms[]): take -- out of history alias. + + * vi.c(print_expansions): handle trailing slash correctly (don't + print empty strings). + + * c_ksh.c(c_cd): put back ksh_get_wd() call for os/2. + + * misc.c(ksh_get_wd): changed buf to b in call to getcwd(). + +Tue May 9 13:57:31 NDT 1995 Michael Rendell (michael@dragon.cs.mun.ca) + + * c_test.h: new file. + * c_test.c: major code restructuring: common parsing/evaluation + routines call/called-by three sets of routines: one for + normal test (and [..]), one for parsing [[ .. ]] one for + evaluating [[ .. ]]. + * c_test.c(oexpr,aexpr,nexpr,primary,is_op): renamed to test_oexpr, + test_aexpr, test_nexpr, test_primary, test_isop. + * c_test.c(eval_unop,eval_binop): combined into new test_eval function. + * c_test.c(syntax): renamed to ptest_error, + * c_test.c(ptest_isa,ptest_getopnd,ptest_eval): new functions. + * syn.c(syntaxerr): added extra arg; changed all calls. + * syn.c(db_parse,db_oaexpr,db_nexpr,db_primary): deleted. + * syn.c(dbtestp_isa,dbtestp_getopnd,dbtestp_eval,dbtestp_error): added. + * syn.c(get_command): case DBRACKET: changed to call new routines. + * tree.c(ptree): case DBRACKET: changed. + * exec.c(execute): case DBRACKET: changed. + * exec.c(dbteste_isa,dbteste_getopnd,dbteste_eval,dbteste_error): added. + +Fri May 5 17:10:23 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * emacs.c(compl_file,compl_command): fixed buffer growing code. + +Thu May 4 22:44:01 NDT 1995 Michael Rendell (michael@garfield.cs.mun.ca) + + * aclocal.m4(KSH_UNISTD_H): include <sys/types.h> and only include + <dirent.h> if HAVE_DIRENT_H is defined. + +Thu May 4 21:19:15 NDT 1995 Michael Rendell (michael@garfield.cs.mun.ca) + + * c_ksh.c: include "ksh_stat.h". + * c_ksh.c(c_cd): don't do physical chdir if S_ISLNK not defined. + +Wed May 3 10:08:32 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.2.0 distribution + + * misc.c: include <ctype.h>. + * misc.c(gmatch): added isfile argument; changed all calls. + * sh.h(FILECHCONV): (os2 version) - use isupper. + * emacs.c(strmatch): don't increment in FILECHCONV. + + * aclocal.m4(KSH_HEADER_SYS_WAIT): new macro. + * configure.in: use KSH_HEADER_SYS_WAIT instead of AC_HEADER_SYS_WAIT. + * ksh_wait.h: if POSIX_SYS_WAIT not defined, undef W* macros. + +Tue May 2 12:10:39 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * emacs.c,emacs-gen.sh,emacs-c.in,emacs.out,Makefile.in: changed emacs + source munging to create emacs.out which is included by emacs.c + rather then munging emacs.c itself. + + * lex.c(pprompt): flush shl_out. + + * vi.c(glob_word): if path has *?[, don't add * (was if last component). + + * emacs.c(x_search_char): renamed to x_search_char_forw. + * emacs.c(x_search_char_back): new function; bound to ^[^]. + + * sh.h: changed SVR3_PGRP to SYSV_PGRP. + (fixes from Gabor Zahemszky). + +Tue May 2 10:09:57 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * c_ksh.c(c_cd): deleted OS2 ifdefs. + * path.c(make_path): use ISRELPATH instead of ISABSPATH + * path.c(simplify_path): use ISROOTEDPATH instead of ISABSPATH. + * sh.h(ISABSPATH,ISROOTEDPATH,ISRELPATH): changed/new defines. + + * aclocal.m4(AC_LANG_C,AC_LANG_CPLUSPLUS,AC_TRY_RUN): copied + from autoconf's acgeneral.m4, changed to handle .exe suffix. + * aclocal.m4(KSH_OS_TYPE): os2 case: set $ac_exe_suffix. + * configure.in: substitute ac_exe_suffix. + * Makefile.in: changed references to E to exe_suffix, set to + ac_exe_suffix + + * c_ksh.c(c_cd): ifdef S_ISLNK second use of get_phys_path(). + * edit.c(x_mode): removed ifndef OS2. + (fixes from Dale DePriest) + * exec.c(search_access1): add .sh to suffix lists. + * vi.c(vi_insert,vi_hook): OS2: changes to allow arrow keys work + in insert mode. + +Mon May 1 16:28:44 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * path.c(ksh_get_wd): getcwd() case, return alloc'd buffer, not + a malloc'd one. + +Mon May 1 09:41:56 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * aclocal.m4: changed HAVE_SYS_RESOURCES_H to HAVE_SYS_RESOURCE_H. + + * aclocal.m4(KSH_OS_TYPE): new macro. + * aclocal.m4(KSH_OS2_EMX): deleted. + * configure.in: deleted calls to AC_AIX,AC_MINIX,AC_ISC_POSIX, + KSH_OS2_EMX; replaced with KSH_OS_TYPE. + * acconfig.h(OS_ISC,OS_SCO): new undefs. + * sh.h: changed use of isc386 to OS_ISC + * edit.c: changed use of M_UNIX to OS_SCO. + +Sat Apr 29 21:10:54 NDT 1995 Michael Rendell (michael@garfield.cs.mun.ca) + + * vi.c(glob_word): don't append * if there are unescaped globing + characters in the last component of the filename; some redundent + code eliminated. + (based on fix from Michael Jetzer). + +Fri Apr 28 16:10:22 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * eval.c(globit): save/restore actual DIRSEP char - don't use DIRSEP. + + * c_ulimit.c: removed ARGS from declaration of ulimit to avoid + portability problems (osf/1 has ulimit(int,...), os2 has + ulimit(int,long)). + + * tty.c(tty_init): added __SCO__ defines to avoid opening /dev/tty. + + * configure.in,aclocal.m4,acconfig.h: added KSH_OS2_EMX test. + * os2/config.h, os2/configure.cmd, os2/make.sed: updated for new + autoconf. + +Tue Apr 25 12:20:45 NDT 1995 Michael Rendell (michael@dragon.cs.mun.ca) + + * configure.in: added sys/param.h test; changed getcwd test to getwd. + * c_ksh.c(c_pwd): new function. + * sh.h(current_wd, current_wd_size): new variables. + * c_ksh.c(c_cd): changed to handle -L, -P. + * main.c(main): use set_current_wd when setting $PWD; + instead of changing to / when can't get pwd, print warning; + deleted pwd alias; don't make PWD and OLDPWD reaedonly. + * path.c(simplify_path): changed to handle relative paths. + * path.c(make_path): added phys_path argument to support cd -P. + * path.c(set_current_wd,get_phys_path,do_phys_path): new functions. + * misc.c(ksh_get_wd): new function. + * missing.c(getcwd): deleted. + * misc.c(options[]),sh.h: added "physical", FPHYSICAL. + +Mon Apr 24 14:33:03 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * shf.c(shf_smprintf): new function. + + * expand.h(Xsize): new define. + +Fri Apr 21 21:22:44 NDT 1995 Michael Rendell (michael@garfield.cs.mun.ca) + + * sh.h: changed SIZEOF_long to SIZEOF_LONG. + * exec.c(scriptexec): if OS2 ifdefed code, changed ISDIRSEP to + explicit /. + +Thu Apr 20 21:18:12 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * history.c(hist_get) if n < 0, use n + 1 to account for histbackup(). + + * lex.c(set_prompt): added source argument; changed all calls; + changed to do ! and !! substitutions when setting PS1. + * lex.c(pprompt): ifdef'd out code to deal with ! and !!. + + * shf.c(shf_puts): new routine. + * exec.c(herein), lex.c(getsc_): changed to use shf_puts. + +Thu Apr 20 15:50:35 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * siglist.sh: clear traps in subshell to cover for bug in bash 1.4.3 + (based on fix from Fritz Heinrichmeyer). + +Wed Apr 19 12:04:59 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * vi.c(classify): cleaned up table; filled in U_ flag for commands + that don't modify things. + * vi.c(first_insert, saved_inslen): new variables. + * vi.c(vi_reset): don't reset yanklen, inslen, lastcmd, lastac; + set first_insert, saved_inslen. + * vi.c(vi_insert): added code to handle first insertion to allow + redoing commands from last edit. + (based on fixes from Michael Jetzer). + + * vi.c(VVERSION): new state. + * vi.c(classify): cleared C_ flag for 032 (^Z); set it for ^V. + * vi.c(nextstate): added VVERSION. + * vi.c(vi_hook): cases for VVERSION. + * sh.h(ksh_version): new declaration; removed declaration from + all other files. + + * Makefile.in: removed rcs-ci, rcs-diff targets; put RCSFILES + into DISTFILES and removed former. + + * var.c(newblock): copy argc/argv from previous env if it exists. + +Tue Apr 18 23:10:32 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * jobs.c(exchild): report internal error if execute() returns in child. + * exec.c(execute): case TASYNC: clear exec flag in call to execute(). + +Tue Apr 18 12:05:23 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * emacs.c(x_bind): added list argument. + * c_ksh.c(c_bind): added -l (list) option. + + * emacs.c,emacs-c.in: moved emacs.c to emacs-c.in. + * Makefile: add rule to create emacs.c from emacs-c.in. + * emacs-gen.sh: new file. + * emacs.c(struct x_defbindings, x_defbindings[]): new struct/array. + * emacs.c(struct x_ftab, x_ftab[]): removed x_db_tab, x_db_char; + initialize x_ftab[] via script. + * emacs.c(x_init_emacs): changed to load key bindings from + x_defbindings. + * emacs.c(Findex): added typedef. + * emacs.c(x_tab[]): changed to index into x_ftab; changed all refernces. + * emacs.c(xft_*): changed to XFUNC_*. + * emacs.c(XF_PREFIX): new flag, used for x_meta1, 2, 3. + * emacs.c(KPREF,KNULL): deleted (no functional use), changed + references to KSTD. + * emacs.c(x_last_command): changed type to Findex. + * emacs.c(x_emacs): set x_last_command to 0 at start; removed + same from case KEOL. + + * emacs.c(XF_ARG): new flag for struct ftab. + * emacs.c(x_ftab[]): filled in XF_ARG for appropriate commands. + * emacs.c(x_arg_defaulted): new variable. + * emacs.c(x_emacs,x_set_arg): set x_arg_defaulted. + * emacs.c(x_bword, x_fword,x_fold_case): removed use of x_last_command. + * emacs.c(x_fold_upper,x_fold_lower,x_fold_capitailze): trivial + functions that call x_fold_case; changed x_ftab[] to use these + instead of x_fold_case so arbitrary keys can be bound to them. + * emacs.c(x_fold_case): changed to assume argument is 'L', 'U', or 'C'. + * emacs.c(x_del_back,x_del_char,x_prev_histword,x_prev_com,x_next_com, + x_kill,x_insert): use x_arg and x_arg_defaulted. + * emacs.c(x_delete): don't change mark point (xmp) if <= cp; added + force_push argument; changed all calls. + +Mon Apr 17 10:30:12 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * emacs.c(x_e_getc): changed to handle macroptr, ungetting characters. + * emacs.c(x_e_ungetc): new function. + * emacs.c(x_emacs): let x_e_getc() take care of macroptr. + * emacs.c(x_version,x_search_hist): use x_e_ungetc() instead of + macroptr. + * emacs.c(x_set_arg): handle string of digits. + + * emacs.c(x_search_hist): handle deleting chars from search string. + (fix from Dale DePriest) + * emacs.c(x_search): added sameline paramater. + * emacs.c(x_search_list): changes x_zots() to x_e_puts(); make + deleting in empty pattern break out of search. + + * vi.c(domove): case '%': adjust ncursor forward only if matching + opening bracket (so when cursor is on the B in "(fooBar)", c% + changes the openbracket as well. + * vi.c(vi_cmd): case y/d/c: special case to move end point ahead + if move cmd is % and match was to the left of the cursor. + +Thu Apr 13 10:34:26 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * vi.c(complete_word): no bell on ambiguous matches (user can + tell its ambiguous 'cause there is not space or slash appended) + + * configure.in,aclocal.m4: added KSH_MEMMOVE, KSH_MEMSET tests + to fix problems with compiler builtins. + + * misc.c(blocking_read, reset_nonblock) new routines. + * sh.h: deleted O_NONBLOCK ifdefs/defines. + * main.c(main),lex.c(getsc_),edit.c(x_getc),shf.c(shf_fillbuf): + use reset_nonblock(). + (fix based on code from John Rochester) + +Tue Apr 11 14:36:22 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * history.c(c_fc): mostly POSIXized. + * history.c(hist_execute,hist_get_newest,hist_get_oldest): new routines. + * history.c(hist_get,histget): changed histget to hist_get. + * history.c(hist_replace,histrpl): changed histrpl to hist_replace. + * lex.h(SHIST,histpush): deleted; deleted all references. + * history.c(histget): add approx check for history that hasn't + happened yet. + + * misc.c(getn): allow leading plus (eg, +3). + + * main.c(initcoms[]): defined history as "fc -l --". + + * conf-end.h(JOBS): don't define if no posix or bsd process groups + (was if SIGCONT not defined). + +Mon Apr 10 14:51:54 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c(comexec),c_ksh.c(c_getopts),c_sh.c(c_read): use FEXPORT flag. + + * ksh_wait.h: changed to work with autoconf 2.x AC_HEADER_SYS_WAIT - + if sys/wait.h uses union wait, don't include it. + +Thu Apr 6 12:19:58 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * tty.c(tty_init): print warning if open of /dev/tty fails. + +Sat Mar 4 01:20:03 NST 1995 Michael Rendell (michael@garfield.cs.mun.ca) + + * io.c(maketemp): create valid dos filenames. + +Mon Feb 27 11:04:32 NST 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * Changed from autoconf 1.x to autoconf 2.x. + * acconfig.h: included old config.h.top and config.h.bot. + * config.h.top, config.h.bot: deleted; deleted all references. + * install.sh: changed to install-sh; changed all references. + * Makefile.in: use @CPPFLAGS@, @CFLAGS@, @LDFLAGS@; + use @configure_input@; remove config.log and config.cache in + distclean; use @prefix@ and @exec_prefix@. + * ksh_dir.h: changed to use new autoconf defines; changed NLENGTH() + to NAMLEN(); changed all references. + +Mon Feb 27 9:31:02 NST 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * sh.h(ISABSPATH): new macro. + * var.c(setspec): use ISABSPATH() when setting tmpdir. + + * emacs.c(compl_file): added OS2 ifdefs. + * exec.c(scriptexec): OS2: ignore path specified in #! scripts. + * sh.h(ksh_dupbase): OS2: now same as unix. + * trap.c(sigtraps[],inittraps): remove OS2 defines. + * trap.c(alarm_catcher): V7_SIGNALS: use sig, not i. + (Fixes from Dale DePriest) + +Mon Feb 27 10:06:00 NST 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * configure.in: test for resource.h. + * c_ulimit.c: include ksh_time.h instead of sys/time.h; use + HAVE_SYS_RESOURCE_H when including sys/resource.h + (was HAVE_SETRLIMIT). + * aclocal.m4(KSH_RLIM_T): check sys/resources.h for rlim_t. + +Fri Feb 24 17:30:16 NST 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * vi.c(struct macro_state, macro): new structure/variable. + * vi.c(vi_hook, vi_cmd): use macro state info to allow nested macros, + detect recursive macros. + +Wed Feb 22 21:20:43 NST 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * c_ksh.c(c_whence): "an export" instead of "a export". + * vi.c(classify[]): added @<char>. + * vi.c(vi_hook,vi_cmd): added support for @<char> (macros). + (fixes from Frank Edwards). + +Sun Feb 19 11:57:20 NST 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c(comexec): case CFUNC: use cp (not tp->name) when checking if + an autoloaded function was defined; save/restore kshname before/after + function call. + * var.c(popblock): don't set kshname to e->loc->argv[0] - it isn't + always right. + +Fri Feb 10 12:36:16 NST 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * misc.c(parse_args): check OF_SET when building set_opts (was + checking OF_CMDLINE). + + * conf-end.h(JOBS): don't define if SIGCONT not defined. + + * sh.h(FLOGIN) new enum. + * misc.c(options[],parse_args): added login option; set FLOGIN if + name in argv[0] starts with -. + * main.c(main): use FLOGIN flag; changed the way OS2 code looks + for profile. + +Wed Feb 1 09:55:40 NST 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * expr.c(varsub): in FUNSET test, don't always fail # and % + substitutions (test for unset variable). + +Wed Jan 25 09:22:15 NST 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * sh.h(MIN_COLS): new define. + * sh.h(MIN_EDIT_SPACE): new define. + * vi.c(prompt_trunc): new variable. + * vi.c(edit_resize): calculate how much of prompt to truncate. + * lex.c(pprompt): added new argument; changed all calls. + * lex.c(yylex),emacs.c(x_emacs),vi.c(x_vi): move pprompt() inside + x_emacs(), x_vi() or just before read in yylex(). + +Tue Jan 24 12:35:18 NST 1995 Michael Rendell (michael@panda.cs.mun.ca) + + * misc.c(parse_args): changed arrayname variable to array. + * var.c(basename): changed name of function to arrayname(); + changed all references (Based on fix from Dan Quinlan). + +Fri Dec 30 10:34:50 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * ksh.1: modifications to generate two man pages: sh and ksh + (Fixes from Michael Harrdt). + +Wed Dec 28 16:55:13 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * vi.c(complete_word): don't check for globing characters. + +Wed Dec 28 10:32:18 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c(search_access1): don't use ret variable; move "." to end + of xsuffixes/rsuffixes. + * os2.c(_execve): OS2: fixed typo. + * sh.h(FILENCMP): changed stricmp to strnicmp. + * os2/config.h: added define for rlim_t. + * os2/make.sed: changed > null to > nul. + * Makefile.in(dist): generate os2/makefile after running Dist-fixup. + (Fixes from Dale DePriest) + +Thu Dec 22 15:06:06 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.1.3 distribution + + * *.c: removed RCSids. + +Wed Dec 21 11:55:01 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * table.h(struct tbl): changed array field to union of array/fname; + changed all references. + * c_ksh.c(c_whence): print undefined function path. + * exec.c(comexec): do autoloading of undefined functions; print + error if function can't be found. + * exec.c(findcom): fill in tp->u.fname for undefined functions; + search FPATH if search of PATH fails. + * table.h(FC_NOAUTOLOAD): deleted define; removed all references. + +Tue Dec 20 14:16:16 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c(herein): check if name is null. + * lex.h(HEREDELIM,SHEREDELIM,SHEREDQUOTE): new defines. + * lex.c(yylex): added code for HEREDELIM. + * syn.c(synio): use HEREDELIM. + * lex.c(readhere): changed to allow \n in here-delimiter. + + * tree.c(tputS): quote ", ` and $ inside "-quotes. + * tree.c(ptree,pioact): made static. + * tree.c(ptree,fptreef,vfptreef): added indent argument; changed to + use indent argument; changed all calls. + * tree.h(struct ioword): added delim field. + * tree.c(iocopy),syn.c(synio,syntaxerr): deal with delim field. + * tree.c(pioact): print contents of here documents. + + * c_ksh.c(c_typeset): typeset -f foo: set exit code to 1 if function + not found. + +Mon Dec 19 15:14:02 NST 1994 Michael Rendell (michael@garfield.cs.mun.ca) + + * history.c(histinit): increment line number for each history line. + + * exec.c(iosetup): OS2: if open /dev/null fails, try nul instead. + * Makefile.in(debugtools,install,uninstall): make check-pgrp last; + use $E. + * eval.c(eval,expand): OS2: added DODIRSWP code. + * main.c(main): OS2: only include $HOME/kshrc.ksh if interactive. + * sh.h(FILENCMP,FILECMP,FILECHCONV): new defines. + * misc.c(gmatch),vi.c(grabsearch,complete_word),emacs.c(compl_file): + OS2: case insensitive compares. + (fixes from Dale DePriest). + +Mon Dec 19 09:54:42 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * vi.c(vi_cmd): make ~ honour argcnt (fix from Troy Bollinger). + + * vi.c(complete_word): don't add trailing / if there is already one. + * vi.c(glob_word): return rval, not 0. + +Thu Dec 15 11:06:01 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * vi.c(vi_cmd): call complete_word() with argument of 1 not 0. + +Tue Dec 13 12:07:50 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * history.c(histget): made static; added approx argument; changed + all calls. + +Tue Dec 13 10:58:14 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * Makefile.in(mandir): use $(manext), not 1 (fix from Mike Long). + +Mon Dec 12 20:55:53 NST 1994 John Rochester (jr@panda.cs.mun.ca) + + * tree.c(ptree): print TELIF part of if statements + +Fri Dec 9 15:21:36 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * trap.c(inittraps): OS2: don't trap SIGTERM (temproary fix). + + * exec.c(search_access1): OS2: fixed to check for valid suffix + and change mode from X_OK to R_OK if appropriate. + + * edit.c: include <sys/stream.h>, <sys/ptem.h> for SCO unix + (fix from William Bader). + + * c_ulimit.c(c_ulimit): changed type of val from long to rlim_t + (fix from Thomas Gellekum and J.T.Conklin). + * aclocal.m4(KSH_RLIM_T): new test for rlim_t. + * configure.in: use KSH_RLIM_T. + * acconfig.h: added rlim_t. + +Thu Dec 8 12:20:25 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * expr.c(evalexpr): changed div-by-zero test to only derefernce vr + if operation is a divide. + +Mon Dec 5 14:42:52 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c(search): OS2: typo - changed namlen to namelen. + * exec.c(search_access): OS2: check execute bit explicitly. + * main.c(main): OS2: don't include ./profile.ksh. + * options.h(DEFAULT_PATH): OS2: added /os2 to path. + * sh.h(ksh_getdup): OS2: define to getdup(); prototype for getdup(). + * Makefile.in(dist): create os2 Makefile based on distribution + Makefile.in. + +Mon Dec 5 12:17:14 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.1.2 distribution + + * eval.c(globit): when searching directory, re-calculate end of + string based on prefix length. + +Fri Dec 2 11:07:48 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * syn.c(wordlist): if token isn't 'in', don't reject ;. + + * eval.c(expand): leading non-white-space IFS chars no cause initial + empty field. + +Thu Dec 1 12:04:00 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.1.1 distribution + +Thu Dec 1 10:50:38 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * sh.h(TF_FATAL,fatal_trap): new define,variable. + * trap.c(inittraps,trapsig,fatal_trap_check,trap_pending,runtrap, + settrap): use TF_FATAL, fatal_trap. + * trap.c(runtraps): changed argument from bool to TF_* flag; changed + all calls. + * jobs.c(j_waitj): check fatal_trap flag. + +Wed Nov 30 11:20:03 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * conf-end.h: new file. + * config.h.bot: moved guts to conf-end.h. + + * emacs.c(struct x_ftab): changed type of xf_db_char from char to int. + * emacs.c(x_emacs): changed type of c from char to int. + * emacs.c(X_NTAB): new define. + * emacs.c(x_bind,x_init_eamcs): new X_NTAB, X_TABSZ. + * emacs.c(x_prefix3, x_meta3): ifdef OS2. + * emacs.c(x_bind): ifdef OS2; mask *a1 with CHARMASK. + + * exec.c(search_access): new function. + * exec.c(search): use search_access() instead of duplicating test. + * exec.c(search,search_access1): ifdef OS2. + + * Makefile.in(OS2FILES): new macro. + * Makefile.in(dist): add OS2FILES to distribution. + + * options.h(DEFAULT_PATH): ifdef OS2. + * edit.c(x_getc,x_mode): ifdef OS2. + * path.c(make_path): ifdef OS2. + +Tue Nov 29 16:51:35 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * sh.h(EXECSHELL,EXECSHELL_STR): ifdef OS2. + * exec.c(scriptexec): use EXECSHELL_STR (was "EXECSHELL"). + + * trap.c(sigtraps[]): ifdef OS2. + * lex.c(yylex): ifdef OS2. + * misc.c(change_flag): ifdef OS2. + * history.c(HISTFILE): ifdef OS2. + * eval.c(homedir): ifdef OS2. + * c_sh.c(shbuiltins[]): ifdef OS2. + + * sh.h(ksh_execve,ksh_dupbase): new defines. + + * jobs.c(exchild): ifdef use of nice. + +Tue Nov 29 12:32:26 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * eval.c(globit,copy_non_glob): changed to pass/use &xp it can change + (memory can be re-allocated). + + * ksh_dir.h(NLENGTH): new macro. + * eval.c(globit): use NLENGTH macro. + + * alloc.c(aresize): removed redundent 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 (redundent) + use isspace() instead of IFS chars; include <ctype.h>. + + * aclocal.m4(KSH_TERM_CHECK): do not allow HAVE_TERMIOS_H check to + succeed on ultrix (avoid type-ahead loss). + + * emacs.c(x_fword): cahnged loop to skip non word chars, then word + chars (was the opposite). + + * main.c(shell): after error/interrupt/etc, reset an EOF if ignoreeof + option is set. + + * vi.c(classify[]): changed space (040) from C_|U_ to M_ + (got broken in 5.0.10). + + * ksh_wait.h(ksh_waitpid): new define. + * jobs.c(waitpid): moved define to ksh_wait.h; changed use of + waitpid() to ksh_waitpid(). + + * history.c(hist_skip_back),io.c(maketemp): use procpid instead of + getpid(). + +Fri Nov 18 16:08:09 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * vi.c(FSHOW8): inverted meaning: now if set, do the M- stuff + (done so 8 bit char sets work by default). + + * main.c(main): set exstat to 127 if command file can't be opened. + + * main.c(main): use argv[0] instead of kshname when deciding + whether to include profiles. + +Fri Nov 18 14:25:11 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.0.10.1 distribution + + * tty.h: deleted KSH_VDISABLE; moved _POSIX_VDISABLE stuff to edit.c. + * edit.c(x_init): calculate value for vdisable_c. + * edit.c(x_mode): use vdisable_c instead of KSH_VDISABLE. + +Thu Nov 17 12:09:13 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.0.10 distribution + + * lex.c(getsc_),edit.c(x_getc): call runtraps(FALSE) if read is + interrupted. + * vi.c(x_vi),emacs.c(x_emacs): call runtraps(FALSE) (was TRUE). + +Wed Nov 16 09:48:54 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c(execute,scriptexec): call __setostype(0)/(1) before/after + execve() on ISC machines. + + * trap.c(trap_pending): new fuction. + * jobs.c(j_waitj): use trap_pending(); return -<signal-number> if + interrupted. + * jobs.c(waitfor): added sigp argument; changed all calls. + * c_sh.c(c_wait): use signal number set by waitfor() to set exit status. + + * shf.c(SHF_INTERRUPT): no longer calls intrcheck() - now sets + error flag and returns EOF. + * c_sh.c(c_read): re-arranged to have single shf_getc() call; if read + interrupted and signal is fatal (fatal_trap_check()), make read + return with appropriate exit code. + * trap.c(fatal_trap_check()): new function. + * trap.c(inittraps()): catch and cleanup on SIGHUP; don't force the + setting of SIGINT,SIGQUIT,SIGTERM,SIGHUP. + + * table.c(tenter): changed to use strlen()/memcpy() instead of loops. + + * var.c(initvar): new function. + * main.c(main): call initvar(). + * var.c(special): changed to use hash table for lookup. + + * main.c(main),syn.c(initkeywords): moved table initialization + from main() to initkeywords(). + +Tue Nov 15 10:01:20 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * eval.c(copy_non_glob): new routine. + * eval.c(globit): changed to use copy_non_glob() instead of strchr(). + + * misc.c(cclass): if [..] pattern has no closing ], do literal + compare of character with [ (used to always fail). + + * eval.c(globit): handle symbolic links in the check code. + + * configure.in: added check for lstat(). + * ksh_stat.h: defined lstat to be stat if lstat is not available. + + * exec.c(search): return Xclose() instead of Xstring(). + +Mon Nov 14 16:28:41 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * ksh_times.h: changed BROKEN_TIMES to TIMES_BROKEN. + + * c_test.c(syntax): removed \n from error messages. + + * eval.c(glob,globit): changed to use dynamicly allocated string + instead of a fixed sized buffer. + +Thu Nov 10 10:47:55 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * history.c(sethistsize): don't set size if new size is < 0; fixed + offset calculation so histptr is not way beyond the end of array; + if history is shrinking, save newest history back. + + * vi.c(vi_hook): case VSEARCH: call restore_cbuf() after \n or \r. + + * main.c(quitenv): call restfd() even if fd < 0 to re-close fd. + + * exec.c(execute): commented out code that set savefd[0/1] to -1 + if input/output was a pipeline. + + * missing.c(dup2_fixup): deleted function. + * sh.h(dup2->dup2_fixup): deleted define. + * io.c(ksh_dup2): new function; changed all dup2() calls to ksh_dup2(). + +Wed Nov 9 11:11:31 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * edit.h(struct edchars): added eof field. + * edit.c(x_init): initialize eof fields. + * vi.c(x_vi): changed ^D to edchars.eof. + * vi.c(vi_cmd): make I/cc/S skip blanks. + + * history.c(histsave): EASY_HISTORY: use memmove() to copy pointers + back one. + + * vi.c(vi_cmd): make G act the same as at&t ksh. + * vi.c(ismeta,O_): deleted macros; removed all references to O_. + * vi.c(classify[]): add ^X and ^F to command mode. + +Tue Nov 8 11:15:01 NST 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * main.c(initsubs[]): don't set SHELL. + + * vi.c(vi_cmd): added v command (start up vi). + * vi.c(vi_hook): added case for vi_cmd() returning 2. + * vi.c(grabsearch): set anchored flag if pattern starts with ^. + (based on fixes from Michael Jetzer). + + * history.c(findhist): added anchored argument; changed all calls. + * history.c(histget): start searching from histptr-1; changed to + call findhist() to do searching. + * history.c(c_fc): changed to print multiline commands correctly. + (based on fixes from Michael Jetzer). + +Fri Nov 4 10:30:14 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * lex.c(yylex): when pushing alias sources, allocate from existing + source's area. + + * lex.c(struct source): added areap field. + * lex.c(pushs): added area argument; changed all calls. + * history.c(histrpl): changed constant sized hline[] to expandable + string; removed hline/hsize parameters; changed all calls; put + newline at end of string. + * history.c(c_fc): changed to use dynamically sized buffer when reading + commands; strip nulls after read. + * history.c(histbackup): made static. + + * trap.c(block_pipe): if handler is SIG_DFL, change it to SIG_IGN. + + * lex.c(readhere): changed to allow eof after end-of-file marker + (bug report from Andrew Moore). + +Thu Nov 3 09:09:39 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * io.c(coproc_read_close,coproc_write_close): new functions. + * c_sh.c(c_read): call coproc_read_close() when eof is read. + * c_ksh.c(c_print): set PO_COPROC if fd is coproc.write; call + coproc_write_close() if write fails due to EPIPE. + * exec.c(iosetup): call coproc_write_close() after #>&p. + * sh.h(EF_COPROC_DUPED): deleted. + * sh.h(struct coproc): deleted isopen field. + * io.c(cleanup_coproc): do not use isopen field. + * c_sh.c(c_exec): deleted EF_COPROC_DUPED code. + * exec.c(TCOPROC): don't set isopen; don't start new coprocess if + old job exists and write pipe hasn't been closed. + + * misc.c(str_zcpy): new function. + * lex.c(getsc_): made line[] buffer local/static; use str_zcpy() + to fill line[]. + * history.c(c_fc): use local hline buffer instead of global line[]; + use str_zcpy() to fill hline[]; + * history.c(histrpl): added hline and hsize parameters; changed all + calls. + * history.c(hist_init): EASY_HISTORY: use local hilne buffer instead + of global line[]. + * lex.h(line[]): deleted. + * syn.c(compile): do not set s->str to null for STTY and SHIST. + +Wed Nov 2 11:48:36 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * lex.c(getsc_): case SDDPAREN: set csstate before going to + SPAREN state. + + * Makefile.in(RCSFILES): removed POSIX from list (now covered in + man page). + +Tue Nov 1 09:27:46 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * eval.c(comsub): save/restore source before/after compile(). + + * c_ulimit.c(c_ulimit): allow value to be arithmetic expression + (as per Korn book). + + * c_sh.c(c_read): call set_prompt() before printing prompt. + + * expr.c(v_evaluate): treat an empty expression as 0. + +Mon Oct 31 09:23:57 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * vi.c(grabhist,grabsearch): check that history line doesn't overflow + edit buffer. + + * history.c(hist_finish): (EASY_HISTORY) changed for-loop condition to + prevent passing the end of history. + + * eval.c(expand): when stuffing MAGIC, cast c to char. + + * misc.c(strip_nuls): new function. + * lex.c(getsc_): case STTY/SFILE/SSTDIN: call strip_nuls() after + reading commands. + + * edit.c(set_editmode): reversed strstr() arguments - check for + vi/emacs in $EDITOR/$VISUAL string. + + * syn.c(yyparse): allow EOF as well as newline after a command. + * lex.c(getsc_): case SSTRING: don't fake newline + +Sun Oct 30 10:55:20 NST 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * c_ksh.c(c_print): echo: check for -n, -e and -E options. + + * exec.c(comexec): don't allow command -p if restricted. + +Fri Oct 28 10:24:48 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * var.c(typeset): in fake_assign code, was freeing t->val.s + t->type + instead of t->val.s - now uses free_me variable instead of aflag. + + * Makefile.in(depend): change blank lines in depend output to sh.h + so dumb make(1)s won't die. + + * mail.c: changed checking to use atime/mtime instead of size; changed + struct mbox mb_size field to mb_mtime, changed all references. + + * main.c(shell): do not execute (or set the exit status for) a null + command. + * lex.c(readhere): read the newline after the eof marker. + +Wed Oct 26 09:11:08 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * eval.c(globit): added FMARKDIRS support. + + * emacs.c(x_ftab[]): added entries for ansi arrow key bindings. + + * exec.c(execute,iosetup): move tracing of redirections from + execute() to iosetup() so expanded name can be printed. + + * exec.c(execute): case TDBRACKET: read was being called instead of + test. + + * ksh_stat.h(S_ISCDF): new define. + * c_test.c: added -H for context dependent files (HP bizarreness). + + * main.c(initcoms[]): added alias local=typeset. + + * Makefile.in(stamp-h,config.status): added double quotes CONFIG_FILES + and LDSTATIC assignments for dmake. + * aclocal.m4(KSH_SYS_SIGLIST): do something with sys_siglist so it + isn't optimized away. + * aclocal.m4(KSH_CLOCK_T): do extra check for clock_t in sys/times.h. + * acconfig.h(CLOCK_T_IN_SYS_TIMES_H): new define. + * sh.h(SIGNALS): use _SIGMAX if NSIG, _MINIX not defined. + (fixes from Brian Campbell <brianc@qnx.com>) + + * emacs.c(x_transpose): changed behavior if FGMACS flag set + (fix from <guy@netapp.com>). + +Tue Oct 25 17:11:58 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * tty.c(KSH_VDISABLE): new define. + * edit.c(x_init): use KSH_VDISABLE. + +Tue Oct 25 09:55:09 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.0.9 distribution + + * c_ulimit.c(c_ulimit): changed SOFT, HARD from enum to defines + to avoid problems with ancient compilers. + + * vi.c(CHAR_LEN,char_len): changed macro to function; added FVISHOW8 + support. + * misc.c(options[]), sh.h(FVISHOW8): added FVISHOW8 option. + +Sun Oct 23 11:02:26 NDT 1994 Michael Rendell (michael@maple.cs.mun.ca) + + * main.c(shell): keep unwinding if LINTR and not interactive. + + * lex.c(yylex): do redumentery quote parsing for $(..). + +Thu Oct 20 11:02:27 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c(execute): case TSELECT: set rv to 1 if eof is read. + * exec.c(execute): case TFOR/TSELECT/TWHILE/TUNTIL: set rv to 0 before + entering loop, but after setjmp incase of a continue; rv to 0 + after a break. + * exec.c(execute): case TFOR/TSELECT: do readonly check before + assigning value. + * c_ksh.c(c_getopts): do readonly check before assigning value. + + * misc.c(print_columns),c_ksh.c(kill_fmt_entry), + misc.c(options_fmt_entry),exec.c(select_fmt_entry): new functions. + * c_ksh.c(c_kill),misc.c(printoptions),exec.c(pr_menu): use + print_columns() call a call-back routine to format information + in columns. + +Wed Oct 19 10:26:25 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * misc.c(cclass): require MAGIC before - and ]. + * eval.c(expand): prefix - and ] with MAGIC if appropriate. + + * var.c(typeset): don't allow export flag of readonly variables + to be cleared. + + * eval.c(globit): added call to intrcheck(). + +Mon Oct 17 11:48:05 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * lex.c(readhere): check for and report write errors. + +Sun Oct 16 16:10:59 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * c_ksh.c(c_cd): don't allow cd if restricted. + * exec.c(comexec): if restricted and command contains /, print error. + * exec.c(ioestup): if restricted, don't allow file creations. + * main.c(is_restricted): new function. + * main.c(main): save and reset FRESTRICTED during .profile/ENV reading; + set FRESTRICTED if argv[0] or SHELL refers to restricted shell; + make PATH, ENV, SHELL readonly if restricted. + * var.c(typeset): check for restricted shell and PATH/ENV/SHELL. + +Thu Oct 13 21:01:14 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * main.c(shell): only call j_notify() for interactive shells. + + * c_sh.c(c_read): check if variable is readonly before assigning + value. + +Wed Oct 12 14:08:46 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * lex.h(COPROC),tree.h(TCOPROC,XCOPROC): added defines. + * lex.c(yylex): return COPROC for |& token. + * syn.c(tokentab): added COPROC. + * syn.c(c_list): accept COPROC, create TCOPROC node. + * tree.c(ptree): added case for TCOPROC. + * exec.c(execute): added case for TCOPROC. + * io.c(check_fd,get_coproc_fd): new functions. + * c_sh.c(c_read),c_ksh.c(c_print): changed to use check_fd(); + added -p option; for c_print() ensure SIGPIPE doesn't kill shell. + * exec.c(iosetup): changed to use check_fd() for IODUP; when + checking fore close, require exactly the string '-', not any + string starting with '-'; added strerror() to error message. + * jobs.c(exchild): don't open /dev/null if XCOPROC; close + coproc.read/write/childread in child if XCOPROC; don't pass + XCOPROC flag on to execute(); set coproc.job to job in parent + if XCOPROC. + * jobs.c(check_job): clear coproc.job if said job dies. + * trap.c(block_pipe,restore_pipe): new functions. + * sh.h(struct coproc, EF_COPROC_DUPED): new structure and define. + * c_sh.c(c_exec): if EF_COPROC_DUPED set, clean up co-process stuff. + + * main.c(cleanup_parents_env): new function. + * jobs.c(exchild): call cleanup_parents_env() after fork(). + + * tree.h(IORDUP): new define. + * lex.c(yylex): changed redirection parsing to not accept & only after + a single < or >; set IORDUP flag for x<&y; fixed <</<>/>> check to + not allow >< (again). + * tree.c(pioact): use IORDUP flag to print <& or >&. + + * jobs.c(exchild): set JF_ORIGFG flag if job started in foreground. + * jobs.c(j_waitj): don't get default tty settings if JF_ORIGFG not + set. + + * misc.c(parse_args): treat -A as a flag that is handled later + (used to require argument); do array setting after argument + sorting. + * var.c(set_array): changed second argument from 0/1 flag to + -1/1 flag; changed all calls. + +Thu Oct 6 11:55:27 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * table.c(tinit): added initial table size argument; call texpand + if size isn't 0; changed all calls. + * main.c(main): try to make sure table size is big enough for + builtins and keywords (cut down on amount of re-hashing). + + * eval.c(expand): added next and prev fields to struct SubType; + removed fixed length subtype array, changed code to allocate + SubTypes as needed. + +Wed Oct 5 09:25:06 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * main.c(main): moved initio() above inittraps() as later can print + stuff. + + * table.h(IMPORT): new flag. + * var.c(typeset): if IMPORT flag set, don't allow array references, + insist on assignment. + * var.c(import): deleted function. + * main.c(main): use typeset() instead of import(). + + * sh.h: include expand.h. + * expand.h(Xnleft): new define. + * expand.h(struct XString, Xinit): added areap field; added area + argument to Xinit; changed all calls. + * lex.h(struct source): added xs field. + * shf.c(shf_gets,shf_getse): changed name fromshf_gets to shf_getse; + return pointer to null byte instead of start of buffer. + * lex.c(pushs): if type is SFILE or SSTDIN, initialize s->xs. + * lex.c(getsc_): case SFILE/SSTDIN: use s->xs instead of fixed + size line buffer. + + * syn.c(compile): don't change s->str if SFILE. + * main.c(main): call pushs() explicitly for each of SSTRING, + SFILE, SSTDIN, STTY. + + * aclocal.m4(KSH_GCC_FUNC_ATTR): changed GCC_FUNC_ATTR to + HAVE_GCC_FUNC_ATTR. + * config.h.bot: changed use of GCC_FUNC_ATTR; deleted + GCC_FA_NORETURN, GCC_FA_CONST, GCC_FA_FORMAT defines, created + generic GCC_FUNC_ATTR define; changed all uses of GCC_FA_*. + + * main.c(main): set s->file for SSTDIN input. + + * main.c(shell): pass LERROR on if not interactive. + + * expand.h(Xcheck,XcheckN): added XcheckN define, changed Xcheck + to use XcheckN; made XcheckN call Xcheck_grow_() do do any real work + (to cut down on code size). + * misc.c(Xcheck_grow_): new function. + * exec.c(search),c_sh.c(c_read): changed to use Xstring() routines + (used to use the fixed size buffer line[]). + * exec.c(findcom): avoid re-saving search() result in ATEMP. + +Tue Oct 4 15:32:37 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * jobs.c(j_jobs): return int value indicating error/ok; changed + all calls. + + * misc.c(getn): added int * argument to hold result; changed + return value to indicate success/failure; changed all calls. + * misc.c(bi_getn): new function. + * misc.c(getn_): deleted function. + + * io.c(internal_error,error_prefix,warningf): new functions. + * *.c: changed errorf() calls reporting internal errors to + use internal_error() function; changed many shellf()s to + warningf(). + * io.c(errorf),lex.c(yyerror): changed to use error_prefix(). + + * alloc.c(aprint): ifdef'd out. + * tree.c(phash): deleted function. + +Mon Oct 3 15:08:24 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * sh.h(kshname): new variable + * main.c(main): changed name to kshname, deleted local variable. + * exec.c(comsub): update kshname. + * var.c(popblock): restore kshname. + * io.c(errorf,bi_errorf): print shell name before error message. + + * c_ksh.c(c_cd): print new directory on stdout, not stderr. + + * sh.h(GI_MINUS): new define. + * misc.c(ksh_getopts): changed so once - or + introduces option, + all options must start with same character. + + * sh.h(builtin_argv0): new variable. + * exec.c(call_builtin): set/clear builtin_argv0, builtin_flag; changed + argument to a struct tbl *; changed all calls. + * io.c(bi_errorf): new function. + * c_ksh.c,c_sh.c,c_ulimit.c,emacs.c,history.c,jobs.c: changed all uses + of errorf() to bi_errorf(). + * emacs.c(x_bind): changed return value to int; changed all calls. + * history.c(histrpl): return 0 if there is an error; changed all calls. + * misc.c(parse_args): use bi_errorf(); return -1 for error; changed all + calls. + * misc.c(ksh_getopts): call bi_errorf instead of errorf which means + ksh_getopts() may return after an error, so changed all calls to + check for '?' return. + + * exec.c(iosetup): use shellf() to report errors and return value + indicating success or failure. + * exec.c(execute): if iosetup fails, cause fatal error for special + builtins, return otherwise; print PS4 and redirections. + +Fri Sep 30 15:17:37 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * c_ulimit.c(c_ulimit): accept unlimited as a valid value. + + * c_test.c(c_test): changed posix special case code to use + while loop. + + * c_ksh.c(c_whence): for whence -p, don't look for built-ins or + fuctions. + +Thu Sep 29 10:34:59 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * c_ksh.c(c_alias): added -r option so the sysv-bounre shell + hash -r will work. + + * eval.c(debunk): use strchr() to find first MAGIC, if any. + +Wed Sep 28 15:34:32 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * sh.h(O_NONBLOCK): define to O_NDELAY or FNDELAY if not defined. + * main.c(main): if stdin is O_NONBLOCK'd, clear O_NONBLOCK. + + * misc.c(options[], parse_args): make -c a normal flag, not an option + with an argument (POSIX); deleted cargp argument to parse_args(). + * main.c(main): print error if -c and no arguments left. + + * lex.h(SSTDIN): new define. + * lex.c(yylex): added case for SSTDIN. + * main.c(main): if -s flag used, set source type to SSTDIN. + +Tue Sep 27 08:52:11 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * lex.c(get_brace_var): new function. + * lex.c(yylex): removed most ${..} parsing - leave it to expand(); + use get_brace_var() to read the variable part of a ${..} expression. + * tree.c(tputs,wdscan): case OSUBST: delete code that understood + partially compiled ${..}. + * sh.h(C_SUBOP,C_SUBOP1,C_SUBOP2): changed C_SUBOP to C_SUBOP1, + added C_SUBOP2. + * misc.c(initctypes): removed # and % from C_SUBOP; changed C_SUBOP to + C_SUBOP1; added C_SUBOP2. + * eval.c(varsub): look at word part of substitution to figure out + type of substitution; check for bad substitutions; check for unset + variables for #/% substitutions. + * eval.c(struct SubType): changed type field to stype; changed quote + field to short; added f field. + * tree.h(DOTEMP_): new define. + * eval.c(expand): case CSUBST: case '=': deleted bad substitution + error (now handled in varsub); case OSUBST: removed special handling + of trimming - varsub() does it now; when pushing/poping state (st), + save/restore value of f; set f to DOPAT when trimming; case CSUBST: + case '=': restore original position in string, substitute the value + of the variable (as opposed to the value that was assigned to the + variable); case OSUBST: if '?' qualifier, turn off DOBLANK when + expandined word part; define DOTEMP_ when expanding word part + of ${..[#%=?]..}; deleted first_eq and tstart - replaced with + tilde_ok and saw_eq. + + * eval.c(expand): tilde expansion: use tstart variable instead of cp; + changed '?' error message to be like at&t ksh; don't test if strval() + returns NULL - it doesn't. + + * var.c(strval): if !ISSET, instead of returning null, set s to null. + + * exec.c(comexec): case TDBRACKET: don't pass DOASNTILDE to evalstr(). + + * exec.c(scriptexec): changed line[] to buf[] so it doesn't get + confused with global the line[]. + + * main.c(initsubs): initialize PS4. + * edit.c(x_getc): cast char to unsigned before returning. + +Mon Sep 26 11:06:55 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * eval.c(globit): call strnsave instead of strsave; if file has + trailing /, use stat() to check that it is a directory. + + * eval.c(expand): case CSUBST: case #/%: deleted duplicate *dp = 0; + case CSUBST: case =: copy string and call debunk() to oust MAGICs. + + * misc.c(print_value_quoted): deleted bogus shf_shlout argument to + shprintf(); deleted unneeded test (p != s). + + * main.c(main): turn on FBRACEEXPAND. + * misc.c(change_flag): turn FBRACEEXPAND off if turning FPOSIX on. + + * vi.c(x_vi): use x_vi_zotc() to print ^D. + * vi.c(CHAR_LEN): new define. + * vi.c(vi_hook): use CHAR_LEN() instead of inline tests for + c < ' ' || c== 0x7f; search editing: display M- if necessary. + * vi.c(display): changed to deal tiwh meta-characters. + + * vi.c(x_vi_zotc): print M- for meta chars. + * emacs.c(x_e_getc): new function; changed all x_getc() calls to + x_e_getc() calls. + * edit.c(x_getc): don't and out upper bit. + + * sh.h(OPAREN,CPAREN,OBRACK,CBRACK,OBRACE,CBRACE): new defines + * expr.c(OPAREN,CPAREN): re-named to OPEN_PAREN, CLOSE_PAREN. + + * eval.c(debunk): changed to convert MAGIC MAGIC -> MAGIC. + * eval.c(expand): removed ismagic_bracket stuff - not needed. + * eval.c(expand): always restore value of quote when CSUBST + reached; don't set DOGLOB in fdo if trimming. + +Sat Sep 24 11:46:03 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * tree.h(DOBRACE_): new define. + * eval.c(expand): changed check for leading ! in [..] to be more + robust (old test could have looked before start of string). + * eval.c(expand,maybe_expand_tilde): case ~: moved code into a function + (maybe_expand_tilde). + * eval.c(expand): expand alternations after macros, before globing + (was before macros). + * eval.c(alt_expand): changed to be called after macro expansion. + * eval.c(alt_scan,alt_count): deleted (no longer needed). + + * misc.c(cclass): return NULL (no match) if first char in a range + is greater than the second. + * eval.c(expand): when building strings, stuff literal MAGIC chars. + +Thu Sep 22 15:05:48 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c(comexec): re-arranged handling of builtin and exec; + handle command (and command -p, etc.); deleted comexec_flags + variable; made function static again; removed fcflags argument. + * table.h(FC_NOBLOCK): deleted define. + * c_sh.c(c_exec): changed empty function to deal with preserving I/O + redirects (code taken from comexec()). + * c_ksh.c(c_command): new function - calls c_whence. + * c_ksh.c(c_whence): removed code to deal with command -p. + + * Makefile.in: changed [ to test. + * shf.h: changed errno structure member to errno_; changed all uses + (fixes for QNX from Brian Campbell). + + * c_test.c(enum Op): deleted trailing comma (some compilers complain). + * proto.h: added volatile to tp arg of comexec() prototype. + +Thu Sep 22 11:08:31 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.0.8 distribution + + * Makefile.in(install): added missing dollar (fix from Thomas Gellekum). + + * emacs.c: changed CMASK to CHARMASK to avoid conflicts with some + system headers (eg, HP-UX 9.01 <sys/param.h>). Reported by Sean + Hogan. + + * history.c(c_fc): wp not being incremented; -e strcmp() test reversed + (reported by Sean Hogan). + +Thu Sep 21 21:12:03 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.0.7 distribution + +Tue Sep 20 09:56:54 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * history.c(c_fc): use ksh_getopt() to parse arguments. + * c_ksh.c(c_bind): use ksh_getopt() to parse arguments. + + * main.c(initcoms[]): changed hash alias from alias -t - to alias -t --. + + * misc.c(print_value_quoted): don't use quotes if no special + characters. + + * c_ksh.c(c_whence): added POSIX command command. + + * c_sh.c(c_label): removed check for null wp. + + * exec.c(comexec): added new flags argument (FC_*); + don't call newblock() if FC_NOBLOCK set; pass flags on to + findcom(); changed all calls; made comexc() a non-static + function. + + * table.h:(FC_SPECBI,FC_FUNC,FC_REGBI,FC_UNREGBI,FC_PATH,FC_DEFPATH, + FC_NOAUTOLOAD,FC_NOBLOCK): new defines. + * exec.c(findcom): merged insert/justsearch/autoload arguments + into one flags argument; changed code to check various flags; + changed all calls. + +Sat Sep 17 20:17:59 NDT 1994 Michael Rendell (michael@garfield.cs.mun.ca) + + * exec.c(comexec): print error if builtin has no command. + + * exec.c(execute): before doing redirections, check for TCOM and + evaluate arguments and determine if it is a special builtin; + print arguments (using PS4) if FXTRACE set; case TCOM: simply call + comexec(). + * exec.c(comexec): deleted vp argument; only call newblock() if + needed (ie, !special, !empty); evaluate assignments and put + in environment one at a time; print environment (using PS4) if + FXTRACE set; removed code to turn empty command into :; + removed environment setting code in switch statement. + * exec.c(echo): deleted function. + + * lex.c(yylex): only honour CMDWORD if FPOSIX set. + + * c_sh.c(shbuiltins): removed = attribute from false/true commands. + + * sh.h(E_TCOM): delete define - not used. + + * sh.h(null),var.c: use EXTERN for initialization of null. + * sh.h(space,newline,slash): new variables (" ", "\n", "/") + use these everwhere instead of "", " ", "\n", "/". + * path.c: include sh.h. + + * exec.c(execute): combined TFOR/TSELECT cases. + +Fri Sep 16 11:32:01 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * lex.h(CMDWORD): new define to prevent continued alias expansion in + non-command contexts. + * lex.c(yylex): only set ALIAS if SF_ALIAS and CMDWORD are set. + * syn.c(get_command): case LWORD/REDIR: pass CMDWORD if argc is 0. + + * exec.c(comexec): if there is no command, do assignments and set + the return value to subst_exstat (used to fake a : command). + + * sh.h(subst_exstat): new variable. + * exec.c(execute): case TCOM: clear subst_exstat before doing eval()s. + * eval.c(expand): set subst_exstat to return value of waitlast(). + * c_sh.c(c_set): if !FPOSIX, return subst_exstat instead of 0. + + * exec.c(execute): removed redundent "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 seperately from JOBS. + * jobs.c(j_kill): only send SIGCONT if job is stopped. + * jobs.c(j_jobs): remove exited/signaled jobs even if !FMONITOR, + un-ifdef JOBS same. + + * jobs.c(check_job): ifdef FNOTIFY with JOBS (noted by + Michael Haardt <u31b3hs@POOL.Informatik.RWTH-Aachen.DE>). + * jobs.c(j_notify,j_waitj): put ifdef JOBS around use of FMONITOR. + * main.c(shell): removed ifdef JOBS from declaration of interactive. + + * ksh_limval.h,sh.h: moved include of <limits.h> from ksh_limval.h + to sh.h since some machines define CLK_TCK there. + +Thu Sep 15 09:58:14 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * vi.c(vi_cmd): took out ESC as file completion char in command mode + (too annoying). + + * jobs.c(exchild): if starting a job in the background and FBGNICE + is set, call nice(). + + * Makefile.in: changed maxext to manext (fix from Thomas Gellekum + <thomas@ghpc8.ihf.rwth-aachen.de>); in the install target, check + if the path of the installed shell is in /etc/shells and + complain if it isn't; added depend target, removed old $(OBJS) + and trap.o dependencies. + +Wed Sep 14 09:39:55 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * misc.c(options[]): changed position of vi-tabcomplete option. + + * lex.c(yylex): ifdef use of FVI/FEMACS/FGMACES with VI/EMACS + (fix from Gordan Larson <hoh@approve.se>). + + * ksh.1(DESCRIPTION): added missing P in \fP + (fix from Gordan Larson <hoh@approve.se>). + +Tue Sep 13 11:01:47 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.0.6 distribution + +Mon Sep 12 11:39:07 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c(scriptexec): changed so it will compile if SHARPBANG is + defined; fixed error message; shell argument is everything up to + a newline; don't listen to #! line unless it ends in a newline. + + * syn.c(get_command): case FOR: changed VARASN to ARRAYVAR. + + * jobs.c(waitfor): restore signal mask before returning if named job + isn't own own; when waiting for unspecified jobs, only consider + running jobs; don't pass JW_STOPPEDWAIT flag to j_wait. + + * table.h(V_TMPDIR,tmpdir): new define/variable. + * var.c(setspec, unsetspec): added case for V_TMPDIR. + * io.c(maketemp): use tmpdir variable if it is set, else use /tmp. + + * var.c(popblock): if poping a variable that wasn't set in the old + environment, call unsetspec(). + +Fri Sep 9 10:37:18 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * sh.h(PATHMAX): increased value from 256 to 1024. + + * main.c(main): moved initialization of name to start of main; + if getcwd() fails, use name in error message and call shf_flush(). + + * io.c(maketemp): check/use TMPDIR variable instead of /tmp; allocate + temp structure and path in one chunk. + + * c_ksh.c(c_cd): when checking for no home directory, compare + against null, not (char *) 0. + +Thu Sep 8 10:52:59 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * lex.c(yylex): case SHIST: flush shl_out after printing command. + + * jobs.c(check_job): when notifing, do not remove job if it is stopped. + +Wed Sep 7 10:55:35 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.0.5 distribution + + * main.c(shell): commented out shf_flush(shl_out) - shouldn't be + needed since -v flushes itself. + +Tue Sep 6 09:30:57 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * sh.h(LSHELL,really_exit): new define/variable. + * c_sh.c(c_exitreturn): if how is LEXIT, check if there are stopped + jobs and, if so, unwind with LSHELL (also check/set really_exit). + * main.c(shell): added case for LSHELL - stop unwinding if shell + is interactive; changed local reallyquit to global really_exit. + * main.c(include),exec.c(comexec): added case for LSHELL. + + * c_sh.c(c_exitreturn): quitenv() for LRETURN as well as LEXIT. + + * sh.h(TF_CHANGED): new define. + * trap.c(runtrap): default EXIT/ERR trap during execution and restore + original if TF_CHANGED not set. + * trap.c(settrap): set TF_CHANGED when setting trap. + + * jobs.c(j_stopped): check that job created by current process; print + "You have stopped jobs" message. + * main.c(shell): don't print you have stopped jobs message. + + * main.c(initcoms): removed echo alias. + * c_ksh.c(kshbuiltins): added echo as a builtin. + * c_ksh.c(c_print): if wp[0] is echo, act like strict sysv echo; + added \a (BEL) escape sequence. + + * syn.c(function_body): new function; calls get_command() to get + the body of a function (old code did nested { } block which + caused problems with how redirections after the block were + handled). + * syn.c(get_command): call function body to deal with foo() and + function foo. + + * jobs.c(restore_ttypgrp): new variable. + * jobs.c(j_change): set restore_ttypgrp if process group is set. + * jobs.c(j_exit): if necessary, restore tty process group for main + shell. + +Fri Sep 2 21:32:03 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * main.c(main): set FPRIVILEGED if uid (gid) doesn't match euid (egid); + don't include $HOME/.profile if FPRIVILEGED; include + /etc/suid_profile instead of $ENV if FPRIVILEGED. + * misc.c(change_flag): if clearing FPRIVILEGED flag, set euid (egid) + to uid (gid). + +Fri Sep 2 21:10:23 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * main.c(main): don't include $ENV if uid (gid) doesn't match + euid (egid) (from J.T.Conklin). + +Fri Sep 2 12:07:14 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * syn.c(get_command): removed MPAREN case, taken care of in '(' case, + as per POSIX.2 which says () is two operators, not one. + * lex.c(yylex): don't check for/return MPAREN. + * lex.h(MPAREN): deleted define. + + * configure.in: add test for library routine confstr(); add + header test for paths.h. + * sh.h: include paths.h if available; define DEFAULT__PATH. + * table.h(def_path): new variable. + * options.h(DEFAULT_PATH): new define. + * main.c(main): initialize value of def_path; set path to def_path; + remove PATH initalization from initsubs; do not set value of HOME + variable (POSIX); allow SHELL, PS1, PS2, PS3 to have empty values + (at&t ksh). + * var.c(unsetspec): when unsetting PATH, set path to def_path. + + * jobs.c(j_waitj): restore proc mask before calling error if + 1st tcsetpgrp() fails. + +Thu Sep 1 10:28:03 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * Makefile.in: added check-sig.c, check-fd.c and check-pgrp.c + to RCSFILES; added rules for compiling the above; added debugtools + target to compile them all. + + * c_test.c(arg0,t_error,T_ERR_EXIT): new variables/defines. + * c_test.c(c_test): set arg0 to wp[0], t_error to 0; after + calling eval_binop() or oexpr() check t_error and if set, + return T_ERR_EXIT. + * c_test.c(syntax): set t_error exit; use shellf() instead of + errorf(); use arg0 instead of "test"; delete GCC_FA_NORETURN + attribute; changed all calls to return after calling. + * c_test.c(oexpr,aexpr,primary): check terror after calling + oexpr(), aexpr(), nexpr(). + + * c_test.c(primary): if unary operator is -t and there is no + argument, don't increment t_wp; if missing closing parenthesis, + show next operand (if any) in error message. + * c_test.c(eval_unop): default case, print t_wp[-2] (was -1). + * c_test.c(c_test): set t_wp before calling eval_binop() incase + there is an erorr. + * c_test.c(syntax): print first message even if op is an empty string. + +Wed Aug 31 11:48:51 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * expr.c(O_LT, O_GT): reverse order of enums to match opinfo table. + + * c_test.c(nexpr): always call !nexpr() (never !primary()). + * c_test.c(c_test): switch on argc-1 to make code match POSIX + description; make 4 arg case fall into 3 arg case, and 3 arg case + fall into 2 arg case. + * c_test.c(is_not,is_and,is_or): new defines. + * c_test.c(c_test,oexpr,aexpr,nexpr): use is_not,is_and and is_or. + * c_test.c(primary): don't decrement t_wp in final string case. + + * c_test.c(eval_unop): change S_ISIFO to S_ISFIFO and S_ISFITO + to S_ISFIFO. + + * misc.c(parse_args): use OF_SET when initializing set_opts. + +Wed Aug 31 09:32:39 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * made pdksh-5.0.4 distribution + + * jobs.c(j_change): do not restore tty process group when turning + off job control; no need to save original tty process group; + deleted orig_ttypgrp variable. (fixes bug in which turning off + job control causes an interactive shell to exit) + +Tue Aug 30 14:43:48 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * aclocal.m4(KSH_OPENDIR_CHECK): always include sys/types.h; + set return value according to what failed. + +Tue Aug 30 11:17:09 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * missing.c(strerror): for systems without sys_errlist[], report + error number if unknown. + + * Makefile.in: added BUG-REPORTS to DISTFILES. + + * jobs.c(exchild): do tcsetpgrp() in both parent and child after + the first process is created (may need to change to every child). + + * aclocal.m4(KSH_PGRP_SYNC): new test - defines NEED_PGRP_SYNC. + * acconfig.h: added define for NEED_PGRP_SYNC. + * configure.in: use KSH_PGRP_SYNC test. + * jobs.c(exchild,j_startjob,j_sync_open,j_sync_pipe): if NEED_PGRP_SYNC + is defined, use a pipe to block the first process in a pipeline + until the whole pipeline is set up. + +Mon Aug 29 09:15:00 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * jobs.c(exchild): for background, unmonitored jobs, don't open + /dev/null if input is a pipe. + + * jobs.c(exchild): for background, unmonitored jobs, use setsig() + instead of setexecsig() to set up SIGQUIT and SIGINT; changed + restoration of SIGTSTP,SIGTTIN,SIGTTOU - set them to DFL if + monitoring and not a `..` command, otherwise leave them alone. + * jobs.c(j_init): only use SIGTSTP,SIGTTIN,SIGTTOU if talking + or monitoring - if just talking leave signals ignored. + * jobs.c(j_change): if going into job control, set TF_SHELL_USES + flag for sigtraps[SIGTSTP,SIGTTIN,SIGTTOU]; if leaving job control + ignore signals if interactive, else restore original signals. + + * table.h(SPEC_BI, REG_BI): new defines. + * exec.c(builtin): check for * or + in front of builtin names and set + SPEC_BI or REG_BI if found. + * exec.c(findcom): search for special builtins first, then functions, + then regular builtins, then PATH search. + * c_sh.c(shbuiltins[]),c_ksh(kshbuiltins[]): add */+ in front of POSIX + special/regular builtins; add = infront of unset; + remove = from alias. + * c_sh.c(c_label): set exit value according to name (for true/false). + * c_sh.c(shbuiltins[]): add entries for true and false. + * main.c(initcoms[]): deleted true/false aliases. + + * aclocal.m4(KSH_OPENDIR_CHECK): new test - see if opendir() will + open non-directories. + * configure.in,acconfig.h: added KSH_OPENDIR_CHECK. + * missing.c,ksh_dir.h(ksh_opendir): new define/function. + * eval.c(globit),emacs.c(compl_file): use ksh_opendir() instead of + opendir(). + + * main.c(include): save source filename since search() uses line[] + for the filename and shell() trashes line[]. + + * table.h(FINUSE,FDELETE) new defines. + * exec.c(execute): case CFUNC: re-arranged code so normal return goes + through setjmp() switch; use FDELETE/FINUSE flags to avoid problems + with a function being undefined or redefined during its execution. + * exec.c(define): if FINUSE is set, set FDELETED and find a new table + entry. + +Fri Aug 26 21:58:25 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * lex.c(getsc_): flush output after write when echoing. + + * Makefile.in(dist): after creating distrubution, use pathchk -p + to check file names. + +Fri Aug 26 10:28:20 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * made pdksh-5.0.3 distribution + + * expr.c(IS_ASSIGNOP): new define. + * expr.c(evalexpr): use IS_ASSIGNOP (bug fix). + + * exec.c(execute): case TFOR,TSELECT: change e->type just + before setjmp() to avoid problems with bad jmpbufs. + + * jobs.c(startlast): new function. + * jobs.c(waitlast): print error if job not started. + * eval.c(comsub): call startlast() after execute(). + * jobs.c(exchild,j_startjob,j_sync_pipe): when starting a pipeline + use a pipe to ensure the first process doesn't die before + the last process is started. + + * exec.c(execute): case TFUNC: set/clear FXTRACE according to + tp->flag & TRACE, and restore old value when function completes. + + * c_test.c,exec.c,io.c,mail.c,vi.c: changed all uses of + (x&S_IFMT) == S_IF* to the equivilent S_IS* (for ISC unix). + * c_test.c(eval_unop): if system doesn't have symlinks or sockets + (S_ISLNK,S_ISSOCK), return 0 (used to cause internal error). + * ksh_stat.h(S_ISVTX): define if sys/stat.h doesn't. + + * sigaction.c(Signal,signal): ifdef'd Signal() and signal() out as + they cause header file conflicts on some systems (eg, signal() + in ISC unix); also ifdef'd out other routines not used by ksh + (ie, sigdelset, sigfillset, sigismember, sigpending). + +Thu Aug 25 11:50:03 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * c_test.c(primary): always check that *t_wp isn't 0 before using it. + + * eval.c(homedir): cache home directory values. + + * exec.c(findcom): search builtins before tracked aliases. + + * table.h(commands,taliases): changed name of commands to taliases. + + * c_ksh.c(c_unalias): changed to use ksh_getopt(); added -t and -a + options; exit with non-zero value if non-alias name unaliased + (POSIX). + + * main.c(initcoms[]): alias hash to 'alias -t -'; added autoload alias + as well; set selected comands to be tracked aliases (eg, grep, + ls, who, vi, emacs, etc.). + + * c_ksh.c(c_alias): when printing aliases, check ISSET, not DEFINED + flag (so unset tracked aliases won't cause problems); changed + to use ksh_getopt(); added -t flag; added -x flag (does nothing). + * c_ksh.c(c_hash): deleted function; removed all references. + + * table.h(CTALIAS): new define. + * exec.c(findcom): added search argument for whence -p; fixed + introduced bug preventing tracking of commands when insert set; + changed all calls; when creating tracked aliases, set type to CTALIAS + (was CEXEC). + * exec.c(findcom,flushcom): when freeing old tracked aliases, use + APERM, not commands.areap. + * c_ksh.c(c_whence): changed to use ksh_getopt(); assume findcom() + never returns 0 and never returns tp->type == CNONE; made output + closer to at&t ksh output; combined vflag/!vflag switch statements; + added case for CTALIAS. + * exec.c(findcom): set tracked alias type to CTALIAS. + + * c_ksh.c(c_print): added -s option; changed to use Xstring() routines + and write() instead of shf routines; returns non-zero if there + was a write error. + + * jobs.c(struct job): changed pid_t lpid field to Proc *last_proc; + changed all uses. + * jobs.c(check_job): use j->last_proc instead of lp. + * jobs.c(j_waitj): when checking for fake ^C, test j->last_proc + status to see if it was signaled and use WTERMSIG to get signal. + + * main.c: initialize integer TMOUT parameter to 0; call alarm_init() + if FTALKING. + * trap.c(alarm_init,alarm_catcher): new functions. + * trap.c(runtraps): if ksh_tmout_leave is set, exit. + * sh.h(TMOUT_EXECUTING,TMOUT_READING,TMOUT_LEAVING,ksh_tmout, + ksh_tmout_state): new enum values/variables. + * table.h(V_TMOUT): new define. + * var.c(special,setspec,unsetspec): added V_TMOUT entry. + * edit.c(x_getc): call intrcheck() if read interrupted. + +Thu Aug 25 09:36:54 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * c_test.c(c_test): for argc cases 4 and 5, return the complement + of the expressioin result; for [[ .. ]] expressions, don't parse + based on argc; deleted struct t_op.op_flags field and related + defines UNOP,BINOP,ACCEPT_BE,ISTEST,ISDBRACKET,ISBOTH - changed all + uses to test the value of isdbracket. + * c_test.c(filstat): moved body of filstat() into eval_unop(), deleted + filstat(). + + * c_test.c: incorperated changes from J.T. Conlin (jtc@cygnus.com) + for POSIXization of test builtin. + +Wed Aug 24 12:16:25 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * new-version.sh: new file - updates date in version.c + * Makefile.in: added new-version.sh to RCSFILES; call new-version.sh + in dist: target. + +Tue Aug 23 09:28:10 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * made pdksh-5.0.2 distribution + + * jobs.c(exchild): don't call restoresigs(). + * exec.c(execute): do call restoresigs() (just before execve()). + + * c_sh.c(c_trap): ifdef'd out code to print default traps. Too ugly + and it isn't clear POSIX needs it. + + * c_ksh.c(c_print): put -e option back in for echo emulation (-R). + + * c_sh.c(c_shift): generate error if n < 0. + + * c_sh.c(c_trap): use shellf instead of errorf to report errors + (so we can return a value instead of unwinding - POSIX). + + * exec.c(execute): if command fails and !FERREXIT, call + trapsig(SIGERR_). + + * c_sh.c(c_exit,c_return,c_exitreturn): combined c_exit and c_return + functions. + +Mon Aug 22 09:39:54 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * eval.c(expand): case XARG: set word to IFS_WORD to force null + words to be created. + + * lex.c(yylex): added indquotes flag and code for `...` parsing + (to deal with "`...`" the way sh/at&t-ksh does). + + * trap.c(runtrap): don't reset ERR trap. + + * sh.h: combnied SS_USER/SS_FORCE flags with RESTORE_CURR et. al. + flags; changed RESTORE_* to SS_RESTORE_*. + * trap.c(setsig): combined restore, force and user flags; changed + all calls. + * trap.c(setexecsig): use SS_RESTORE_* values as argument. + + * table.h: changed LCASE to LCASEV (and UCASE_AL to UCASEV_AL) to + avoid problems with ioctl/tty LCASE define. + + * sh.h(struct env): deleted interactive field. + * main.c(shell),exec.c(iopsetup): deleted e->interactive assignment. + + * sh.h: made e a struct env * (was struct env); changed all references. + * main.c(newenv): copy loc, flags and interactive fields explicitly. + + * var.c(newblock): allocate block structure, don't assume already + exists. + * var.c(popblock): free old block structure. + * main.c(main): set e->loc to 0 before calling newblock(). + * exec.c(comexec): let newblock() allocate new structure; deleted + l variable (changed references to e->loc). + * table.h: deleted globals variable. + + * c_ksh.c(c_print): treat a lone - like --. + + * main.c(main),trap.c(inittraps): move SIGINT,SIGQUIT,SIGTERM signal + initialization to inittraps() and do it regardless of FTALKING. + + * trap.c(settrap): deleted force trap since probably will never + add -f flag to trap. + + * main.c(unwind): if we are dieing of SIGINT or SIGTERM, kill + ourselves with a signal. + + * vi.c(x_vi),emacs(x_emacs): if ^C read, call trapsig()/runtraps(); + don't return -2. + * edit.c(x_read): don't check for -2 return value. + * vi.c(x_vi): check for quit char (^\) and fake SIGQUIT. + + * exec.c(comexec): made flags argument volatile. + + * misc.c(getn_): new function. + * c_sh.c(c_exit,c_return): call quitenv() before unwind()ing; + moved c_exit() next to c_return(); use getn_() instead of getn(). + + * main.c(shell): added exit_atend argument to deal with POSIX trap exit + semantics; changed all calls. + * sh.h: added STOP_RETURN macro. + * c_sh.c(c_return): determine if we are returning or exiting before + unwind()ing so POSIX trap exit semantics are honored. + + * jobs.c(j_sigchld): call trapsig() instead of messing with sigtraps[]. + * trap.c(trapsig): don't restore signal handler if it wasn't set to + trapsig. + + * sh.h: added TF_TTY_INTR flag. + * trap.c(inittrap): set TF_TTY_INTR for SIGINT. + * jobs.c(j_waitj): if + + * jobs.c,sh.h: deleted SA_RESTART ifdefs; moved SIGCLD->SIGCHLD ifdefs + from jobs.c to sh.h. + +Sat Aug 20 15:26:24 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * sh.h: added KSH_SA_FLAGS define. + * trap.c(inittrap,setsig): set sa_flags field to KSH_SA_FLAGS. + * sigact.c(sigaction): if using BSD42_SIGNALS, set the SV_INTERRUPT + flag. + * configure.in: deleted AC_RESTARTABLE_SYSCALLS. + * config.h.bot: add error message to prevent compilation if using + BSD41 signals. + + * shf.h: added SHF_INTERRUPT flag. + * shf.c(shf_fillbuf,shf_putchar,shf_write,shf_emptybuf): call + intrcheck() if read()/write() interrupted and SHF_INTERRUPT set. + * c_sh.c(c_read): use SHF_INTERRUPT flag. + * lex.c(getsc_): call intrcheck() if read() interrupted. + + * main.c(main),trap.c(inittrap): moved Sigact* initialization + from main() to inittrap(); made Sigact_trap/Sigact_ign static; + deleted Sigact and Sigact_dfl. + * sh.h: deleted declarations of Sigact*. + + * main.c(shell): deleted sigaction call - no longer needed. + + * sh.h: added RESTORE_CURR, RESTORE_ORIG, RESTORE_DFL and RESTORE_IGN + defines. + + * trap.c(intrcheck): new function. + * trap.c(runtraps): added intr argument; clear trap/intrsig + before running traps; changed all calls. + * trap.c(runtrap): save/restore exstat when running trap. + * jobs.c(j_waitj): changed interrupt test to check intrsig + and return -1. + * jobs.c(waitfor): if j_waitj() returns -1, call intrcheck(). + * jobs.c(j_change): use setsig() instead of sigaction() to + set up SIGTTIN,SIGTTOU,SIGTSTP. + + * trap.c(inittraps): initialize flags for INT/QUIT/TERM. + * sh.h(intrsig): new variable. + * trap.c(trapsig): set intrsig if signal has TF_DFL_INTR flag set; + deleted longjmp(). + * trap.c(runtraps): clear intrsig. + * trap.c(runtrap): if signal is defaulted and TF_DFL_INTR is + set, set exstat and call unwind(); return if signal ignored; + reset an ERR trap before executing it. + * trap.c(cleartraps): deleted special case for EXIT; reset + command traps using settrap(); clear intrsig. + * trap.c(restoresigs): only deal with traps that have the TF_EXEC_IGN + flag set (others take care of themselves). + + * trap.c(sigtraps[]): added ERR trap. + * trap.c(gettrap): deleted #if 0'd ERR/EXIT check. + * trap.c(gettrap,runtrap,cleartraps,restoresigs): use SIGNAL+1 to + go through trap table. + * sh.h(SIGEXIT_,SIGERR_): new defines. + * c_kill.c(c_kill): test for signals > 128 (was >= 128) + * c_sh.c(c_trap): when printing traps, use SIGNALS+1. + + * sh.h(struct trap): replaced ourtrap and sig_dfl fields with + flags field; defined TF_SHELL_USES, etc. for flags field; added + cursig field. + * sh.h(struct env): replaced func_parse field with + flags field; defined EF_FUNC_PARSE, EF_BRKCONT_PASS for flags + field; defined STOP_BRKCONT(); changed uses of func_parse + (get_command()/readhere()). + * c_sh.c(c_brkcont): use STOP_BRKCONT(), EF_BRKCONT_PASS; call + unwind() to do the work. + +Fri Aug 19 09:59:43 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * trap.c(settrap): new function - does most of what c_trap did. + * c_sh.c(c_trap): changed to use settrap(); print out default + actions as well as caught/ignored actions. + * trap.c,c_sh.c(setsig): moved setsig from c_sh.c to trap.c and + made it static; added force and user arguments; don't do anything + for EXIT and ERR. + * main.c(main): use sigtrap() instead of ignoresig(). + * trap.c(ignoresig): deleted function. + + * exec.c(execute): case TSELECT: don't change SIGINT signal handler. + + * proto.h: use GCC_FA_NORETURN on aerror(). + * syn.c(yyparse): made function static and void. + + * exec.c(herein): changed error() calls to errorf()s; use error() + in error handler; call shf_close() instead of shf_fdclose(). + + * tree.h,sh.h: moved LBREAK/LCONTIN from tree.h to sh.h; + added LEXIT, LRETURN, LERROR, and LINTR defines; changed values + of LBREAK/LCONTIN; changed all calls to longjmp() and setjmp(). + * exec.c(execute): put Break: label after main switch, changed + goto Break[0-9] to Break; deleted Break[0-9] labels. + * exec.c(execute): changed FOR, SELECT, WHILE, DO loop setjmps + to explicitly check for LBREAK/LCONTIN, otherwise call unwind(). + * exec.c(execute): case TFUNC: added setjmp switch statement to take + care of various L* values. + * main.c(include,shell): added setjmp switch statement to take care of + various L* values. + * main.c(unwind): added L* parameter to pass on to longjmp(); + changed all calls. + * c_sh.c(c_return): just call unwind(LRETURN); + * main.c(unwind): put code from leave() in E_NONE case. + * main.c(error,leave): deleted functions; replace all calls with + unwind(LLEAVE or LERROR). + * *.c(longjmp): replaced all calls with unwind(L..) (except the + call in unwind()). + + * shf.h: add areap field to struct shf. + * shf.c(shf_fdopen,shf_sopen): initialize areap + * shf.c(shf_emptybuf,shf_close,shf_sclose,shf_finish): use areap + instead of ATEMP. + + * shf.c(shf_sopen): if buf is 0 and writing and DYNMAIC, allocate + a buffer; if writing, save room for a trailing null. + * shf.c(shf_sclose): new function. + * shf.c(shf_snprintf),tree.c(snptreef): use shf_sclose(). + * tree.c(snptreef): changed return type to char *; if buffer + is null, pass SHF_DYNAMIC to shf_sopen(); return (possibly + allocated) string. + * syn.c(syntaxerr): use snptreef() instead of ident. + + * tree.h: new define TDBRACKET; new defines DB_NORM,DB_OR,DB_AND, + DB_BE,DB_PAT. + * tree.c(ptree): added case for TDBRACKET. + * syn.c(get_command): case DBRACKET: make TDBRACKET command; + at end of function, null terminate args/vars if TDBRACKET. + * c_test.c(is_db_patop): new function. + * syn.c(db_primary): set type of arg to DB_PAT if is_db_patop() + returns true. + * exec.c(execute): added case for TDBRACKET. + + * syn.c(get_command): case MDPAREN: make arg[0] an allocated stuffed + string (was a literal string). + +Thu Aug 18 11:06:49 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * c_test.c(is_op,is_binop,is_unop): new functions to test for + unary/binary operators. + * c_test.c(oexpr,aexpr,nexpr,primary): no longer take argument - + call is_unop/is_binop directly. + * c_test.c(ISDBRACKET,ISTEST,ISBOTH,ACCEPT_BE): new defines. + * c_test.c(struct t_op): changed op_type field to op_flags. + * c_test.c(ops[]): broke into two arrays: u_ops and b_ops; + set flag field to ISDBRACKET/ISTEST/ISBOTH as appropriate. + * c_test.c(t_lex): deleted function. + * c_test.c(primary): before checking for unary operator, check + for -BE (binary expression next) if appropriate. + + * c_test.c: made operator type an enum to make it easier to add + operators; changed oexpr/aexpr/nexpr/primary/filstat/t_lex + operand/return types. + * c_test.c(struct t_op): changed op_text from char * to char [4]. + * c_test.c(primary): case FILTT: using digit(**t_wp) causes core dump + when there is no arg - just test if *t_wp is 0; move test into + filstat(). + * c_test.c(primary): use *opnd1 != 0 instead of strlen(opnd1) (3 + instances). + + * tree.c(wdscan,wdcopy): changed string argument to const; changed + return type of wdscan to const. + + * eval.c(expand): case CSUBST: case '?': use st->var->anme instead + of cp to allow proper nesting. + * eval.c(expand): case OSUBST: don't change cp - declare local + variable. + * eval.c(expand): case COMSUB: deleted Xsavepos() call; XCOM: deleted + Xrestpos() call. + * eval.c(expand): save the position of the first unquoted = for tilde + expansion. + * eval.c(expand): case '~': use sp == (cp+2) instead of dp == Xstring + so we aren't fooled by ''~ or ${foo}~; sp[-1]/sp[-2] and firsteq + for the DOASNTILDE after first = or unquoted : test. + + * tree.h(struct op): changed noexpand field to evalflags; changed + all uses. + * exec.c(execute): if t->evalflags is non-zero, pass them to eval(). + + * lex.h: added DBRACKET define for [[ keyword. + * syn.c(get_command): added case for DBRACKET. + * syn.c(db_parse,db_oaexpr,db_nexpr,db_primary): new functions + to parse [[ .. ]] expressions. + * syn.c(tokentab[]): added [[/DBRACKET keyword. + * c_test.c(is_db_unop,is_db_binop): new functions to test if arg + is [[ .. ]] unary/binary operator. + + * syn.c(syntaxerr): call REJECT; before token; removed REJECT + before all calls to syntaxerr(). + * syn.c(get_command): removed syntax error kludge. + +Wed Aug 17 11:07:40 NDT 1994 Michael Rendell (michael@arlene.cs.mun.ca) + + * c_test.c(struct ops): changed -U to -O. + + * tree.h: added new defined DOASNTILDE. + * eval.c(expand): case '~': changed !(f&DOBLANK) to f&DOASNTILDE. + * exec.c(execute): case TCOM: pass DOASNTILDE when evaluating vars + and when expanding args when t->noexpand is set. + + * exec.c(execute): case TCASE: pass DOTILDE to both evalstr() calls + (POSIX). + + * syn.c(get_command): don't check for redirections before determining + command type; handle REDIR where LWORD are handled; default case + always returns; pass ARRAYVAR flag to tpeek() if t->noexpand; + don't allow redirections before "x()" function; don't allow + redirection before keywords; allow a '(' in the case LWORD/REDIR: + if no variables or arguments (POSIX doesn't allow this, but + at&t ksh/bourne sh do). + * lex.h(ARRAYVAR): new define. + * lex.c(yylex): parse x[1 & 2] as one word if VARASN|ARRAYVAR. + + * var.c(is_wdvarname): added aok argument; changed all calls. + * syn.c(get_command): case FOR/SELECT: check identifier is valid. + + * c_sh.c(c_trap): use print_value_quoted() to print traps; + use ksh_getopt() to skip possible --. + * trap.c(gettrap): allow digits only if signal numbers match + POSIX values (ie, HUP=1,INT=2,QUIT=3,ABRT=6,KILL=9,ALRM=14,TERM=15). + + * misc.c(print_value_quoted): new function to print strings with + appropriate quoting. + * c_ksh.c(c_typeset,c_alias): use print_value_quoted() when printing + values. + + * lex.h: added new ESACONLY flag - only accept ESAC keyword. + * lex.c(yylex): check for ESACONLY flag when doing keyword search. + * syn.c(caselist): pass ESACONLY flag to tpeek(). + +Tue Aug 16 10:17:47 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * lex.h(struct source); changed echo field to generic flags field; + defined SF_ECHO and SF_ALIASEND. + * lex.c(_getsc): case SALIAS: use isspace() to check for trailing + space in alias; if alias does not end in a space, return a fake + space to keep the current alias in the source list - this allows + recursive alias detection; set SF_ALIAS in next source if alias + does end if space. + * lex.c(yylex): deleted expanding_alias, rec_alias_cnt and + rec_alias_table variables; check for recursive aliases by checking + the source list; don't use EXPALIAS flag; if SF_ALIAS is set in + current source, set ALIAS flag and clear SF_ALAIS; deleted global + alias variable. + * table.h: deleted EXPALIAS define. + * main.c(shell): changed s->echo to set/clr of SF_ECHO in s->flags. + + * table.h: added aliases and keywords tables; deleted lexicals table + (POSIX says they are seperate). + * syn.c(keywords,initkeywords): changed name of keywords() function + to initkeywords(); changed all calls; add to keywords table instead + of lexicals table. + * main.c(main): initialized aliases/keywords tables; deleted + initialization of lexicals table. + * c_ksh.c(c_whence,c_alias,c_unalias),lex.c(yylex): use + keywords/aliases tables instead of lexicals table; removed unneeded + type == CALIAS checks. + + * var.c(getint): new function, largely take from strint(). + * var.c(strint): call getint() to do most of the work; if vq + was an allocated string, free the string and clear the alloc + flag. + * var.c(intval): call getint() instead of strint(). + + * mail.c(mcheck): use getint() instead of strint() when getting + value of MAILCHECK. + + * var.c(skip_varname): added argument to allow array references; + changed all calls. + * var.c(set_array): remove valid variable name check - done in + parse_args(). + + * var.c(strint): check if getspec()/setspec() need to be called. + * var.c(intval): remove getspec()/INTEGER checks - let strint() do it. + + * var.c(skip_wdvarname,is_wdvarname,is_wdvarassign): new + functions. + * lex.h(VARASN): new define indicating variable assignment expected, + currently used to parse "x[1 & 2]" as one token - may be used + in future in returning AWORD (assignment word) to the parser. + * lex.c(yylex): Subst: case '[': use is_wdvarname() in determining + whether to parse an array dereference; do not require an = after the + dereference (typeset -r x[1 & 2] is legal). + * syn.c(many functions): pass VARASN to token/musthave/tpeek/synio + when ever the next token might be the first word of a simple + command. + * syn.c(get_command): case LWORD: use is_wdvarassign() to distinguish + variable assignments from normal arguments; allow aliases after + redirections and variable assignments; generate syntax error + for "foo=bar bogusfunction()"; allow array variables in for + and select statements. + * lex.c: make global alias variable static (no longer used by + get_command()). + + * syn.c(get_command): put MDPAREN into its own case. + + * lex.c(yylex): deleted place holder for tilde expansion. + + * tree.h(struct tree): added noexpand field for + alias/export/readonly/typeset. + * syn.c(newtp),tree.c(tcopy): initialize/copy noexpand field. + * syn.c(get_command): case LWORD: set noexpand if assign_command() + returns true. + * syn.c(assign_command): new function - returns true if command + is alias, export, readonly or typeset. + * exec.c(execute): case TCOM: don't pass DOTILDE flag when + expanding t->vars; don't do field splitting/globbing/tilde expansion + of t->args if t->noexpand is set. + + * table.h: re-grouped the struct tbl flags into common, variable, + funtion, builtin/alias, etc (some values overlap); new flag + names: KEEPASN (was overloaded with TRACE) and NOEXPAND. + * exec.c(comexec,builtin): changed TRACE to KEEPASN. + + * tree.c(tcopy): when copying t->str, use strsave(), not wdcopy() + if not copying a TCASE. + + * c_ksh.c(c_typeset): added -p flag for POSIX export/readonly. + + * eval.c(expand): expand tilde in place; don't do tilde expansion + on results of substitution (POSIX); only do tilde expansion + if login name is unquoted (POSIX); don't check for fdo&DOTILDE + when a word is completed. + * eval.c(tilde): changed to return home directory of a given login name + (taking care of null/+/-) (old version copied string, scanning + for, and replacing, magic tildes). + + * exec.c(findcom): don't create commands table entries when + not inserting; when doing access test of tracked alias, + check for ISSET, not ALLOC (but test ALLOC before calling afree). + * exec.c(search): use strcpy() instead of loop. + +Mon Aug 15 14:46:58 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * c_ksh.c(c_print): added at&t ksh -R flag; deleted -e flag; + changed to use ksh_getopt(). + * main.c(initcoms[]): changed echo alias from 'print -' to 'print -R'. + + * sh.h: new variable procpid; added pid field to struct temp. + * jobs.c: changed references to my_pid to procpid; deleted my_pid + variable. + * io.c(maketemp): initialize pid field from procpid. + * main.c(remove_temps): only remove temporary files created by + the current process. + +Sun Aug 14 11:47:05 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * var.c: added var field to struct Expand and struct SubType; deleted + struct SubType.name field. + * var.c(expand): case OSUBST: use x.var/st->var field to save result + of global() for use in case CSUBST - this avoids problems with + x[i+=1] being evaluated twice; check to see if value is being + assigned to non-variable (eg, ${*:=aja}) or read-only variable. + * var.c(varsub): set value of xp->var; possibly generate error if + FNOUNSET set when expanding ${#*}, ${#var}, or ${#array[*]}. + + * table.h: added struct tbl.areap field to get rid of lastarea + problems; deleted lastarea variable; changted all refernces + to lastarea to var->areap. + * table.c(tenter): initialize areap field. + * var.c(arraysearch): deleted area parameter; initialize areap field. + * var.c(global,local,intval,setint): initialize areap field of vtemp. + +Fri Aug 12 10:54:51 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * expr.c: re-wrote e*() functions using single function and table; + token() now does table lookup; added full C expressions (sans + pre/post increment, sizeof). + + * table.h(INT_U,INT_L): new flags. + * var.c(strval): handle INT_U. + * c_ksh.c(c_typeset): add exclusions for INT_U/INT_L; + add -U option for unsigned (non-at&t ksh). + + * var.c(set_array): use global() instead of local(); + + * var.c(global): when parsing $123, don't limit number to 1000. + + * expr.c(v_evaluate): like old evaluate, but assigns result to + specified variable. + * expr.c(evaluate): changed to use v_evaluate(). + * expr.c(e0): when parsing literals, use strint() instead of setstr(). + + * var.c(setstr): when assigning to integers, use v_evaluate() instead + of strint(). + +Thu Aug 11 11:33:17 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * var.c(strint): don't change anything until number completely + parsed; don't clear ISSET flag if parsing fails; fail parse + if non-integer/non-letter found; default base if copying from + integer variable; set ISSET if parsing succeeds. + + * expr.c(tempvar): set vp->val.i to 0. + * expr.c(token): when skipping a literal number, don't allow _; + use isspace() to skip. + * expr.c(asn): use strint()/setstr() instead of setint(). + * expr.c(e0): added unary ~ and + (posix). + + * var.c(global,local): always call substitute on contents of [..]; + free sub when finished eval. + + * ksh_limval.h: new file. + * shf.c: use ksh_limval.h. + * Makefile.in: added ksh_limval.h to HDRS. + +Wed Aug 10 10:57:01 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * c_ksh.c(c_whence): case CFUNC: print exported/traced/undefined, + do not print function definition. + * exec.c(findcom): new argument indicated if functions to be + autoloaded; changed all calls. + + * misc.c(ksh_getopt): added # option modifier for typeset. + * c_ksh.c(c_typeset): changed to use ksh_getopt(); added + -L, -R, -Z, -l, -u flags; quote variable values when printing; + generally re-arranged function. + * table.h: added LJUST,RJUST,ZEROFIL,LCASE,RCASE; deleted FUNCT. + * exec.c(findfunc): new function. + * exec.c(define,findcom): use findfunc(); skeleton autoload code. + * var.c(typeset): added two arguments for initial field width and + base; changed all calls; deal with LJUST,.. flags; re-arrange to + have one loop iterating over array; do readonly check before + changing attributes. + * var.c(strval): deal with LJUST,.. flags when getting integers. + * var.c(setstr): deal with LJUST,.. flags when setting strings. + * var.c(setstr): deal with LJUST,.. flags when setting strings. + * var.c(arraysearch,tenter): initialize {new,p}->field to 0. + * table.h: added struct tbl.field. + + * siglist.in: changed signal messages to be more or less the + same as sys_siglist[]; moved SIGUNUSED before SIGBUS. + * jobs.c(j_print): assume sigtrap[].mess always valid; use + sigtrap[].mess directly for stopped processes. + + * aclocal.m4(KSH_SYS_SIGLIST): new macro like KSH_SYS_ERRLIST. + * configure.in: use KSH_SYS_SIGLIST instead of AC_SYS_SIGLIST_DECLARED. + +Tue Aug 9 10:28:45 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * version.c: removed RCS logs since version numbers don't match + release numbers. + + * configure.in: added AC_PROG_CPP + * Makefile.in: added rules for generating siglist.out + * siglist.in,siglist.sh: new files. + * trap.c(inittraps,deftraps[]): deleted deftraps[]; initialize + sigtraps[] directly by including siglist.out. + + * tree.h: added EXPRSUB for $((..)), re-numbered defines. + * tree.c(tputS,wdscan): added case for EXPRSUB. + * eval.c(expand,alt_count,alt_scan): added case for EXPRSUB. + * lex.h: added SDDPAREN for $((..)). + * lex.c(yylex): added case for SDDPAREN. + + * lex.c(yylex): case SPAREN: match parenthesis using counter instead + of pushing/poping states. + + * lex.h: re-numbered S* defines to be sequential; added SREREAD. + * lex.c(yyerror): pop SREREADs. + * lex.c(getsc_): added case for SREREAD. + * lex.c(arraysub): changed to save whatever is read and return a value + indicating if brackets matched; changed all calls. + * lex.c(yylex): if brackets in array reference are not balanced, + or if array reference not followed by an =, re-read the input. + +Mon Aug 8 21:20:08 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * sh.h: fix lseek prototype (from sjg@zen.void.oz.au) + + * configure.in,jobs.c: added HAVE_SYSCONF; don't use sysconf() unless + HAVE_SYSCONF defined (for NetBSD-Feb12 from sjg@zen.void.oz.au). + + * jobs.c(put_job): removed PJ_ON_END case and define (not used). + +Wed Jul 27 10:19:42 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.0.1 distribution + + * exec.c(comexec): when doing exec has no command, fall through + and do variable assignments. + + * c_ksh.c(c_let): complain if there are no arguments. + + * exec.c(comexec): if XEXEC is set, exit after executing + (builtin/function) command; deleted FERREXIT code; don't set + exstat. + * exec.c(execute): handle FERREXIT; set exstat to rv for all + cases. + + * lex.c,syn.c: changed calls to errorf to yyerror (except the + on in yyerror()). + * lex.h(struct source): added errline field. + * lex.c(yyerror): now a varargs function; if source->errline field is + non-zero, print it instead of source->line. + * syn.c(syntaxerr): set source->errline if read EOF in a multiline + command. + +Tue Jul 26 11:22:06 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * lex.h(SYNTAXERR,INP): deleted macros. + * lex.h(HISTORY): upped to 128 as per POSIX. + * lex.h,syn.c(multiline): moved from lex.h to syn.c. + * tree.c(vfptreef): added %R to format I/O redirections. + * tree.c(pioact): don't output unit more than once; print unit + only if it isn't the default for the action; print only + one opening quote for quoted here documents; print <> for + read-write (was ><). + * syn.c(zzerr, syntaxerr): replaced zzerr function with new syntaxerr + function; changed uses of SYNTAXERR to syntaxerr() calls; print + out the last unused token; if in a multiline command when EOF + encountered, print token that was unmatched. + + * tree.h(TBANG),lex.h(BANG): for POSIX ! keyword + * tree.c(ptree),exec.c(execute): added case for TBANG. + * syn.c(restab[]): added ! keyword. + * syn.c(get_command): added case for BANG (!). + + * tree.h(XERROK): new define to allow non-zero exits to be + ignored in certian circumstances (set -e). + * exec.c(execute): pass XERROK to recursive execute() calls; set + XERROK when evaluating conditional part of if/while/until/&&/||. + * exec.c(comexec): pass XERROK on to functions; don't exit if + XERROK is set. + + * jobs.c(async_pid): new variable needed since async_job may go + away, but $! should still be expanded. + * jobs.c(j_async): return async_pid. + * jobs.c(j_set_async): set async_pid. + +Mon Jul 25 14:15:25 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * misc.c(parse_args): changed to use ksh_getopt(). + + * c_sh(c_set): deleted call to resetopts() - neither POSIX nor + at&t ksh touch OPTIND when positional parameters are changed. + * exec.c(comexec): changed resetopts() call to getopts_reset(1) - this + is not a proper fix - should declare a local optind. + + * misc.c(builtin_getopt,ksh_getopt): renamed builtin_getopt to + ksh_getopt (shorter); changed to use state structure instead + of static variables; changed all calls; optionally allow + to + introduce an option; don't skip lone - (or +) in arguments - set + optind to point to it. + * misc.c(ksh_getopt_reset): changed to use state structure instead + of static variables. + * sh.h(Getopt): new structure for ksh_getopt() state. + * exec.c(call_builtin): call ksh_getopt_reset(). + * c_ksh.c(c_getopts,getopts_reset): new getopts implementation that is + POSIX complient and uses ksh_getopt() routine. + * var.c(setspec): call getopts_reset() when OPTIND set. + * getopts.c: deleted file. + * Makefile.in: deleted getopts.c and getopts.o. + * options.h(FASCIST): deleted option. + +Thu Jul 21 09:52:03 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-4.9+mun.5 distribution + + * syn.c(get_command),shf.c(shf_fdopen,shf_sopen,shf_emptybuf): + added cast to alloc()/aresize() calls. + * sh.h: changed enum flags_enum to enum flags. + * misc.c(change_flag): changed type of first argument to enum flag. + * edit.c(set_editmode): change type of static array to enum flag. + + * edit.c(x_init): initialize tty chars to -1, except for werase, + which is set to ^W. + * edit(x_mode): split oldedchars structure declaration and + initalization (some old compilers don't like them combined). + + * c_test.c: made -h work like -L. + +Wed Jul 20 11:12:52 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * config.h.top: moved compile options into options.h; include + options.h. + * options.h: new file. + + * c_ksh.c(c_fgbg): if !FPOSIX, set return job exit status. + + * jobs.c(j_jobs,j_notify): use JF_REMOVE to flag jobs to delete + after all notification done (to prevent multiple + or - jobs). + + * jobs.c(put_job): new funtion, takes argument to specify where + to put job; changed all calls to put_job_on_front and + put_job_on_end to use this; put background processes just + after stopped jobs (instead of at end) (POSIX). + * jobs.c(put_job_on_front,put_job_on_end): deleted. + +Tue Jul 19 10:33:55 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * aclocal.m4(AC_MMAP): copied from autoconf's acspecific.m4 and + modified to use the MAP_FILE flag if available. + + * misc.c(change_flag): ifdef use of FVI/FEMACS/FGMACS. + +Mon Jul 18 13:19:29 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * sh.h(async): deleted. + * var.c(global): call j_async() instead of using async global. + * jobs.c(j_async,j_set_async()): new functions. + * jobs.c(j_startjob): new function. + * jobs.c(exchild,j_waitlast): call j_startjob() to start job. + * jobs.c(j_sigchld): if any jobs aren't started, note signal + occured and return without calling wait. + * jobs.c(j_resume): allow un-reported dead jobs to be fg'd/bg'd; + if backgrounding, set async job. + * jobs.c(j_jobs,j_notify,j_waitj,check_job,remove_job): re-wrote to + deal with posix `known processes'. + * jobs.c(j_lookup()): if number specified, see if it is a lpid first, + then check for pgrp. + * jobs.c(new_job): added functionality of j_newjob(). + * jobs.c(j_newjob): deleted function and all calls. + + * tty.c(tty_init,tty_close): new functions which initialize + tty_fd, tty_state and tty_devtty. + * jobs.c(j_init,j_change): use tty_init()/tty_close(); changed + references of ttyfd to tty_fd; moved tty_fd, tty_state and + tty_devtty to tty.h; call tty_init() if !FMONITOR; save/restore + tty modes on foreground job completion if FTALKING (was FMONITOR). + * edit.h(X_chars): new structure for tty driver characters (replaces + ed_erase, ed_kill, ed_werase, ed_intr, ed_quit); moved prototypes + for emacs.c and vi.c from proto.h to edit.h; changed all references + to ed_* to edchars.*. + * edit.c(x_mode): use tty_state instead of cborig; re-initialize + tty state from tty_state whenever entering xmode; save tty characters + in edchars structure. + * edit.c(x_init): now called from main(); initializes edchars, x_cols, + and calls x_init_emacs. + * emacs.c(x_init_emacs): changed erase,kill,werase,intr,quit arguments + to X_chars argument. + +Fri Jul 15 10:35:13 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * io.c(restfd): only flush if fd is 2; use dup2() instead of + fcntl(F_DUPFD) (2 system calls instead of 3). + + * ksh_wait.h(WEXITSTATUS): changed mask from 0x7f to 0xff. + + * tty.h: added TTY_state structure. + * tty.c: new file - contains get_tty() and set_tty(). + * edit.c(x_init,x_mode): use get_tty() and set_tty(). + * jobs.c(j_waitj,j_resume): save/restore tty modes of stopped + jobs, as per POSIX.1, B.2, job control; for foreground jobs, + save tty state after successful completion, restore tty state + after non-successful completion (signaled, non-0 exit, stopped). + + * misc.c(options): changed "alternations" to "braceexpand" (this + is what bash uses - no need to invent new option names); changed + ALTERNATIONS define to BRACEEXPAND, same for FALTERNATIONS to + FBRACEEXPAND. + + * jobs.c(check_job): if process died of SIGINT or SIGPIPE, leave + job state as PEXITED (not PSIGNALLED). + * jobs.c(j_print): if printing short notice, ignore SIGPIPE the + way SIGINT is ignored. + + * exec.c(comexec): flush shl_out after 'not found' message. + + * c_ksh(c_kill): re-wrote function (again) to handle posix + options (-s, --, etc.) and posix -l output. + + * configure.in: added strcasecmp function check. + * missing.c(strcasecmp): define strcasecmp function if not available. + * trap.c(gettrap): use strcasecmp when comparing signal names. + + * var.c(global): expand $! to nothing if there haven't been any + asynchronous processes started yet. + + * misc.c(options): added posix option (set automatically if + POSIXLY_CORRECT env variable is set or if POSIXLY_CORRECT config + define is defined) + * var.c(special,setspec): added POSIXLY_CORRECT. + * config.h.top: added POSIXLY_CORRECT define. + * main.c(main): set FPOSIX if POSIXLY_CORRECT is defined. + * POSIX: new file describing what the posix flag controls. + +Thu Jul 14 10:53:15 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * jobs.c,sh.h(killpg): moved killpg define to sh.h. + + * jobs.c(exchild): set async variable when starting background + processes. + * jobs.c(j_resume): print [job-num] before command when backgrounding; + print to stdout instead of stderr. + * c_ksh.c(c_fgbg): call builtin_getopt to skip possible --, complain + about unknown options. + + * jobs.c(held_sigchld): new global variable. + * jobs.c(j_sigchld): if any jobs aren't started, set held_sigchld and + return. + * jobs.c(exchild,waitlast): after setting JF_START, if held_sigchld + set, call j_sigchld(). + + * jobs.c(exchild): added/initialized ppid field to struct job; deleted + global is_child. + * jobs.c(waitfor): don't wait for a process that isn't a child of + the current process. + * jobs.c(j_exit): kill stopped jobs owned by current process only. + + * jobs.c(check_job): don't do monitor stuff for XXCOM jobs, but do + set up notification. + * jobs.c(j_waitj): added JW_NOTIFY flag to print job notification + messages. + + * jobs.c(exchild): remove !XPIPEI condition - we now close pipe so + pipeline doesn't have to call waitlast(). + * exec.c(execute): case TPIPE: no need to restore 0 or call waitlast() + since exchild() handles everything. + + * jobs.c(remove_job): set last_job to 0 if we are removing it. + * jobs.c(waitlast): check if last_job is 0. + + * jobs.c(struct job): combined started, waiting, interactive + field into flags field; added JF_* flags; changed JW_NONOTIFY + to JW_NOTIFY and changed all calls to j_waitj() to reverse + this flag. + + * jobs.c(exchild): ignore SIGTSTP, TTIN, TTOU for `command` jobs. + +Wed Jul 13 09:28:34 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * main.c(main): when checking if FMONITOR set on command line, + use 127 instead of -1 (signed vs unsigned char problem). + + * tree.h: renumbered, XEXEC, XFORK, ... and DOBLANK, DOGLOB, ... + to use bits in order (instead of 0 5 2 4...). + * jobs.c(j_init): don't set sigtrap[SIGCHLD].sig_dfl = 1 as a + forked child may be a shell that needs to trap SIGCHLD. + + * tree.h(XPCLOSE,XCCLOSE): flags for close in parent, close in child. + * jobs.c(exchild): added third argument - a file descriptor - if + flags has XPCLOSE, close fd in parent, if flags has XCCLOSE, close + in child. + * exec.c(execute): pass input side of pipe to exchild() so it can + be closed in the child. + +Tue Jul 12 10:21:57 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * main.c(main): let ignoresig() handle SIGTERM. + * jobs.c(exchild): let restoresigs handle SIGTERM. + + * lex.c(yylex): don't parse array references inside double quotes + (partial fix). + + * emacs.c(x_print): use shprintf instead of shellf so the output + of bind can be redirected. + + * trap.c(deftraps): added SIGINFO (from jconklin@netcom.com). + +Fri Jul 8 09:37:51 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-4.9+mun.4 distribution + + * Makefine.in: added $(LDSTATIC) to LDFALGS to make static + linking easier (suggested by sjg); use cp -p when creating + distributions to preserve file dates. + * configure.in: set LDSTATIC in Makefile if present in environ + when configure is run. + + * sigact.c(sigsuspend): when calling 4.2bsd sigpause, pass *mask, + not mask. + + * main.c(main): fixed up initialization of PWD (free memory, print + more informative message). + + * misc.c(getcwd): range check backwards. + + * c_sh.c(setsig): set sa_flags/sa_mask. + + * edit.c(init_editmode),main.c,proto.h: deleted function - not needed + since VISUAL/EDITOR are special. + + * lex.c(set_prompt),table.h: take out PS3. + + * c_sh.c(c_umask): handle multiple actions in symbolic mode clauses + (eg, u+r-w); handle X (eg, o+X); ignore s (eg, u+s). + + * shf.c(shf_fillbuf): continue reading if we get an EINTR. + * shf.c(shf_emptybuf,shf_write,shf_putchar): continue writing if we + get an EINTR. + +Thu Jul 7 10:19:24 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * sigaction,sigprocmask: changed NULL third argument to + (struct sigaction *) 0. + * sh.h(kshpid): changed type from int to pid_t. + * proto.h(do_ulimit): removed do_ulimit() declaration. + * vi.c(complete_word): removed unused variable pos. + * syn.c(pipeline,elsepart): removed unused variable c. + * jobs.c(exchild): deleted variable s (assigned but not used). + * history(hist_init),shf.c(shf_gets): changed variable e to end + because there is a global e. + * exec.c(do_selectargs): removed secondarg argument; changed return + (char *) 1 to (char *) 0; changed all calls. + + * io.c(canseek): use fd argument instead of 0. + + * lex.c(readhere): don't use fixed sized buffer (line). + + * expand.h: changed multi-statement macros to use do {..} while (0); + changed temporary variable vp to vp__ to avoid lint complaints. + + * aclocal.m4(KSH_DUP2_CHECK): define F_GETFD/F_SETFD if not + already defined. + + * etc/profile, etc/ksh.kshrc: replaced with new versions from + Simon J. Gerraty (sjg@zen.void.oz.au). + +Wed Jul 6 10:09:55 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * jobs.c(killpg),configure.in: restored use of killpg() - some + systems don't understand kill(-pgrp, signal) (ultrix 2.2); + added test for killpg() in configure.in. + + * trap.c(inittraps): changed initialization of sigtraps - was + using -1 in deftraps[] as end of table marker but some systems + (eg, ultrix 2.2) define signals with -1 values (SIGPWR). + + * Makefile.in(mandir,install): fixed mandir value; added / + in man installation; prefixed ksh.1 with $(srcdir). + + * jobs.c(j_init): Ignore failure of TIOCSETD. + + * misc.c(options[]): changed "vicomplete" to "vitabcomplete". + + * emacs.c(x_e_putc,x_e_puts,x_debug_info): x_e_putc()/x_e_puts() are + the x_putc()/x_puts() functions from ksh4.9 edit.c (they got lost + in the merge); same for x_debug_info(); changed x_e_putc/x_e_puts + to call x_putc/x_puts. + +Mon Jul 4 09:29:05 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-4.9+mun.3 distribution + + * main.c(remove_temps): use unlink() instead of remove(); delete + remove() define. + + * sh.h(func_heredocs, struct env): added func_heredocs for here + documents in functions; added func_parse field to struct env + - set when a function is being parsed. + * lex.c(readhere): if e.func_parse, save temp file in func_heredocs. + * syn.c(get_command): increment/decrement func_parse when parsing + functions. + * main.c(remove_temps,reclaim,leave): added remove_temps(); make + reclaim() call remove_temps(); make leave() clean up function + here documents. + + * aclocal.m4(KSH_TIMES_CHECK): new test - define TIMES_BROKEN + if times() doesn't exist or if it always returns 0. + * acconfig.h(TIMES_BROKEN): new define. + * missing.c(ksh_times): new function. + * ksh_times.h: new file. + * c_sh.c,jobs.c: changed <sys/times.h> to "ksh_times.h" + * c_sh.c,ksh_time.h(CLK_TCK): moved CLK_TCK define from c_sh.c + to ksh_time.h (needed in missing.c). + + * syn.c(get_command): case TIME: don't call pipeline() with CONTIN + flag. + + * c_sh.c(c_times): combined some printfs(). + + * jobs.c(j_jobs,j_kill,j_resume,waitfor): block SIGCHLD before + calling j_lookup() or looking at jobs list - avoids potential + problems with remove_job() being called in signal handler. + +Sun Jul 3 11:09:01 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c(do_selectargs, selread, pr_menu): re-wrote do_selectargs to + let c_read() to most of the work; deleted selread; make pr_menu + calculate width/ncolumns each call so select can be used recursively. + + * configure.in, aclocal.m4(KSH_TERMIOS_H, KSH_TERM_CHECK): replaced + KSH_TERMIOS_H with KSH_TERM_CHECK; removed calls to + tcgetpgrp/tcsetpgrp - there is a seperate test for this. + + * main.c(main), sh.h: move getcwd() declaration to sh.h. + + * eval.c(expand,varsub): added XNULLSUB case to deal with "$@" + (and "${foo[@]}") when $# (or ${#foo}) is 0. + + * eval.c(expand,varsub): removed free_me field - let reclaim() take + care of it. + +Wed Mar 9 00:50:12 1994 Simon J. Gerraty (sjg@zen.void.oz.au) + + * var.c (setstr): don't set ALLOC flag if vp.s is NULL. + +Thu Jun 30 10:16:44 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * c_sh.c(c_read): ignore null characters; use builtin_getopt() + instead of explicit parsing; removed -e option (neither at&t ksh + nor POSIX have this); added -s option (put line in history, at&t ksh); + if no variable specified, use REPLY (at&t ksh). + + * exec.c(do_selectargs): use Xstring macros to deal with saving + input (was static, growing, buffers); flush shl_out after + shellf(). + + * vi.c(expand_word,complete_word): deleted call to free_edstate + since already done by restore_edstate(). + * var.c(global): in 'if !letter(c)' block, deleted !c from + 'if (!c || !n[1])' - don't know why it was added since it makes + no difference to what is returned. + + * syn.c(dogroup): removed onlydone argument since it is only + used in the while/until statements, where "while command; done" + is not allowed anyway; Changed all calls. + + * misc.c(options[]): allow -i to be specified on the command + line. + + * exec.c(iosetup): if stderr (fd 2) is being re-directed, + re-open shl_out to clear any errors. + * main.c(quitenv): if restoring fd 2, clear any write errors + * io.c(initio): initialize shl_out, shl_spare for writing + (was SHF_GETFL). + + * main.c(main): ignore SIGQUIT if talking. + +Wed Jun 29 11:11:34 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * c_sh.c(c_exit): removed jobs/really quit stuff - exit should + always exit. + * main.c(shell): made reallyquit a local variable. + * sh.h(reallyquit): deleted reallyquit + + * exec.c(comexec), main.c(main), misc.c(change_flag), jobs.c: + ifdef'd use of FMONITOR. + * sh.h, misc.c: define FMONITOR option only if JOBS defined. + + * c_sh.c(c_wait): POSIXized: option parsing (of no options); + return 0 if not given any arguments; deal with multiple arguments. + * jobs.c(j_lookup): changed second argument to return an integer + error code, added defines for error codes, added error message + array; changed all calls to use new conventions. + * jobs.c(waitfor): returns -1 if job not found; added argument + to specify if notification messages should be suppressed. + * jobs.c(j_waitj): added flags argument instead of intr argument; + added JW_STOPPEDWAIT flag to wait for stopped jobs to complete; + added JW_NONOTIFY flag to suppress notification of normal + job termination. + +Tue Jun 28 16:13:10 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * c_ksh.c(c_jobs): added -n and -p options, allow job ids to be + specified for -l option. + * jobs.c(j_jobs): added new arguments to deal with -n and -p options. + + * shf.h(SHF_BSIZE): reduced size to 512 to reduce memory requirements + (I/O is used mostly for one line messages). + + * config.h.bot: removed necessity for tty process groups to define + JOBS, added necessity of signal blocking/pausing. + +Mon Jun 27 21:49:52 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * jobs.c(exchild,j_print,j_waitj,j_jobs,check_job): exchild() - removed + commented out shf_flush() calls; j_print() - print a space between + status reports when lflag < 0, changed flag argument to use + long/medium/short defines; Changed Job.notify field to use + long/medium/short defines; j_waitj() - instead of calling j_print(), + set j->notify to print the job; j_jobs() - clear j->notify so user + won't be notified twice if a job finishes before a jobs command; + check_job() - put job on front of list only if it is stopped. + + * main.c(main): pass flag to j_init() indicating if -m flag set/cleared + on command line; don't initialize ttyfd. + * sh.h: delete ttyfd. + * jobs.c(j_init,ttyfd,check_job): made ttyfd static; re-worked j_init() + to initialize ttyfd, initialize FMONITOR if not set by command line, + initialize shl_j for asynchronous job notification; + check_job() - use shl_j, look through saved fds to find real + standard-error. + + * jobs.c(TTY_PGRP, ttypgrps_ok): modified conditions so job control + is useful without tty process groups; added ttypgrps_ok flag that + indicates if tty process groups should be set up; modifications so + job control useful if ttypgrps_ok not set. + + * main.c(main): set FTALKING if 0 and 2 are tty as specified in POSIX + (was 0 and 1). + + * misc.c(parse_args): do POSIX option processing for -A, -c and -o + (allow -onoglob, -ctrue). + * main.c(main): don't set up shl_stdout before/after parse_args() since + lone -o on command line no longer accepted; remove code to allow + -c with no options to read from stdin (at&t khs does this but POSIX + requires an option to -c). + +Thu Jun 23 17:46:54 NDT 1994 John Rochester (jr@panda.cs.mun.ca) + + * trap.c(cleartraps): added special case for clearing trap 0 from + ksh-4.9 sources. + +Thu Jun 23 10:17:03 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * misc.c(builtin_getopt): print error messages for unknown option, + missing option; reset state if argv is 0. + * c_ulimit.c(c_ulimit): let builtin_getopt() print error messages. + * exec.c(call_builtin): call builtin_getopt() with 0 argv. + + * c_sh.c(c_unset): added -v option (POSIX); use builtin_getopt() to + parse arguments; removed bogus comment about global() and special + variables; don't allow read-only variables to be unset (POSIX). + + * var.c(unset, unsetspec): when unsetting a special variable, call + unsetspec(); unsetspec() new function. + * mail.c(mbset, mcheck): check that path is not 0 before calling stat + (so mbset() can be called with 0 when MAIL is unset); deleted #if 0'd + declarations of munset, mballoc and maddmsg. + + * misc.c(parse_args): pass argv+i+1 to set_array (not argv+i); when + skipping arguments, leave i just before the NULL. + + * exec.c(echo): flush shl_out when done. + + * shf.c(shf_close): always used to return EOF. + + * trap.c(trapsig): skip error handlers when checking for PARSE or LOOP. + +Wed Jun 22 10:24:09 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * sh.h(C_IFSWS), misc.c(initctypes): added and initialized C_IFSWS + (IFS white space). + * c_sh.c(c_read): print continuation prompt if line ends in backslash; + multiple non-white-space IFS chars delimit fields; strip trailing + IFS-white-space from last variable; watch out for backslash followed + by EOF. + * eval.c(expand): only do field splitting on the results of + parameter/command substition (POSIX, !v7-sh); multiple + non-white-space IFS chars delimit fields. + + * eval.c(expand,alt_expand): removed NOALT tests since it could + never be set; added return after call to alt_expand() in expand(). + +Mon Jun 6 10:12:41 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * misc.c(setctypes, initctypes): setctypes - don't allow leading null + in IFS (IFS="" could cause problems, 0 is already added); + initctypes - don't use a leading null. + + * sh.h, eval.c: move definition of ifs0 from eval.c to sh.h; handle + null ifs0. + * var.c(setspec): set ifs0 to first character of IFS. + + * lex.c(yylex): when parsing ${..}, array references were not being + null terminated. + +Fri Jun 3 12:28:06 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * var.c(export): changed to use memcpy() instead of loops. + + * eval.c(varsub, expand): check for #foo[@] in addition to #foo[*]; + handle ${foo[*]} and ${foo[@]} - added free_me field to struct Expand + so pointer vector can get freed. + + * c_sh.c(c_dot), main.c(include): set up positional parameters if + any are specified; Added argc,argv args to include(); changed all + calls to include(); change include() to return 0 or 1 - caller + can check exstat if desired; c_dot() should return 1 if can't open + file (was -1). + + * c_sh.c(c_brkcont): warning message could call getn() with NULL - save + original number and print it. + + * main.c(main): set $0 to first argument when -c used, ie, + "sh -c cmd-string this-is-$0 argumernts..." + + * exec.c(iosetup): print "cannot open" if IOHERE fails. + + * io.c(errorf): set exstat to 1. + + * exec.c(search): assume mode is R_OK or X_OK (not 0/1 - 0 is F_OK, we + want R_OK); changed all calls to pass R_OK/X_OK. + sh.h: define R_OK,W_OK,X_OK,F_OK if not defined. + eaccess(): changed all calls to use [RWXF]_OK. + + * sh.h(flag[], shell_flags[], Flag()): Renamed flag[] array to + shell_flags[] to avoid conflicts with other uses of flag; Changed + all references to flag[] to Flag(); defined Flag() to cast its arg + to an int (for old pcc based C compilers). + + * vi.c(iswordch): use letnum() instead of isalnum || _. + + * misc.c(parse_args): call set_array() to deal with -A flag. + * var.c(set_array): new function. + + +Fri Jun 3 10:22:26 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * eval.c(substitute): afree(s). + + * var.c(global,local), sh.h: ARRAYMAX: new define - max index of an + array; changed 511 constants to this; changed global() and local() + to use array_ref_len() instead of arraysub(). + + * expr.c(token): deleted unneeded arraysub() decl. + + * lex.c(arraysub), proto.h: made static, removed unused arguments, + changed callers; removed prototype from proto.h. + + * ChangeLog: changed descriptions from func(file) to file(func). + +Wed Jun 1 09:17:50 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-4.9+mun.2 distribution + + * Makefile.in: added RCSFILES macro and rcs-ci target. + + * configure.in: add termio.h to the AC_HAVE_HEADERS() call. + + * sh.h, main.c, edit.c, misc.c: use EXTERN for aperm, x_cols, + builtin_optind and builtin_optarg in sh.h, delete definitions + in main.c, edit.c and misc.c. + + * io.c(maketemp): changed sizeof(PATH) to sizeof(path). + + * aclocal.m4(KSH_VOID,KSH_DUP2_CHECK): test that a void * variable + can be used (Ultrix 2.2 compiler doesn't do this); added ifdef + HAVE_FCNTL_H to dup2 test. + + * aclocal.m4, configure.in, sh.h, tree.c, io.c, shf.c: added + new config test KSH_PROTOTYPES to check for function prototypes + (MIPS RICS/os 5.0 C compiler isn't STDC and it can't mix <stddef.h> + with <varargs.h>). Removed stdarg.h test (now redundent). 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: ChangeLog.0,v 1.1 1996/08/14 06:19:10 downsj Exp $'s. + + * acconfig.h: updated SIGSET_T comment: unisgned int -> unsigned. + + * aclocal.m4(KSH_CLOCK_T,KSH_TIME_T,KSH_SIGSET_T): make sure + type is a word (same fix as was done for more_t, et.al.). + + * aclocal.m4(KSH_VOLATILE): check that the compiler can deal with + volatile pointers (dec/pmax ultrix 4.2 compiler can't). + + * misc.c(parse_args): added skelatal code for dealing with -A. + + * var.c,proto.h(skip_varname): new function; deleted isassign() + function, which is no longer called. Changed typeset(var.c) + to use skip_varname(). + + * var.c(strint): fail if base is not in the range 2..36; set variable + base according to first base seen; generate an error if a non-alnum + char is seen (1^A was the same as 11). + + * var.c(strval): for integer variables, output base if != 10. + + * sh.h: fixed typo in x_cols define (#defined -> #define). + +Fri May 27 16:49:29 NDT 1994 Micharl Rendell (michael@panda.cs.mun.ca) + + * made pdksh-4.9+mun.1 distribution + + * finished autoconf'ing source code. + +Fri May 20 16:47:06 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * c_ulimit.c(c_ulimit) (was do_ulimit.c(do_ulimit)): major rework; + deal with combinations of getrusage and ulimit, use options at&t + ksh uses (-SHa..). + * misc.c(builtin_getopt): yet another getopt routine for builtin + commands + * misc.c(parse_args), c_sh.c(c_set), main.c(main): custom option + parsing routine for command line/set options. Lots of changes + to main() to incorporate this (easier to follow). + * c_test.c(c_test): added -e (file exists) test. + +Fri May 5 12:16:46 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) + + * numerous changes from old Notes.4-9 file: + - c_ksh.c: + - calls to errorf identify what function the error is in + (eg, errorf("print: bad -u option");) + - added array stuff (lots of places) + - table.c + - lex.c + - var.c: +arraysearch(), +basename() + - c_sh.c(c_exit), main.c(shell), sh.h: + - added reallyquit flag to deal with stopped jobs at exit time; + only two exit commands (or eofs) in a row will work. in + shell(), set exstat from execute value. + - c_ksh.c(c_print): + - handle \\ at end of string better (don't skip the null) + - c_ksh.c(c_whence): + - stop looking as soon as we get a failure + - pass flag[FTRACKALL] to findcom() instead of 0 + - c_ksh.c(c_alias): + - print '%s alias not found' if not found + - c_ksh.c(c_jobs): + - handle -l option (list pid) + - c_ksh.c(c_kill): + - two column -l output to try to keep it on one screen + - c_sh.c(c_read): + - skip IFS at start of line "echo ' a b' |(read a b; echo $a)" + should print a, not nothing. + - changed EOF check to set all vars to null (same way \n is + handled) + - eval.c(comsub): + - after compile(), return if t is NULL + - use setfileno() instead of fileno(x) = .. (see sh.h changes); + - exec.c(iosetup): + - set exstat if redirect fails + - exec.c(fd_clexec): + - check that fd >= 0 + - exec.c: + - change SHARPBANG ifdef code - everyone uses scriptexec(). + ifdef is put around open/read/parse code in scriptexec(). + [code cleanup] + - exec.c(search): + - eaccess test out side of loop: must be regular file to exec it + (like test in loop) + - eval.c(expand): + - added while getc() == 0 in XCOM + [a null in the output of a file would be treated as eof and + waitlast would not be called] + - eval.c(trimsub): + - '%' subst: use ATEMP, not APERM + - eval.c(tilde): use Xinit et. al., instead of fixed length buffer + - getopts.c(getopt): rename getopt() to ksh_getopt() to avoid + problems with prototypes in system #include files. + - io.c(fopenshf): + - call clearerr(fd) if already open + - use F_GETFL to determine appropriate mode for fdopen() call + instead of opening everything for read/write + - use ifdef _FSTDIO instead of ifdef _BSDI (_FSTDIO is used in + 4.4bsd, NetBSD, FreeBSD and other non BSDI environs) + - lex.c(ungetsc): don't decrement if str == null, remove nullstr hack + - lex.c(yylex): + - \ at eol: call Xfree before goto Again + - use memset() to clear ident, instead of while loop + - syn.c(get_command): renamed from command() to get_command() to + avoid conflicts with command(main.c). + - sym.c(get_command): + - allow A=B <alias-name> to work (alias was not being expanded) + - allow `if .. if .. fi fi' to work (no ; after group terminator + (fi , esac, done, ), }) + - allow `alias FI=fi ; if .. if .. fi FI' to work (alias + expansion after group terminator) + - do not assume resize returns same pointer + - tree.c(pioact): + - handle IORDWR case + - added leading quote for file in IOHERE case + - var.c(special): + - add MAILCHECK check + - var.c(setspec): + - add MAILCHECK case (doesn't do anything yet) + - var.c(strint): + - handle null vp->val.s + - eval.c(sh.h, expand), misc.c(options[]): + - enable alternations only if alternations flag set + (set -o alternations). This is so (att ksh) scripts that don't + expect alternations won't break. + - added notify option (asynchronous job completion notification) + - added vicomplete to enable tab char as file name completion + char in vi (this is likely to go away - exits for historical + reasons) + - jobs.c, main.c, sh.c, trap.c: + - define/use SIG_HDLR instead of void + - eval.c(expand): alt_expand() does not return a value so don't test + it, just return. Also changed decl of alt_expand() to reflect + reality. + - emacs.c(x_emacs): first return returns random value (i); change to + return 0. Also changed the way interrupts are returned to called + (return -2 means interrupt). + - c_sh.c(c_brkcont): at&t ksh allows breaks/continues outside of + loops, 4.9 prints an error and breaks out of all env's (if, case, + etc.). Fixed to act like at&t (ie, allow bogus continues), except + a warning message is printed. (Some HP-UX shell scripts actually + have continues outside of loops...) + - main.c(shell): parameter s should be volatile as it is used after a + setjmp. + - edit.c(promptlen): handle tabs, backspaces... + - cleaner fix to the ^C/source->line problem: in pprompt(lex.c), + convert ! to source->line+1 (same in promptlen(edit.c)), increment + source->line after a (non-empty, non-eof) line has been read + (before call to histsave()). To be pedantic, also adjust + position of source->line++ in SHIST in case PS9 is ever used. + Remove code in shell(main.c) that does the source->line--, + remove the source->line-- for eof and empty line in from + getsc_(lex.c). + - trap.c(sigtrap[]): do not depend on signal number matching position + in initialization array. Use second table in which order does not + matter to initialize sigtrap[] array. Easier to read/port, and + generally less fragile. requires call to inittraps() in main.c. + - trap.c(cleartraps): need to clear Sigact flags/mask after use + (actually, declare a local struct sigact and use that instead of + Sigact) + - exec.c(execute): case TSELECT: no USE_SIGACT code for call to + signal(); case TPIPE: don't call waitlast() if XXCOM since + waitlast() will be called in expand(); 4.9 code that set and + then cleared the XEXEC flag in the TPIPE case not added since + it was ifdef'd out, also the code to not exit if XPIPEI flag + set was not added; 4.9 XXWHL flag not added (don't flush stdin + if in a while loop) since this problem fixed (I hope) by shf stuff. + - ttyfd{sh.h/lex.c}: use EXTERN/_I_ to initialize ttyfd. Remove from + lex.c + - interrupted reads: instead of testing sigchld_caught after reads + fail, continue them if errno == EINTR. + - edit.c(x_getc): check for EINTR, and continue reading if so. + - lex.c(getsc_): check for EINTR, and continue reading if so. + (don't check return of x_read() - check has already been done) + - exec.c(selread): check for EINTR, and continue reading if so. + - history changes: + - history.c: + - histrpl(): bounds check doesn't take global flag into + account - move check into loop and past loop. + - c_fc(): if pattern is the empty string, histrpl() + goes into infinate loop. Start searching for = after first + char (this is what at&t ksh seems to do). + - c_fc(): `fc -l first' should list at most 16 + commands according to at&t manual. + - findhist(): re-wrote: shorter, easier to follow. Now + returns an int. (used only by vi code) + - use COMPLEX_HISTORY's allocated history array in + EASY_HISTORY: common init_histvec(), sethistfile() and + sethistsize() functions. + In the process, hist_open() went away. Use histsize + instead of HISTORY in hist_init() and hist_finish() + #ifdefs in lex.h, table.h, + var.c disappear, history variable definitions in lex.c + disappear. + - hist_init(COMPLEX_HISTORY): move hstarted = 1 to after the + FTALKING test. + - hist_finish(): don't open hname if its null + - histrpl(): made static, use ARGS in decl + - made current and curpos static + - changes to allow embedded newlines in commands: + - histsave(): trash only trailing newline + - hist_init(): read in null terminated lines instead of + newline terminated + - hist_finish(): write null terminated lines + - make multiple line command appear in single history line + (EASY_HISTORY only): + - added histappend() to append new command to last + command + - added call to histappend() in getsc_(lex.c) (also: only + adjust source->line if not multiline). + - lex.h: + - remove second decl of history if !EASY_HISTORY + - tree.c(ptree): case TCOM: check if t->vars or t->args is 0 + - vi.c(x_vi): ^D anywhere in command line is eof ($ foobar^D exits). + at&t ksh ignores ^D in middle of line + - edit.c: + - don't need to include string.h - included in stdh.h + - init_editmode(): in at&t ksh, VISUAL takes precenence over + EDITOR, so put it first. Also, at&t ksh doesn't use FCEDIT so + trash it. + - moved initialization of ed_* from x_read() to x_init() since + thats where they are set from tty structs. + - x_init(): set ed_intrc, ed_quitc for _BSD & _POSIX_TERM ifdefs + - send output to shlout (instead of stdout - at&t ksh writes to + stderr) + - made x_do_init static. + - exit.c(x_getc), lex.c(yylex): restart interrupted reads in + x_getc(), changed read-restart in yylex() to only effect call + to read(). + - syn.c(thenpart): then THEN is not optional - generate a syntax + error if no THEN. (ie, `if true ; fi' is not legal). + - syn.c(get_command): don't accept keywords after re-directory (eg, + `> /dev/null if true ; then echo hi ; fi' is not legal). + - vi.c: handle \ and ^[ in command mode ala at&t ksh (filename + completion) + - syn.c(thenpart), syn.c(elsepart): calling token(0) when they want a + keyword (always worked 'cause tpeek() is always called before, with + the keyword flag). Fix: call token(KEYWORD|ALIAS) (at&t ksh does + alias expansion here). Question: pass CONTIN as well? + - syn.c(get_command): LWORD/MPAREN case: do alias expansion when + getting open brace ({). CASE case: ditto for `in' and `esac'. + IF case: ditto for `fi'. FUNCTION case: dito for open brace ({). + - syn.c(dogroup): alias expansion when getting `do' and `done'. + - syn.c(wordlist): alias expansion when getting `in'. + - syn.c(nested): alias expansion when getting `)', `}' + - syn.c(casepart): alias expansion when getting `esac' or `;;'. + Also removed use of cf variable - it does nothing. + - eval.c(expand): case CSUBST: '#'/'%' - increment st so nested + substitutions work. + - var.c(typeset): INTEGER && no assignment: memory was being freed + and then used (there was even a comment saying it was being + done...) + - lex.c, lex.h, exec.c, main.c: + added shf_{open,fdopen,close,gets}() routines so stdio wasn't + used. When reading a command file under osf/1, stdio would + mess up the read pointer when a child exited: the exit flushed + all open files and flushing a file open for reading changes the + current read position (does a seek to where the next char would + be read). This position is then used by the parent process, + who thinks the read position is still at the end of the buffer + it read. + - c_sh.c: + use shf_*() routines avoids two bugs in read: + - on sunos 4.1.3, a read would gobble up a stdio buffer and + never put it back (ie, lseek backwards). Neither a + fflush() nor a fseek(x, 0L, 1) fixed the problem. + (see Bug 26) + - on linux, stdio knows its current offset and seeks there + before reading a buffer. This causes grief when the shell + replaces file descriptors behind stdio's back (ie, all the + time). + $ read x << EOF + hi + EOF + $ cat > /dev/tty << EOF + 1:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + 2:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + 3:abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + EOF + $ + the cat picks up reading at the offset where the read + command left off (ie, at bcdefg... - the 1:a are skipped). + - exec.c(comexec): built-in c_exec && no args: close the saved fd's + (were thrown away). Also, set close-on-exec flag for fd's > 2, + as per at&t manual. + - lex.c(yylex): only accept 1 digit before a redirection (eg, 1> is + ok, 1abc> is not) + - exec.c(iosetup): only accept 1 digit after a dup-redirection + (eg, >&1 is ok, >&1abc is not) + - exec.c(iosetup): use O_APPEND flag for >> redirections; use O_CREAT + flag for <> redirections. (re-organized to accumulate open() flags + and do one open() call); changed error message to know about dup + failing. + - c_sh.c(c_umask): umask should have leading 0 - doesn't when umask + has 3 digits (eg, `umask 222; umask' prints 222 instead of 0222) + - c_sh.c(setsig): declare local struct sigaction and use it so + sa_flags doesn't have to be cleared (also to try to keep away from + global vars). + - main.c(main): + - don't go set TALKING if name starts with -. + - read $HOME/.profile (not .profile) + - main.c(main): re-wrote option parsing code + - edit.c(init_editmode): don't check FCEDIT + - if set -o vi/emacs/gmacs on command line, don't set edit mode from + FCEDIT/EDITOR/etc. + - var.c(setspec): FCEDIT should not magicly change edit mode; VISUAL + and EDITOR should (EDITOR only if VISUAL is not set) + - changed noclobber char from ! to | (this is what at&t ksh uses) + - exec.c(iosetup): use O_EXCL flag if FNOCLOBBER set and >| not used + - c_sh.c(c_set): don't clear FERREXIT if FTALKING is set; although + some ksh manuals say -e is ignored for interactive shells, they + all seem to honour the -e flag for interactive shells. + - vi.c: reset history position to the end after a line is modified + - `(( 1 + 2 ))' no longer prints `+: bad expression'! + - lex.c(yylex): added parenthesis counting so `(( ((1+2)) ))' no + longer generates a syntax error + - expr.c(intvar): if strint() fails, give bad number error + (`let 1+foo' should fail) + - `echo hi 1abc123> /dev/tty' no longer interpreted as + `echo hi 1> /dev/tty' (is `echo hi 1abc123 > /dev/tty') + - `echo hi 1<> /tmp/does-not-exist' now works (used to say cannot + open) + - c_sh.c(c_umask): umask now takes symbolic arguments (g-r, +w, etc.) + - c_ksh.c(c_whence): pass flag[FTRACKALL] to findcom() instead of 1. + - `echo hi >< bar' now produces an error + - jobs.c(j_lookup): now checks for ambiguous job specifications; + callers now get an error message. + - c_ksh.c(c_fgbg): multiple jobs can be specified + - emacs.c(x_transpose): move past transposed chars like (gnu) emacs + does + - main.c(main): don't copy initcoms (messes up memory allocated for + shf_iob[] in initio(), should not be necessary) + - main.c(main), var.c(import), var.c(typeset), c_ksh.c(c_alias), + expr.c(token), misc.c(strnsave): added strnsave() function; use it + instead of writing nulls in the middle of strings; main() no longer + copies startup commands before executing them. + - vi.c: wbuf[] no longer a fixed size (was hard coded to 80 chars). + - alloc.c(aresize): if passed a null pointer, don't free it + - expand.h: restored usage description and NOTE from previous version + - alloc.c, table.h, table.c: + - changed struct fields named `free' to nfree (struct table) and + freelist (struct Block) to allow memory debugging + (involves #defining free) + - main.c(main): smart initialization of PWD (ensures it is always + valid). + - lex.h, table.h, tree.h: removed redundent 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 redundent - always call afree() diff --git a/bin/pdksh/IAFA-PACKAGE b/bin/pdksh/IAFA-PACKAGE new file mode 100644 index 00000000000..fd04bc405bf --- /dev/null +++ b/bin/pdksh/IAFA-PACKAGE @@ -0,0 +1,17 @@ +$OpenBSD: IAFA-PACKAGE,v 1.1 1996/08/14 06:19:10 downsj Exp $ + +Title: pdksh +Version: 5.2.7 +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. +Copying-Policy: Freely Redistributable (mostly public domain, some copyrighted) +Keywords: pdksh, ksh, Korn, shell, command line interpreter diff --git a/bin/pdksh/INSTALL b/bin/pdksh/INSTALL new file mode 100644 index 00000000000..afd11fa04cb --- /dev/null +++ b/bin/pdksh/INSTALL @@ -0,0 +1,151 @@ +$OpenBSD: INSTALL,v 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/bin/pdksh/LEGAL b/bin/pdksh/LEGAL new file mode 100644 index 00000000000..78dd76f95dc --- /dev/null +++ b/bin/pdksh/LEGAL @@ -0,0 +1,36 @@ +$OpenBSD: LEGAL,v 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/bin/pdksh/Makefile b/bin/pdksh/Makefile new file mode 100644 index 00000000000..81c43a87198 --- /dev/null +++ b/bin/pdksh/Makefile @@ -0,0 +1,24 @@ +# $OpenBSD: Makefile,v 1.1 1996/08/14 06:19:12 downsj 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 sigact.c syn.c table.c trap.c \ + tree.c tty.c var.c version.c vi.c + +DEFS= -DHAVE_CONFIG_H +CFLAGS+=${DEFS} -I. -I${.CURDIR} + +CLEANFILES+= siglist.out emacs.out + +.depend trap.o: siglist.out +.depend emacs.o: emacs.out + +siglist.out: config.h sh.h siglist.in siglist.sh + ${.CURDIR}/siglist.sh "${CPP} ${CPPFLAGS} ${DEFS} -I${.CURDIR}" \ + < ${.CURDIR}/siglist.in > siglist.out + +emacs.out: + ${.CURDIR}/emacs-gen.sh ${.CURDIR}/emacs.c > emacs.out + +.include <bsd.prog.mk> diff --git a/bin/pdksh/NEWS b/bin/pdksh/NEWS new file mode 100644 index 00000000000..4daaaf1ac67 --- /dev/null +++ b/bin/pdksh/NEWS @@ -0,0 +1,521 @@ +$OpenBSD: NEWS,v 1.1 1996/08/14 06:19:10 downsj Exp $ + +Version 5.2.7 + +* bug fixes + * vi: commands can be longer that 16 chars... + + +Version 5.2.6 + +* bug fixes + * break/continue: if too big a number is given, last enclosing loop is used. + * set: set +o now generates a set command that can be saved and executed. + * COLUMNS/LINES are now exported when they are automatically set. + * emacs: completion: space not added after directory names. + * vi: # command inserts # after each newline; # on commented line + undoes the commenting. + * some regression tests made less sensitive to their environment. + * should compile on os/2 again. + + +Version 5.2.5 + +* bug fixes + * configuration: if sig_setjmp() being used, use sigjmp_buf. + * configuration: test for times() fixed. + * configuration: ANSI usage of setjmp() and offsetof(). + * echo/print: octal number in \ sequence must start with a 0. + * echo: don't treat a lone minus as an option. + * typeset -f: correctly prints functions with select statements. + * vi: / with no pattern repeats last search. + * vi: repeat counts no longer effect file completion/expansion. + * vi: tab-completion now also works in command mode. + * emacs/vi: ^O key now read as ^O on suns/alphas (was eaten by tty driver). + * emacs: now has file expansion (^[*). + * emacs: ^O goes to next command, not next next command. + * COLUMNS/LINES: environment variables now set on start up. + * variables: command line assignments can't change readonly variables. + * arithmetic: giving multiple bases (5#4#3) no longer allowed. + * arithmetic: when assigning a non-integer variables, base no longer shown. + * history: fixed replacement bug introduced in last release. + * history: -1 refers to the previous command, not current fc command. + * parsing: correctly handles command substitutions starting with a newline. + +* full command completion added (both vi and emacs). + + +Version 5.2.4 + +* bug fixes + * PS1 imported from environment again. + * vi handles prompts with embedded newlines. + * errors redirecting stderr aren't lost. + * redirection errors for <&n no longer reported as to >&n. + * don't do globbing on re-direction targets if not interactive (POSIX). + * pattern matching in [[ foo = foo*bar ]] now works again. + * HUP signals are passed on to jobs running in the foreground. + * $? now valid (ie, not 0) in trap handlers, `...` expressions, etc. + * noclobber doesn't effect redirections to non-regular files (eg, /dev/null) + * \newline in here-document delimiters handled correctly. + * typeset -f now reports unloaded autoload functions properly. + * ~,~+,~- are not expanded if HOME,PWD,OLDPWD are unset. + * vi completion/expansion: * not appeded if word contains $. + * cd: error message contains correct directory string. + * vi expansion list: printed in column form ala at&t ksh. + * ^C while reading .profile/$ENV nolonger causes shell to exit. + * option errors for build-in commands now include command name. + * emacs completion/expansion: ' and " are treated as word delimiters. + * fc: replacements (a=b) no longer truncates the command. + * alias: alias -t -r now cleans out the tracked alias table. + +* compile-time configuration changed: configure script --enable-XXX options + replace the old options.h file. Use "configure --help" for information + on what the options do (they are basicly the same as what was in the + options.h file). Shell can be configured as a (almost) plain bourne + shell using the --enable-shell=sh (also generates appropriate man page). + Installed name of program (ksh or sh) can be modified using configure's + --program-* options. + +* ulimit: added -p (maxproc) option. + +* case statements can use the old syntax of {,} instead of in,esac. + +* extended file globbing added (eg, f*(bar|Bar) matches f, fbar fBarbar, etc). + +* trim expressions can be of the form ${parameter#pattern1|pattern2|...}. + +* if compiled as sh, $ENV included only if posix option is set. + +* vi: U command added (undo all changes on line). + +* the Bugs script has been replaced by a new regression testing system, kept + in the tests/ directory (contains a perl script which sets up a test + environment and runs tests, and a bunch of tests). + + +Version 5.2.3 + +* bug fixes + * arrays: set -A and unset now unset whole array. + * history(complex version): fixed core caused by uninitialized hist_source. + * getopts: will continue parsing options if called after error. + * getopts: doesn't print shell name twice in error message. + * posix: if posix option is set, $0 is always the name of the shell. + * history: "fc -s foo" now finds foo if it is the most recent command. + * let: expression errors no longer cause scripts to exit. + * PS1: does not go into infinite loop if there is an expansion error. + * configure: memmove/bcopy test has a change of working now. + * configure: check for flock(), undefine COMPLEX_HISTORY if not found. + * substitution: tilde substitution works in word part of ${var[-+=?]word}. + * history: "fc <number>" now edits <number>, not <number> to most recent. + * cd: two argument form works again. + * special commands taking assignments (alias,set,etc.): field splitting, + file globbing, etc. suppressed only for args that look like assignments. + * command: -V now finds reserved words. + +* added support for Korn's /dev/fd tests + +* new compile time option: DEFAULT_ENV - if defined, it names a file to + include if $ENV is not set. + +* test -o option: if option starts with a !, the test is negated. The test + always fails if the option doesn't exist (so [ -o foo -o -o !foo ] is true + iff option foo exists). + +* new option: set -o nohup (currently on by default) - if set, running jobs + are not kill -HUP'd when a login shell exits; if clear, they are. In + future, this will be clear by default (to act like at&t ksh) - if you don't + (won't) like this, add "[ -o !nohup ] && set -o nohup" to your .profile. + +Version 5.2.2 + +* bug fixes + * included c_test.h in distribution (opps). + +Version 5.2.1 + +* bug fixes + * emacs: buffer no longer overflowed when completing file names/commands. + * emacs: <ESC><tty-erase-char> now bound to delete-back-word (was ...-char). + * emacs: ignores a space char after ^V (version), as in at&t ksh. + * emacs: ^O bound to newline-and-next, ^X^Y bound to list-file. + * emacs: emacs words now include underscore. + * vi: set -o markdirs, directories and ^[= now get along. + * cd: -P no longer leaves .. and . in PWD. + * cd: if CDPATH set and can't cd, error doesn't contain any of CDPATH. + * cd: sets PWD properly, on machines without getwd(). + * configuration: unistd.h test fixed (include sys/types before dirent.h). + * configuration: detects memmove/bcopy's that don't handle overlaps. + * [[ ... ]] does lazy evaluation (eg, [[ ! -f foo || $(<foo) = bar ]] does + not evaluate $(<foo) if foo doesn't exist). + + +Version 5.2.0 + +* bug fixes + * vi: completion now allows globbing characters. + * vi: can deal with very long prompts. + * vi: . (redo) works after j, k, return. + * vi: [dyc]% causing backwards motion now gets correct start/end position. + * vi: complete_word (<ESC>\) no longer rings bell on ambiguous matches. + * vi: globbing doesn't append * if last component of file has globbing chars. + * emacs: most commands now take arguments, arguments can be multi digit. + * emacs: newline-and-next command works more correctly. + * after set -u, trimming substitutions no longer automatically fail. + * set -i no longer reports an internal error. + * FPATH: no longer incorrectly complains about function not being defined. + by a file; when it connectly complains, shell name in error is correct. + * set -a; set -o allexport: these now do something. + * shell deals with non-blocking input (clears non-blocking flag). + * autoconf: fixed memmove/memcpy tests. + * ! translation in prompt now done before parameter substitution. + * siglist.sh works around bug in bash 1.4.3. + * correct positional parameters accessible in local assignments. + * (sleep 100&) no longer waits for sleep to complete. + +* fc -s option added (same as -e -). + +* vi: ^V command (version) added. + +* vi: @<char> macros added (@X executes vi commands in alias _X). + +* emacs: bind -l lists all command names. + +* emacs: goto-history command added. + +* emacs: search-char function changed to search-char-forward; + added search-char-backward (bound to <ESC>^]). + +* cd and pwd take -L and -P options; added set -o physical option + (PWD,OLDPWD no longer readonly). + +* new command line -l option tells shell it is a login session. + +* os2 changes completed. + +* uses autoconf 2.x (was using 1.x). + +Version 5.1.3 + +* bug fixes + * fixed bug in arithmetic expression evaluation (||,&& caused core dump). + * ulimit code now uses rlim_t or quad_t, if appropriate. + * vi: file completion in command mode of single character filename works. + * vi: file completion with markdirs set resulted in two trailing /'s. + * vi: completion/expansion/listing acts like at&t ksh when expand fails. + * vi: ~ takes count. + * lines from history file are no longer negative (easy history). + * Makefile now uses manual extension consistently. + * fc now allows out of range relative (negative) numbers. + * functions with elif now printed correctly. + * FPATH now searched if PATH search fails, as in at&t ksh. + +* typeset -f output is readable (and more correct) + +* compiles under SCO unix + +* more os/2 changes integrated + +Version 5.1.2 + +* bug fixes + * for i; do ...; done now accepted. + * leading non-white-space IFS chars no longer ignored (now delimit fields). + * fixed globbing code so echo /usr/*/make works. + +Version 5.1.1 + +* bug fixes + * { ..;} allowed instead of do ..;done in for/select loops + * EOF after ; or & no longer causes syntax error + * complex history: when shrinking history file, keeps inside buffer space. + * vi editing: `v' on modified line no longer changes command numbering. + * ^C in vi/emacs no longer prints two newlines. + * long arguments (> 255) with globbing characters don't cause core dumps. + +* new (un)option, KSH, which compiles out ksh code (for producing minimal sh). + +* os/2 changes partly merged. + +Version 5.1.0 + +* bug fixes + * problem caused by _POSIX_VDISABLE on BSDI machines fixed + * exit status set to 127 if command file could not be opened + * profile files processed if basename argv[0] starts with (was $0) + * PWD now imported properly from environment. + * emacs code now either uses dynamic buffers or does overflow checking. + * emacs forward-word and delete-forward-word now work like other emacs's. + * ^C/^\ in vi/emacs work like at&t ksh (prompt reprinted, even if trapped). + * history number to command mapping now constant (numbers used to change). + * configuration: BSD tty now used on ultrix (avoids type ahead problem) + * eof in the middle of multiline commands now ignored if ignoreeof set. + * vi space command now works again. + * pointer mismatch compiler warning for waitpid() call dealt with. + * emacs internal memory error in command completion fixed. + * autoloaded functions now work first try. + * SECONDS parameter now acts like in at&t ksh. + +* sense of vi-show8 option changed: 8-bit characters are printed as is by + default; turning on vi-show8 now causes chars with 8th bit set to be + prefixed with M-. + +* missing sections in man page added (now basicly complete) + +* emacs ^V command added: prints ksh version + +* vi g command added: moves to most recent history + +Version 5.0.10 + +* bug fixes + * [[ ]] construct unbroken. + * the newline after a here document marker is now read properly. + * blank lines no longer cause $? to be set to 0. + * mail checking now uses atime/mtime instead of size. + * changing attributes of exported parameters no longer causes core dump. + * the last command in a file does not have to end in a newline. + * empty expressions now treated as 0 (previously generated an error). + * nul bytes stripped from input. + * 0241 (M-!) in a command substitution no longer lost. + * when read used in startup file, line continuation no longer causes crash. + * very long commands in history no longer cause vi to overwrite memory. + * easy history: when saving history, avoid going past the end of history. + * emacs mode no longer entered if EDITOR/VISUAL set to null string. + * command -p disabled in restricted mode. + * closed file descriptors are re-closed after a redirection. + * lone [ (test command) no longer causes globbing code to search directory. + * if TIMES_BROKEN is defined, ksh_times no longer recurses infinitely. + * `r r' no longer repeats r command forever. + * make depend no longer generates backslash followed by a blank line. + * globbing code now deals with symlinks that point to non-existent files. + * if the ] is missing in a pattern, the [ matches the [ character. + * syntax errors in test no longer have two newlines. + * in vi, G now goes to the oldest history (was newest). + * configuration: test for sys_siglist now harder for optimizers to break. + * configuration: look for clock_t in sys/times.h. + * configuration: use _SIGMAX, if available, for # of signals. + * SIGHUP now causes builtin read command to exit. + * wait builtin now returns whenever a 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. Compatable 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/bin/pdksh/NOTES b/bin/pdksh/NOTES new file mode 100644 index 00000000000..45bab7b2752 --- /dev/null +++ b/bin/pdksh/NOTES @@ -0,0 +1,491 @@ +$OpenBSD: NOTES,v 1.1 1996/08/14 06:19:10 downsj Exp $ + +General features of at&t ksh that are not (yet) in pdksh: + - exported aliases. + - exported functions. + - set -t. + - signals/traps not cleared during functions. + - trap DEBUG, local ERR and EXIT traps in functions. + - ERRNO, LINENO, LINES parameters. + - doesn't have posix file globbing (eg, [[:alpha:]], etc.). + - use of an `agent' to execute unreadable/setuid/setgid shell scripts + (don't ask). + +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 loose 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 construct. 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 in front of a command + (eg, HISTFILE=/foo/bar echo hi) effects 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 seperate process in pdksh). + Misc: + +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). + - co-processes: in at&t ksh, accessing the co-process in a redirection + always closes the shells copies of the file descriptors; in pdksh + only redirections in an empty exec command has this effect. This + may change if the at&t style proves more useful (doubt it, though) + or if many scripts depend on it. + - 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. + +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. + - 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 seperate name spaces. + in at&t ksh, alias accepts + options (eg, +x, +t) - pdksh does not. + in pdksh, alias has a -d option to allow examination/changing of + cached ~ entries, also unalias has -d and -t options (unalias -d + is useful if the ~ cache gets out of date - not sure how at&t deals + with this problem (it does cache ~ entries)). + - at&t ksh will stop a recursive function after about 60 calls; pdksh + will not since the limit is arbitrary and can't be controlled + by the user (hit ^C if you get in trouble). + - the wait command (with and without arguments) in at&t ksh will wait for + stopped jobs when job control is enabled. pdksh doesn't. + - at&t ksh automatically sets the bgnice option for interactive shells; + pdksh does not. + - in at&t ksh, "eval `false`; echo $?" prints 1, pdksh prints 0 (which + is what POSIX says it should). Same goes for "wait `false`; echo $?". + (same goes for "set `false`; echo $?" if posix option is set - some + scripts that use the old getopt depend on this, so be careful about + setting the posix option). + - in at&t ksh, print -uX and read -uX are interrperted as -u with no + argument (defaults to 1 and 0 respectively) and -X (which may or + may not be a valid flag). In pdksh, -uX is interpreted as file + descriptor X. + - in at&t ksh, some signals (HUP, INT, QUIT) cause the read to exit, others + (ie, everything else) do not. When it does cause exiting, anything read + to that point is used (usually an empty line) and read returns with 0 + status. pdksh currently does similar things, but for TERM as well and + the exit status is 128+<signal-number> - in future, pdksh's read will + do this for all signals that are normally fatal as required by POSIX. + (POSIX does not require the setting of variables to null so applications + shouldn't rely on this). + - in pdksh, ! substitution done before variable substitution; in at&t ksh + it is done after substitution (and therefor may do ! substitutions on + the result of variable substitutions). POSIX doesn't say which is to be + done. + - pwd: in at&t ksh, it ignores arguments; in pdksh, it complains when given + arguments. + - the at&t ksh does not do command substition on PS1, pdksh does. + +Oddities in ksh (pd & at&t): + - array references inside (())/$(()) are strange: + $(( x[2] )) does the expected, $(( $x[2] )) doesn't. + - `typeset -R3 X='x '; echo "($X)"` produces ( x) - trailing + spaces are stripped. + - typeset -R turns off Z flag. + - both shells have the following mis-feature: + $ x='function xx { + cat -n <<- EOF + here we are in xx + EOF + }' + $ (eval "$x"; (sleep 2; xx) & echo bye) + [1] 1234 + bye + $ xx: /tmp/sh1234.1: cannot open + - bizarre special handling of alias/export/readonly/typeset arguments + $ touch a=a; typeset a=[ab]; echo "$a" + a=[ab] + $ x=typeset; $x a=[ab]; echo "$a" + a=a + $ + - both ignore SIGTSTP,SIGTTIN,SIGTTOU in exec'd processes when talking + and not monitoring (at&t ksh kind of does this). Doesn't really make + sense. + (Note that ksh.att -ic 'set +m; check-sigs' shows TSTP et al aren't + ignored, while ksh.att -ic 'set +m^J check-sigs' does... very strange) + - when tracing (set -x), and a command's stderr is redirected, the trace + output is also redirected. so "set -x; echo foo 2> /tmp/O > /dev/null" + will create /tmp/foo with the lines "+ > /dev/null" and "+ echo foo". + - undocumented at&t ksh feature: FPATH is searched after PATH if no + executable is found, even if typeset -uf wasn't used. + +at&t ksh bugs: + [various versions: + MIPS m120 RISC/os 5.0: Version 11/16/88d + Dec alpha osf/1 v1.3: OSF/1 Version 11/16/88d NLS + HP pa HP-UX 9.01: Version 11/16/88 + ] + - (only hpux) + $ _[2]=hi + Bus error (core dumped) + - (only riscos, hpux) + $ typeset x[ + $ + - (only osf/1) + $ A=B cat << EOF + .$A. + EOF + Segmentation fault(coredump) + $ + - (only osf/1) + $ read "?foo " + foo Foo + $ set | grep Foo + =Foo + $ + - (all) + $ typeset -i A + $ typeset -L3 A + $ typeset -l A + Illegal instruction (core dumped) + - (all) + $ for i in a b c ; do echo $i, ${i[2]}, ${i[10]} ; done + a, , + a, , b + a, , c + $ + - (all) + $ echo ${abc:-G { I } K } + G { I K } + $ + $ abc=hi + $ echo ${abc:-G { I } K } + hi K } + $ + The second echo should only have printed `hi'. + - (all) + $ echo ${abc:- > foo} + syntax error: > unexpected + $ + - (all? hpux) read reads too much from pipe (when pipe isn't stdin) + print 'hi\nthere' | ksh 8<&0 0< /dev/tty + $ read -u8 x + $ print $x + hi + $ cat 0<&8 + $ read -u8 y + $ print $y + there + $ + - (all) + $ umask 0 + $ umask + 00 + $ + - (osf, mips, !hpux) + $ exec alias + alias: not found + (shell dead) + - (all) non-white space IFS in non-substitution not preserved + $ IFS="$IFS:" + $ echo : "$@" # this is ok + : + $ echo :"$@" # this should print : too (me thinks) + + $ + - (only osf/1) + $ set +m + $ sleep 1 & # wait for a sec or two + $ jobs + Memory fault (core dumped) + - (all) + $ (sleep 1 & echo hi) & + [1] 123 + $ [1] 234 + hi + - (osf/1, mips) + $ getopts abc optc -a -b -c + $ getopts abc optc -a -b -c + $ getopts abc optc -a + Memory fault (core dumped) + - (osf/1) POSIX says OPTIND shall be initialized to 1 + $ echo $OPTIND + 0 + $ + - (osf/1 + others?) + $ typeset -ri r=10 + $ let r=12 + $ echo $r + 12 + $ + - (osf/1 + others?) + $ typeset -i a + $ typeset -L3 a + Memory fault (core dumped) + - (osf/1 + others?): -L strips leading \ \t\n\r, -R only strips trailing + spaces + $ typeset -L3 x + $ x=' ^I^J^M 2' + $ echo "($x)" + (2 ) + $ typeset -R3 y + $ x='2^I^J^M ' + $ echo "($x)" + (^I^J^M) + $ + - (osf/1 + others?) + $ typeset +i RANDOM + Memory fault (core dumped) + - (osf/1 + others?): -L/-R/-Z clear -l/-u after assignment and vise versa + $ typeset -u x=ab + $ echo "($x)" + (AB) + $ typeset -L4 x=def + $ echo "($x)" + (DEF ) + $ typeset | grep ' x$' + leftjust 4 x + $ + $ typeset -L4 x=def + $ echo "($x)" + (def ) + $ typeset -u x=ab + $ echo "($x)" + (AB ) + $ typeset | grep ' x$' + uppercase x + $ + $ typeset -i x + $ x='2()' + $ x='()' + $ x='2(4)' + - (osf/1, others?) + $ unset foo + $ echo "${foo:-"*"}" + <results of * expansion> + $ + - (osf/1, others?) + $ alias blah + blah: alias not found + $ alias -x blah | grep blah + blah + $ type blah + Memory fault (core dumped) + - (osf/1, others?) + $ trap 'echo hi; false' ERR + $ false + hi + hi + .... + Memory fault (core dumped) + - (osf/1, others?) + $ typeset +i ERRNO + Memory fault (core dumped) + - (osf/1, others?) + $ X=abcdef + $ echo ${X#a{b,c}e} # does not match {} inside word part of ${..#..} + abcdefe} + $ + - (osf/1, others?) + $ x=f=abcdef + $ echo ${f#a|abc} + def + $ echo ${f#abc|a} + bcdef + $ echo ${f#abc|a|d} + abcdef + $ + - (osf/1, hp-ux, others?) + $ i() echo hi + $ typeset -f + function i + { + hi + $ + - (osf/1, others?) + $ function X { + echo start of X + function Y { + echo in Y + } + echo end of X + } + $ X + start of X + end of X + $ typeset -f + function X + { + echo start of X + function Y { + echo in Y + } + echo end of X + } + function Y + { + echo in Y + echo end of X + } + } + $ + - (osf/1, others?) + $ while read x; do print -r "A $x"; done |& + [1] 18212 + $ exec 8<&p + $ kill %1 + Memory fault + - (osf/1, others?) Error only happens for builtin commands (/bin/echo works) + $ while read x; do print -r "A $x"; done |& + [1] 18212 + $ echo hi <&p + hi + $ echo hi <&p + ksh: p: bad file unit number + $ while read x; do print -r "A $x"; done |& + ksh: process already exists + $ + - (osf/1, others?) in restricted shells, command -p should not work. + $ PATH=/tmp ksh -r + $ print hi | command -p cat -n + 1 hi + $ + - (osf/1, others?) error message wrong for autoload files that don't define + functions + $ FPATH=/tmp + $ echo echo hi there > /tmp/aja + $ aja + hi there + ksh: echo: not found + $ + +POSIX sh questions (references are to POSIX 1003.2-1992) + - arithmetic expressions: how are empty expressions treated? + (eg, echo $(( ))). at&t ksh (and now pdksh) echo 0. + Same question goes for `test "" -eq 0' - does this generate an error + or, if not, what is the exit code? + - should tilde expansion occur after :'s in the word part of ${..=..}? + (me thinks it should) + - if a signal is received during the execution of a built-in, + does the builtin command exit or the whole shell? + - is it legal to execute last command of pipeline in current + execution environment (eg, can "echo foo | read bar" set + bar?) + - what action should be taken if there is an error doing a dup due + to system limits (eg, not enough feil destriptors): is this + a "redirection error" (in which case a script will exit iff the + error occured while executing a special built-in)? + IMHO, shell should exit script. Couldn't find a blanket statement + like "if shell encounters an unexpected system error, it shall + exit non-interactive scripts"... + +POSIX sh bugs (references are to POSIX 1003.2-1992) + - in vi insert mode, ^W deletes to beginning of line or to the first + blank/punct character (para at line 9124, section 3). This means + "foo ^W" will do nothing. This is inconsistent with the vi + spec, which says delete preceding word including and interceding + blanks (para at line 5189, section 5). + - parameter expansion, section 3.6.2, line 391: `in each case that a + value of word is needed (..), word shall be subjected to tilde + expansion, parameter expansion, ...'. Various expansions should not + be performed if parameter is in double quotes. + - the getopts description says assigning OPTIND a value other than 1 + produces undefined results, while the rationale for getopts suggests + saving/restoring the OPTIND value inside functions (since POSIX + functions don't do the save/restore automatically). Restoring + OPTIND is kind of dumb since getopts may have been in the middle + of parsing a group of flags (eg, -abc). + - unclear whether arithmetic expressions (eg, $((..))) should + understand C integer constants (ie, 0x123, 0177). at&t ksh doesn't + and neither does pdksh. + - `...` definition (3.6.3) says nothing about backslash followed by + a newline, which sh and at&t ksh strip out completely. e.g., + $ show-args `echo 'X + Y'` + Number of args: 1 + 1: <XY> + $ + POSIX would indicate the backslash-newline would be preserved. + - does not say how "cat << ''" is to be treated (illegal, read 'til + blank line, or read 'til eof). at&t ksh reads til eof, bourne shell + reads 'til blank line. pdksh reads 'til blank line. diff --git a/bin/pdksh/PROJECTS b/bin/pdksh/PROJECTS new file mode 100644 index 00000000000..52913caf817 --- /dev/null +++ b/bin/pdksh/PROJECTS @@ -0,0 +1,132 @@ +$OpenBSD: PROJECTS,v 1.1 1996/08/14 06:19:10 downsj 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 time + + 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 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 it fits the posix style) + + * test suite + Ideally, as the builtin utilities are being POSIXized, short tests + should be written to be used in regression testing. The tests + directory contains some tests, but many more need to be written. + + * internationalization + Need to handle with the LANG and LC_* environment variables. This + involves changes to ensure <ctype.h> macros are being used (currently + uses its own macros in many places), figuring out how to deal with + bases (for integer arithmetic, eg, 12#1A), and (the nasty one) doing + string look ups for error messages, etc.. It probably isn't worth + translating strings to other languages yet as the code is likely + to change a lot in the near future, but it would be good to have the + code set up so string tables can be used. + + * trap code + * add the DEBUG trap. + * fix up signal handling code. In particular, fatal vs tty signals, + have single 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 + POSIX and at&t ksh functions are different in that POSIX functions + don't change disable/restore traps and option parsing (OPTIND/OPTARG + plus internal state) isn't saved/restored. The suggestion made in + POSIX.2 rationale is to have x() { .. } do the POSIX thing, and have + function x { ..} do the at&t ksh thing. So, should have two types of + functions. + + * 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. + + * POSIX says if an exec fails, the exit code should be 127 (not found) + or 126 (not executable)... + + * remove static limits created by fixed sized arrays + (eg, getsc_(line[]), ident[], heres[], PATH, states(lex.c), + buffer size in emacs/vi code) + + * merge the emacs and vi code (should reduce the size of the shell and + make maintenance easier). + [John Rochester is working on the merge] + + * add POSIX globbing (eg, [[:alnum:]]), see POSIX.2:2.8.3.2. + + * catch SIGWINCH and update the COLUMNS and LINES parameters (also, + need to let the command line editor know of change - ideally this + would work even if the editor was currently reading commands). + + * teach shf_vfprintf() about long long's (%lld); also make %p use + long longs if appropriate. + + * add \[...\] parsing to prompt printing (don't count width of chars + inside the \[..\] - used to keep escape sequences in prompts from + messing up command-line-editor's idea of where the cursor is) + + * file(command) completion list in vi/emacs: change so a number-prefix + picks one of the possibilities (eg, if in vi: foo^[= lists fooa, foob + and fooc as possible completions, ^[2= would choose the second + possibility (foob)). diff --git a/bin/pdksh/README b/bin/pdksh/README new file mode 100644 index 00000000000..d22b8e40c84 --- /dev/null +++ b/bin/pdksh/README @@ -0,0 +1,164 @@ +$OpenBSD: README,v 1.1 1996/08/14 06:19:10 downsj Exp $ + +Last updated June '96 for pdksh-5.2.6. + (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 currently underway 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 + + The more detailed description: + * run "configure --help | your-favorite-pager" and look at the + --enable-* and --disable-* options (they are at the end). + Select any you options you wish to enable/disable + (most people can skip this step). + * run configure: this is a GNU autoconf configure script that will generate + a Makefile and a config.h. Some of the useful options to configure are: + --prefix=PATH indicates the directory tree under which the binary + and man page are installed (ie, PATH/bin/ksh and + PATH/man/man1/ksh.1). + The default prefix is /usr/local. + --exec-prefix=PATH overrides --prefix for machine dependent files + (ie, the ksh binary) + --program-prefix=pd install binary and man page as pdksh and pdksh.1 + --verbose show what is being defined as script runs + Note that you don't have to build in the source directory. To build + in a separate directory, do something like: + $ mkdir objs + $ cd objs + $ ../configure --verbose + .... + $ make + See the file INSTALL for a more complete description of configure and its + generic options (ksh specific options are documented in the --help output) + * miscellaneous configuration notes: + * If your make doesn't understand VPATH, you must compile in + the source directory. + * On DecStations, MIPS and SONY machines with older C compilers that + can't handle "int * volatile x", you should use gcc or turn off + optimization. The problem is configure defines volatile to nothing + since the compiler can't handle it properly, but the compiler does + optimizations that the volatile is meant to prevent. So. Use gcc. + * On MIPS RISC/os 5.0 systems, sysv environment, <signal.h> is + messed up - it defines sigset_t, but not any of the rest of + the posix signals (the sigset_t typedef should be in the + ifdef KERNEL section) - also doesn't have waitpid() or wait3(). + Things compile up ok in the svr4 environment, but it dumps core + in __start (perhaps our system doesn't have the full svr4 + environ?). Try compiling in the bsd43 environ instead (still not + perfect - see BUG-REPORTS file), using gcc - cc has problems with + macro expansions in the argument of a macro (in this case, the ARGS + macro). + * On TitanOS (Stardent/Titan), use `CC="cc -43" configure ...'. + When configure finishes, edit config.h, undef HAVE_DIRENT_H and + define HAVE_SYS_DIR_H (the dirent.h header file is broken). + * On Linux (red hat distribution), check that /dev/tty has mode 0666 + (not mode 0644). If it has the wrong permissions, ksh will print + warnings about not being able to do job control. + * 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. + * run make install: this installs ksh (in /usr/local/bin/ksh by default, + or where ever you told configure to put things). + +The following is a list of machines that pdksh is reported to work on: + -/PC Linux 1.x + -/PC NetBSD 0.9a + -/PC BSDI 1.1 + -/PC FreeBSD 2.0, 2.1 + -/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 + Commadore/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 + Dec/vax 4.3BSD+NFS (MtXinu) + 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(?) + 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). + +You can send bug reports, fixes, and enchancements 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 options.h options and set -o options) + * the output of configure, with the verbose flag + (eg, ./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. + +Michael Rendell, michael@cs.mun.ca. diff --git a/bin/pdksh/alloc.c b/bin/pdksh/alloc.c new file mode 100644 index 00000000000..bc39b1bc045 --- /dev/null +++ b/bin/pdksh/alloc.c @@ -0,0 +1,288 @@ +/* $OpenBSD: alloc.c,v 1.1 1996/08/14 06:19:10 downsj Exp $ */ + +/* + * area-based allocation built on malloc/free + */ + +#include "sh.h" +#ifdef MEM_DEBUG +# undef alloc +# undef aresize +# undef afree +#endif /* MEM_DEBUG */ + +#define ICELLS 100 /* number of Cells in small Block */ + +typedef union Cell Cell; +typedef struct Block Block; + +/* + * The Cells in a Block are organized as a set of objects. + * Each object (pointed to by dp) begins with a size in (dp-1)->size, + * followed with "size" data Cells. Free objects are + * linked together via dp->next. + */ + +union Cell { + size_t size; + Cell *next; + struct {int _;} junk; /* alignment */ +}; + +struct Block { + Block *next; /* list of Blocks in Area */ + Cell *freelist; /* object free list */ + Cell *last; /* &b.cell[size] */ + Cell cell [1]; /* [size] Cells for allocation */ +}; + +static Block aempty = {&aempty, aempty.cell, aempty.cell}; + +/* create empty Area */ +Area * +ainit(ap) + register Area *ap; +{ + ap->freelist = &aempty; + return ap; +} + +/* free all object in Area */ +void +afreeall(ap) + register Area *ap; +{ + register Block *bp; + register Block *tmp; + + bp = ap->freelist; + if (bp != NULL && bp != &aempty) { + do { + tmp = bp->next; + free((void*)bp); + bp = tmp; + } while (bp != ap->freelist); + ap->freelist = &aempty; + } +} + +/* allocate object from Area */ +void * +alloc(size, ap) + size_t size; + register Area *ap; +{ + int cells, split; + register Block *bp; + register Cell *dp, *fp, *fpp; + + if (size <= 0) { + aerror(ap, "allocate bad size"); + return NULL; + } + cells = (unsigned)(size - 1) / sizeof(Cell) + 1; + + /* find Cell large enough */ + for (bp = ap->freelist; ; bp = bp->next) { + for (fpp = NULL, fp = bp->freelist; + fp != bp->last; fpp = fp, fp = fpp->next) + if ((fp-1)->size >= cells) + goto Found; + + /* wrapped around Block list, create new Block */ + if (bp->next == ap->freelist) { + bp = (Block*) malloc(offsetof(Block, cell[ICELLS]) + + sizeof(bp->cell[0]) * cells); + if (bp == NULL) { + aerror(ap, "cannot allocate"); + return NULL; + } + if (ap->freelist == &aempty) + bp->next = bp; + else { + bp->next = ap->freelist->next; + ap->freelist->next = bp; + } + bp->last = bp->cell + ICELLS + cells; + fp = bp->freelist = bp->cell + 1; /* initial free list */ + (fp-1)->size = ICELLS + cells - 1; + fp->next = bp->last; + fpp = NULL; + break; + } + } + Found: + ap->freelist = bp; + dp = fp; /* allocated object */ + split = (dp-1)->size - cells; + if (split < 0) + aerror(ap, "allocated object too small"); + if (--split <= 0) { /* allocate all */ + fp = fp->next; + } else { /* allocate head, free tail */ + (fp-1)->size = cells; + fp += cells + 1; + (fp-1)->size = split; + fp->next = dp->next; + } + if (fpp == NULL) + bp->freelist = fp; + else + fpp->next = fp; + return (void*) dp; +} + +/* change size of object -- like realloc */ +void * +aresize(ptr, size, ap) + register void *ptr; + size_t size; + Area *ap; +{ + int cells; + register Cell *dp = (Cell*) ptr; + + if (size <= 0) { + aerror(ap, "allocate bad size"); + return NULL; + } + cells = (unsigned)(size - 1) / sizeof(Cell) + 1; + + if (dp == NULL || (dp-1)->size < cells) { /* enlarge object */ + /* XXX check for available adjacent free block */ + ptr = alloc(size, ap); + if (dp != NULL) { + memcpy(ptr, dp, (dp-1)->size * sizeof(Cell)); + afree((void *) dp, ap); + } + } else { /* shrink object */ + int split; + + split = (dp-1)->size - cells; + if (--split <= 0) /* cannot split */ + ; + else { /* shrink head, free tail */ + (dp-1)->size = cells; + dp += cells + 1; + (dp-1)->size = split; + afree((void*)dp, ap); + } + } + return (void*) ptr; +} + +void +afree(ptr, ap) + void *ptr; + register Area *ap; +{ + register Block *bp; + register Cell *fp, *fpp; + register Cell *dp = (Cell*)ptr; + + /* find Block containing Cell */ + for (bp = ap->freelist; ; bp = bp->next) { + if (bp->cell <= dp && dp < bp->last) + break; + if (bp->next == ap->freelist) { + aerror(ap, "freeing with invalid area"); + return; + } + } + + /* find position in free list */ + for (fpp = NULL, fp = bp->freelist; fp < dp; fpp = fp, fp = fpp->next) + ; + + if (fp == dp) { + aerror(ap, "freeing free object"); + return; + } + + /* join object with next */ + if (dp + (dp-1)->size == fp-1) { /* adjacent */ + (dp-1)->size += (fp-1)->size + 1; + dp->next = fp->next; + } else /* non-adjacent */ + dp->next = fp; + + /* join previous with object */ + if (fpp == NULL) + bp->freelist = dp; + else if (fpp + (fpp-1)->size == dp-1) { /* adjacent */ + (fpp-1)->size += (dp-1)->size + 1; + fpp->next = dp->next; + } else /* non-adjacent */ + fpp->next = dp; +} + +#if DEBUG_ALLOC +void +aprint(ap, ptr, size) + register Area *ap; + void *ptr; + size_t size; +{ + Block *bp; + + if (!ap) + shellf("aprint: null area pointer\n"); + else if (!(bp = ap->freelist)) + shellf("aprint: null area freelist\n"); + else if (bp == &aempty) + shellf("aprint: area is empty\n"); + else { + int i; + Cell *fp; + + for (i = 0; !i || bp != ap->freelist; bp = bp->next, i++) { + if (ptr) { + void *eptr = (void *) (((char *) ptr) + size); + /* print block only if it overlaps ptr/size */ + if (!((ptr >= (void *) bp + && ptr <= (void *) bp->last) + || (eptr >= (void *) bp + && eptr <= (void *) bp->last))) + continue; + shellf("aprint: overlap of 0x%p .. 0x%p\n", + ptr, eptr); + } + shellf("aprint: block %2d: 0x%p .. 0x%p (%d)\n", i, + bp->cell, bp->last, + (char *) bp->last - (char *) bp->cell); + for (fp = bp->freelist; fp != bp->last; fp = fp->next) + shellf( + "aprint: 0x%p .. 0x%p (%d) free\n", + (fp-1), (fp-1) + (fp-1)->size, + (fp-1)->size * sizeof(Cell)); + } + } +} +#endif /* DEBUG_ALLOC */ + + +#ifdef TEST_ALLOC + +Area a; + +main(int argc, char **argv) { + int i; + char *p [9]; + + ainit(&a); + for (i = 0; i < 9; i++) { + p[i] = alloc(124, &a); + printf("alloc: %x\n", p[i]); + } + for (i = 1; i < argc; i++) + afree(p[atoi(argv[i])], &a); + afreeall(&a); + return 0; +} + +void aerror(Area *ap, const char *msg) { + abort(); +} + +#endif + diff --git a/bin/pdksh/c_ksh.c b/bin/pdksh/c_ksh.c new file mode 100644 index 00000000000..01bef267466 --- /dev/null +++ b/bin/pdksh/c_ksh.c @@ -0,0 +1,1375 @@ +/* $OpenBSD: c_ksh.c,v 1.1 1996/08/14 06:19:10 downsj Exp $ */ + +/* + * built-in Korn commands: c_* + */ + +#include "sh.h" +#include "ksh_stat.h" +#include <ctype.h> + +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 */ + if (current_wd[0]) + setstr(oldpwd_s, current_wd); + + 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) { + set_current_wd(pwd); + setstr(pwd_s, pwd); + } 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 = get_coproc_fd(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). Truely 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) { + if (flags & PO_COPROC) + restore_pipe(opipe); + if (errno == EINTR) { + /* allow user to ^C out */ + intrcheck(); + if (flags & PO_COPROC) + opipe = block_pipe(); + continue; + } +#ifdef KSH + if (errno == EPIPE) + coproc_write_close(fd); +#endif /* KSH */ + return 1; + } + s += n; + len -= n; + } + if (flags & PO_COPROC) + restore_pipe(opipe); + } + + 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; + int fset = 0, fclr = 0; + int thing = 0, func = 0, local = 0; + const char *options = "L#R#UZ#fi#lrtux"; /* see comment below */ + char *fieldstr, *basestr; + int field, base; + int optc, 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 seperate 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 */ + 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 */ + /* 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); + fclr &= ~fset; /* set wins */ + if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { + fset |= RJUST; + fclr &= ~RJUST; + } + if (fset & LCASEV) /* LCASEV has priority */ + fclr |= UCASEV_AL; + else if (fset & UCASEV_AL) + fclr |= LCASEV; + if (fset & LJUST) /* LJUST has priority */ + fclr |= RJUST; + else if (fset & RJUST) + fclr |= LJUST; + if ((fset | fclr) & INTEGER) { + if (!(fset | fclr) & INT_U) + fclr |= INT_U; + if (!(fset | fclr) & INT_L) + fclr |= INT_L; + } + fset &= ~fclr; /* in case of something like -LR */ + } + + /* 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, + "function %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, "function %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++); ) + for (; vp; vp = vp->u.array) { + if (!(vp->flag&ISSET)) + continue; + if (flag && (vp->flag & flag) == 0) + 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->field); + if ((vp->flag&RJUST)) + shprintf("-R%d ", vp->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 "); + if (vp->flag&ARRAY) + shprintf("%s[%d]\n", vp->name,vp->index); + else + shprintf("%s\n", vp->name); + } else { + if (pflag) + shprintf("%s ", + (flag & EXPORT) ? "export" : "readonly"); + if (vp->flag&ARRAY) + shprintf("%s[%d]", vp->name, vp->index); + else + shprintf("%s", vp->name); + if (thing == '-') { + 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); + } + } + } + } + return 0; +} + +int +c_alias(wp) + char **wp; +{ + struct table *t = &aliases; + int rv = 0, rflag = 0, tflag, Uflag = 0, xflag = 0; + int optc; + + while ((optc = ksh_getopt(wp, &builtin_opt, "drtUx")) != EOF) + switch (optc) { + case 'd': + t = &homedirs; + 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; + + 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)) { + shprintf("%s=", ap->name); + 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)) { + shprintf("%s=", ap->name); + 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) : val; + if (newval) { + ap->val.s = str_save(newval, APERM); + ap->flag |= ALLOC|ISSET; + } else + ap->flag &= ~ISSET; + } + ap->flag |= DEFINED|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; +} + +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, TRUE)) { + rv = 2; /* distinguish error from zero result */ + break; + } else + rv = val == 0; + return rv; +} + +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))) { + 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))) { + bi_errorf("bad signal `%s'", + builtin_opt.optarg); + return 1; + } + 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); + } + 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; +} + +static Getopt user_opt; /* parsing state for getopts builtin command */ +static int getopts_noset; /* stop OPTIND assign from resetting state */ + +void +getopts_reset(val) + int val; +{ + if (!getopts_noset && val >= 1) { + ksh_getopt_reset(&user_opt, + GF_NONAME | (Flag(FPOSIX) ? 0 : GF_PLUSOPT)); + user_opt.optind = val; + } +} + +int +c_getopts(wp) + char **wp; +{ + int argc; + const char *options; + const char *var; + int optc; + char buf[3]; + struct tbl *vq; + + 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'; + } + vq = global(var); + if (vq->flag & RDONLY) + bi_errorf("%s is readonly", var); + if (Flag(FEXPORT)) + typeset(var, EXPORT, 0, 0, 0); + setstr(vq, buf); + + getopts_noset = 1; + setint(global("OPTIND"), (long) user_opt.optind); + getopts_noset = 0; + + if (user_opt.optarg == (char *) 0) + unset(global("OPTARG"), 0); + else + setstr(global("OPTARG"), user_opt.optarg); + + if (optc < 0) + return 1; + + return 0; +} + +#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}, + {"let", c_let}, + {"print", c_print}, + {"pwd", c_pwd}, + {"*=readonly", c_typeset}, + {"=typeset", c_typeset}, + {"+unalias", c_unalias}, + {"whence", c_whence}, +#ifdef JOBS + {"+bg", c_fgbg}, + {"+fg", c_fgbg}, +#endif +#ifdef EMACS + {"bind", c_bind}, +#endif + {NULL, NULL} +}; diff --git a/bin/pdksh/c_sh.c b/bin/pdksh/c_sh.c new file mode 100644 index 00000000000..7fd2a645b12 --- /dev/null +++ b/bin/pdksh/c_sh.c @@ -0,0 +1,751 @@ +/* $OpenBSD: c_sh.c,v 1.1 1996/08/14 06:19:10 downsj 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; + + if (wp[1]) { + evaluate(wp[1], &val, FALSE); + n = val; + } else + n = 1; + if (n < 0) { + bi_errorf("%s: bad number", wp[1]); + 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; + + if ((cp = wp[1]) == NULL) + return 0; + file = search(cp, path, R_OK); + if (file == NULL) { + bi_errorf("%s: not found", cp); + return 1; + } + + /* Set positional parameters? */ + if (wp[2]) { + argv = ++wp; + argv[0] = e->loc->argv[0]; /* preserve $0 */ + for (argc = -1; *wp++; 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 = get_coproc_fd(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 (Flag(FTALKING)) { + /* at&t says it prints prompt on fd if its open + * for writing and is a tty, but it doesn't do it + */ + 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. + */ + 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) && 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); + 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); + setstr(vp, Xstring(cs, cp)); + } + + 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 */ + 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; + + s = pushs(SWORDS, ATEMP); + s->u.strv = wp+1; + 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; + } + + s = (gettrap(*wp) == NULL) ? *wp++ : NULL; /* get command */ + if (s != NULL && s[0] == '-' && s[1] == '\0') + s = NULL; + + /* set/clear traps */ + while (*wp != NULL) { + p = gettrap(*wp++); + 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; + + if (wp[1] != NULL && !getn(wp[1], &exstat)) { + exstat = 1; + warningf(TRUE, "%s: bad number", wp[1]); + } + 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; + + if (!wp[1]) + n = 1; + else if (!bi_getn(wp[1], &n)) + return 1; + quit = n; + if (quit <= 0) { + /* at&t ksh does this for non-interactive shells only - weird */ + bi_errorf("bad option `%s'", wp[1]); + 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; + + 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&RDONLY)) { + bi_errorf("%s is read only", vp->name); + return 1; + } + unset(vp, strchr(id, '[') ? 1 : 0); + } else /* unset function */ + define(id, (struct op *)NULL); + return 0; +} + +int +c_times(wp) + char **wp; +{ + struct tms all; + + (void) ksh_times(&all); + shprintf("Shell: %8s user ", clocktos(all.tms_utime)); + shprintf("%8s system\n", clocktos(all.tms_stime)); + shprintf("Kids: %8s user ", clocktos(all.tms_cutime)); + shprintf("%8s 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; +{ + int rv; + struct tms t0, t1; + clock_t t0t, t1t; + extern clock_t j_usrtime, j_systime; /* computed by j_wait */ + + j_usrtime = j_systime = 0; + t0t = ksh_times(&t0); + rv = execute(t->left, f); + t1t = ksh_times(&t1); + + shf_fprintf(shl_out, "%8s real ", clocktos(t1t - t0t)); + shf_fprintf(shl_out, "%8s user ", + clocktos(t1.tms_utime - t0.tms_utime + j_usrtime)); + shf_fprintf(shl_out, "%8s system ", + clocktos(t1.tms_stime - t0.tms_stime + j_systime)); + shf_fprintf(shl_out, newline); + + return rv; +} + +static char * +clocktos(t) + clock_t t; +{ + static char temp[20]; + register int i; + register char *cp = temp + sizeof(temp); + + if (CLK_TCK != 100) /* convert to 1/100'ths */ + t = (t < 1000000000/CLK_TCK) ? + (t * 100) / CLK_TCK : (t / CLK_TCK) * 100; + + *--cp = '\0'; + *--cp = 's'; + 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]); + /* keep anything > 2 private */ + if (i > 2 && e->savefd[i]) + fd_clexec(i); + } + 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/bin/pdksh/c_test.c b/bin/pdksh/c_test.c new file mode 100644 index 00000000000..7ac8c03a007 --- /dev/null +++ b/bin/pdksh/c_test.c @@ -0,0 +1,613 @@ +/* $OpenBSD: c_test.c,v 1.1 1996/08/14 06:19:10 downsj 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 ::= <any thing> +*/ + +#define T_ERR_EXIT 2 /* POSIX says > 1 for errors */ + +struct t_op { + char op_text[4]; + Test_op op_num; +}; +static const struct t_op u_ops [] = { + {"-a", TO_FILAXST }, + {"-b", TO_FILBDEV }, + {"-c", TO_FILCDEV }, + {"-d", TO_FILID }, + {"-e", TO_FILEXST }, + {"-f", TO_FILREG }, + {"-G", TO_FILGID }, + {"-g", TO_FILSETG }, + {"-h", TO_FILSYM }, + {"-H", TO_FILCDF }, + {"-k", TO_FILSTCK }, + {"-L", TO_FILSYM }, + {"-n", TO_STNZE }, + {"-O", TO_FILUID }, + {"-o", TO_OPTION }, + {"-p", TO_FILFIFO }, + {"-r", TO_FILRD }, + {"-s", TO_FILGZ }, + {"-S", TO_FILSOCK }, + {"-t", TO_FILTT }, + {"-u", TO_FILSETU }, + {"-w", TO_FILWR }, + {"-x", TO_FILEX }, + {"-z", TO_STZER }, + {"", TO_NONOP } + }; +static const struct t_op b_ops [] = { + {"=", TO_STEQL }, + {"!=", TO_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 ommited 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); + 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 + res = isatty(opnd1 ? res : 0); + return res; + case TO_FILUID: /* -O */ + return test_stat(opnd1, &b1) == 0 && b1.st_uid == geteuid(); + 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, TRUE) + || !evaluate(opnd2, &v2, TRUE)) + { + /* 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 */ + return stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 + && b1.st_mtime > b2.st_mtime; + case TO_FILOT: /* -ot */ + return stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 + && b1.st_mtime < b2.st_mtime; + 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); +} + +/* Another nasty kludge to handle Korn's bizarre /dev/fd hack */ +static int +test_eaccess(path, mode) + const char *path; + int mode; +{ +#if !defined(HAVE_DEV_FD) + int fd; + + 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 */ + + return eaccess(path, mode); +} + +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/bin/pdksh/c_test.h b/bin/pdksh/c_test.h new file mode 100644 index 00000000000..0caf40b3dfc --- /dev/null +++ b/bin/pdksh/c_test.h @@ -0,0 +1,55 @@ +/* $OpenBSD: c_test.h,v 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/bin/pdksh/c_ulimit.c b/bin/pdksh/c_ulimit.c new file mode 100644 index 00000000000..040eaccb409 --- /dev/null +++ b/bin/pdksh/c_ulimit.c @@ -0,0 +1,257 @@ +/* $OpenBSD: c_ulimit.c,v 1.1 1996/08/14 06:19:10 downsj 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 <sys/resource.h> +#endif /* HAVE_SYS_RESOURCE_H */ +#ifdef HAVE_ULIMIT_H +# include <ulimit.h> +#else /* HAVE_ULIMIT_H */ +# ifdef HAVE_ULIMIT +extern long ulimit(); +# endif /* HAVE_ULIMIT */ +#endif /* HAVE_ULIMIT_H */ + +#define SOFT 0x1 +#define HARD 0x2 + +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) { + char *p = *wp; + + if (all || wp[1]) { + bi_errorf("too many arguments"); + return 1; + } +#ifdef RLIM_INFINITY + if (strcmp(p, "unlimited") == 0) + val = RLIM_INFINITY; + else +#endif /* RLIM_INFINITY */ + { + long rval; + + if (!evaluate(p, &rval, TRUE)) + 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) { + 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/bin/pdksh/conf-end.h b/bin/pdksh/conf-end.h new file mode 100644 index 00000000000..f7d076d4598 --- /dev/null +++ b/bin/pdksh/conf-end.h @@ -0,0 +1,55 @@ +/* $OpenBSD: conf-end.h,v 1.1 1996/08/14 06:19:12 downsj Exp $ */ + +/* + * End of configuration stuff for PD ksh. + */ + +#if defined(EMACS) || defined(VI) +# define EDIT +#else +# undef EDIT +#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/bin/pdksh/config.h b/bin/pdksh/config.h new file mode 100644 index 00000000000..801064fe978 --- /dev/null +++ b/bin/pdksh/config.h @@ -0,0 +1,351 @@ +/* $OpenBSD: config.h,v 1.1 1996/08/14 06:19:12 downsj 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 <sys/types.h> 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 <sys/wait.h> that is POSIX.1 compatible. */ +#define HAVE_SYS_WAIT_H 1 + +/* Define if you have <unistd.h>. */ +#define HAVE_UNISTD_H 1 + +/* Define if on MINIX. */ +/* #undef _MINIX */ + +/* Define to `int' if <sys/types.h> doesn't define. */ +/* #undef mode_t */ + +/* Define to `long' if <sys/types.h> doesn't define. */ +/* #undef off_t */ + +/* Define to `int' if <sys/types.h> 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 <sys/stat.h> do not work properly. */ +/* #undef STAT_MACROS_BROKEN */ + +/* Define if `sys_siglist' is declared by <signal.h>. */ +#define SYS_SIGLIST_DECLARED 1 + +/* Define if you can safely include both <sys/time.h> and <time.h>. */ +#define TIME_WITH_SYS_TIME 1 + +/* Define to `int' if <sys/types.h> 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 <sys/types.h> 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 <time.h> */ +#define TIME_DECLARED 1 + +/* Define to `unsigned' if <signal.h> 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 <errno.h> */ +#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 <termios.h> 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 <termio.h> 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 compatiable <sys/wait.h> */ +#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:/usr/ucb" + +/* 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) */ +/* #undef 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 getgroups function. */ +/* #undef HAVE_GETGROUPS */ + +/* 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 wait3 function. */ +#define HAVE_WAIT3 1 + +/* Define if you have the waitpid function. */ +#define HAVE_WAITPID 1 + +/* Define if you have the <dirent.h> header file. */ +#define HAVE_DIRENT_H 1 + +/* Define if you have the <fcntl.h> header file. */ +#define HAVE_FCNTL_H 1 + +/* Define if you have the <limits.h> header file. */ +#define HAVE_LIMITS_H 1 + +/* Define if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* Define if you have the <ndir.h> header file. */ +/* #undef HAVE_NDIR_H */ + +/* Define if you have the <paths.h> header file. */ +#define HAVE_PATHS_H 1 + +/* Define if you have the <stddef.h> header file. */ +#define HAVE_STDDEF_H 1 + +/* Define if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define if you have the <sys/dir.h> header file. */ +/* #undef HAVE_SYS_DIR_H */ + +/* Define if you have the <sys/ndir.h> header file. */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define if you have the <sys/param.h> header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define if you have the <sys/resource.h> header file. */ +#define HAVE_SYS_RESOURCE_H 1 + +/* Define if you have the <sys/time.h> header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define if you have the <sys/wait.h> header file. */ +#define HAVE_SYS_WAIT_H 1 + +/* Define if you have the <ulimit.h> header file. */ +/* #undef HAVE_ULIMIT_H */ + +/* Define if you have the <values.h> 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/bin/pdksh/edit.c b/bin/pdksh/edit.c new file mode 100644 index 00000000000..c009ed726d0 --- /dev/null +++ b/bin/pdksh/edit.c @@ -0,0 +1,900 @@ +/* $OpenBSD: edit.c,v 1.1 1996/08/14 06:19:10 downsj 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 <sys/stream.h> /* needed for <sys/ptem.h> */ +# include <sys/ptem.h> /* needed for struct winsize */ +#endif /* OS_SCO */ +#include <ctype.h> +#include "ksh_stat.h" + +static char vdisable_c; + + +/* Called from main */ +void +x_init() +{ + /* set to -1 to force initial binding */ + edchars.erase = edchars.kill = edchars.intr = edchars.quit + = edchars.eof = -1; + /* default value for deficient systems */ + edchars.werase = 027; /* ^W */ +#ifdef TIOCGWINSZ + { + struct winsize ws; + + if (ioctl(tty_fd, TIOCGWINSZ, &ws) >= 0) { + struct tbl *vp; + + if (ws.ws_col) { + x_cols = ws.ws_col < MIN_COLS ? MIN_COLS + : ws.ws_col; + + if ((vp = typeset("COLUMNS", EXPORT, 0, 0, 0))) + setint(vp, (long) ws.ws_col); + } + if (ws.ws_row + && (vp = typeset("LINES", EXPORT, 0, 0, 0))) + setint(vp, (long) ws.ws_row); + } + } +#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 */ +} + +/* + * read an edited command line + */ +int +x_read(buf, len) + char *buf; + size_t len; +{ + int i; + + x_mode(TRUE); +#ifdef EMACS + if (Flag(FEMACS) || Flag(FGMACS)) + i = x_emacs(buf, len); + else +#endif +#ifdef VI + if (Flag(FVI)) + i = x_vi(buf, len); + else +#endif + i = -1; /* internal error */ + x_mode(FALSE); + 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); + + 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; + + while (*cp) { + if (*cp == '\n' || *cp == '\r') { + count = 0; + cp++; + sp = cp; + } else if (*cp == '\t') { + count = (count | 7) + 1; + cp++; + } else if (*cp == '\b') { + if (count > 0) + count--; + cp++; + } +#if 1 + else + cp++, count++; +#else + else if (*cp++ != '!') + count++; + else if (*cp == '!') { + cp++; + count++; + } else { + register int i = source->line + 1; + + do + count++; + while ((i /= 10) > 0); + } +#endif /* 1 */ + } + 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; + } +} + +/* ------------------------------------------------------------------------- */ +/* 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)); + +/* XXX not used... */ +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; +} + +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 omitt 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_menu(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 + */ +/* XXX static? */ +int +x_file_glob(flags, str, slen, wordsp) + int flags; + const char *str; + int slen; + char ***wordsp; +{ + char *toglob; + char **words; + int nwords; + XPtrV w; + struct source *s, *sold; + + if (slen <= 0) + return 0; + + toglob = add_glob(str, slen); + + /* + * Convert "foo*" (toglob) to an array of strings (words) + */ + sold = source; + s = pushs(SWSTR, ATEMP); + s->start = s->str = toglob; + source = s; + if (yylex(ONEWORD) != 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; +} + +/* XXX 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) !isspace(c) + +/* XXX 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; + + if (pos == buflen) + pos--; + if (pos < 0 || pos >= buflen) { + *startp = pos; + *is_commandp = 0; + return 0; + } + + /* Go backwards 'til we are in a word */ + for (start = pos; start >= 0 && !IS_WORDC(buf[start]); start--) + ; + /* No word found? */ + if (start < 0) + return 0; + /* Keep going backwards to start of word */ + for (; start >= 0 && IS_WORDC(buf[start]); start--) + ; + start++; + + /* Go forwards to end of word */ + for (end = start; end < buflen && IS_WORDC(buf[end]); 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 (len == 0) + return 0; + + if (!(flags & XCF_COMMAND)) + is_command = 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; + + 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 ('$'), 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; + } + if (!*s) { + 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 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, 0); + newsize = XPsize(*wp); + + /* Check that each match is executable... */ + words = (char **) XPptrv(*wp); + for (i = j = oldsize; i < newsize; i++) { + if (search_access(words[i], X_OK) >= 0) { + 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); +} + +#endif /* EDIT */ diff --git a/bin/pdksh/edit.h b/bin/pdksh/edit.h new file mode 100644 index 00000000000..c60b4121666 --- /dev/null +++ b/bin/pdksh/edit.h @@ -0,0 +1,89 @@ +/* $OpenBSD: edit.h,v 1.1 1996/08/14 06:19:10 downsj 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_complete_word ARGS((const char *str, int slen, int is_command, int *multiple, char **ret)); +void x_print_expansions ARGS((int nwords, char *const *words, int is_command)); +int x_locate_word ARGS((const char *buf, int buflen, int pos, int *startp, 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_file_glob ARGS((int flags, const char *str, int slen, char ***wordsp)); +int x_command_glob ARGS((int flags, const char *str, int slen, char ***wordsp)); +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)); +/* 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/bin/pdksh/emacs-gen.sh b/bin/pdksh/emacs-gen.sh new file mode 100644 index 00000000000..97449de8fa4 --- /dev/null +++ b/bin/pdksh/emacs-gen.sh @@ -0,0 +1,44 @@ +#!/bin/sh +# $OpenBSD: emacs-gen.sh,v 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/bin/pdksh/emacs.c b/bin/pdksh/emacs.c new file mode 100644 index 00000000000..a682f32fb5b --- /dev/null +++ b/bin/pdksh/emacs.c @@ -0,0 +1,2152 @@ +/* $OpenBSD: emacs.c,v 1.1 1996/08/14 06:19:10 downsj 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 <ctype.h> +#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 */ + + +/* 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_* */ + 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_TABSZ 256 /* size of keydef tables etc */ +# define X_NTABS 4 /* normal, meta1, meta2, meta3 */ +static int x_prefix3 = 0xE0; +#else /* OS2 */ +# define CHARMASK 0x7F /* 7-bit ASCII character mask */ +# define X_TABSZ 128 /* size of keydef tables etc */ +# define X_NTABS 3 /* normal, meta1, meta2 */ +#endif /* OS2 */ + +/* 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 x_col; +static int x_displen; +static int x_arg; /* general purpose arg */ +static int x_arg_defaulted;/* x_arg not explicitly set; defaulted to 1 */ + +static int xlp_valid; +/* end from 4.9 edit.h } */ + +static int x_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 */ +#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)); +#ifdef DEBUG +static int x_debug_info ARGS((void)); +#endif /* DEBUG */ +static void x_e_puts ARGS((const char *s)); +static int x_fold_case ARGS((int c)); +static char *x_lastcp ARGS((void)); +static void do_complete ARGS((int flags, Comp_type type)); + + +/* 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_<name> where <name> 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 }, +#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('^') }, +#if defined(BRL) && defined(TIOCSTI) + { XFUNC_stuff, 0, CTRL('T') }, +#else + { XFUNC_transpose, 0, CTRL('T') }, +#endif + { XFUNC_complete, 1, CTRL('[') }, + { 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' }, +#else /* 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_prev_com, 2, 'A' }, + { XFUNC_next_com, 2, 'B' }, + { XFUNC_mv_forw, 2, 'C' }, + { XFUNC_mv_back, 2, 'D' }, +#endif /* OS2 */ +}; + +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; + + x_col = promptlen(prompt, &p); + prompt_skip = p - prompt; + x_adj_ok = 1; + x_displen = x_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; + + 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; +} + +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 = x_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 i; + i = x_size(c); + while (i--) + x_e_putc('\b'); +} + +static int +x_size_str(cp) + register char *cp; +{ + register 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 (c < ' ' || c == 0x7F) /* ASCII 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 (c < ' ' || c == 0x7F) { /* ASCII */ + 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; + +} + +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 = x_cols - 2 - x_col; + xlp_valid = FALSE; + cp = x_lastcp(); + x_zots(xbp); + if (xbp != xbuf || xep > xlp) + limit = x_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 < (x_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 positiion: + * 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; + + if (c < ' ' || c == 0x7F) { /* ASCII */ + *p++ = '^'; + *p++ = (c == 0x7F) ? '?' : (c | 0x40); +#ifdef OS2 + } else if (c == 0xE0) { + *p++ = '^'; + *p++ = '0'; +#endif /* OS2 */ + } 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; + + return 0; +} + +void +x_init_emacs() +{ + register int i, j; + + 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[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; +} + +void +x_emacs_keys(ec) + X_chars *ec; +{ + x_tab[0][ec->erase] = XFUNC_del_back; + x_tab[0][ec->kill] = XFUNC_del_line; + x_tab[0][ec->werase] = XFUNC_del_bword; + x_tab[0][ec->intr] = XFUNC_abort; + x_tab[0][ec->quit] = XFUNC_noop; + x_tab[1][ec->erase] = XFUNC_del_bword; +} + +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 = 0; + int start, end; + int is_command; + int do_glob = 1; + Comp_type t = type; + char *comp_word = (char *) 0; + + if (type == CT_COMPLIST) { + do_glob = 0; + /* decide what we will do */ + nwords = x_cf_glob(flags, + xbuf, xep - xbuf, xcp - xbuf, + &start, &end, &words, &is_command); + if (nwords > 0) { + if (nwords > 1) { + int len = x_longest_prefix(nwords, words); + + t = CT_LIST; + /* Do completion if prefix matches original + * prefix (ie, no globbing chars), otherwise + * don't bother + */ + if (strncmp(words[0], xbuf + start, end - start) + == 0) + comp_word = str_nsave(words[0], len, + ATEMP); + else + type = CT_LIST; + /* Redo globing to show full paths if this + * is a command. + */ + if (is_command) { + do_glob = 1; + x_free_words(nwords, words); + } + } else + type = t = CT_COMPLETE; + } + } + if (do_glob) + nwords = x_cf_glob(flags | (t == CT_LIST ? XCF_FULLPATH : 0), + xbuf, xep - xbuf, xcp - xbuf, + &start, &end, &words, &is_command); + if (nwords == 0) { + x_e_putc(BEL); + return; + } + switch (type) { + case CT_LIST: + x_print_expansions(nwords, words, is_command); + x_redraw(0); + break; + + case CT_COMPLIST: + /* Only get here if nwords > 1 && comp_word is set */ + { + int olen = end - start; + int nlen = strlen(comp_word); + + x_print_expansions(nwords, words, is_command); + xcp = xbuf + end; + x_do_ins(comp_word + olen, nlen - olen); + x_redraw(0); + } + break; + + case CT_COMPLETE: + { + int nlen = x_longest_prefix(nwords, words); + + if (nlen > 0) { + x_goto(xbuf + start); + x_delete(end - start, FALSE); + words[0][nlen] = '\0'; + x_ins(words[0]); + /* If single match is not a directory, add a + * space to the end... + */ + if (nwords == 1 + && !ISDIRSEP(words[0][nlen - 1])) + x_ins(space); + } else + x_e_putc(BEL); + } + break; + } +} + +/* 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 > x_cols / 2 + */ + if ((xbp = xcp - (x_displen / 2)) < xbuf) + xbp = xbuf; + xlp_valid = FALSE; + x_redraw(x_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 < x_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 >= (x_cols - 2))) + { + x_adjust(); + } +} + +#ifdef DEBUG +static int +x_debug_info() +{ + x_flush(); + printf("\nksh debug:\n"); + printf("\tx_col == %d,\t\tx_cols == %d,\tx_displen == %d\n", + x_col, x_cols, x_displen); + printf("\txcp == 0x%lx,\txep == 0x%lx\n", (long) xcp, (long) xep); + printf("\txbp == 0x%lx,\txbuf == 0x%lx\n", (long) xbp, (long) xbuf); + printf("\txlp == 0x%lx\n", (long) xlp); + printf("\txlp == 0x%lx\n", (long) x_lastcp()); + printf(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; +} + + +/* 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; + char **hp; + + hp = x_histp-1; + if (hp < history || hp > histptr) + { + x_e_putc(BEL); + return KSTD; + } + cp = *hp; + 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/Capitial 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/bin/pdksh/eval.c b/bin/pdksh/eval.c new file mode 100644 index 00000000000..da952584d96 --- /dev/null +++ b/bin/pdksh/eval.c @@ -0,0 +1,1361 @@ +/* $OpenBSD: eval.c,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* + * Expansion - quoting, separation, substitution, globbing + */ + +#include "sh.h" +#include <pwd.h> +#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)); +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 spliting 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), + FALSE); + sp = strchr(sp, 0) + 1; + for (p = str_val(&v); *p; ) { + Xcheck(ds, dp); + *dp++ = *p++; + } + } + continue; + case OSUBST: /* ${{#}var{:}[=+-?#%]word} */ + /* format is: + * OSUBST plain-variable-part \0 + * compiled-word-part CSUBST + * This is were all syntax checking gets done... + */ + { + char *varname = sp; + int stype; + + sp = strchr(sp, '\0') + 1; /* skip variable */ + type = varsub(&x, varname, sp, &stype); + if (type < 0) { + char endc; + char *str, *end; + + end = (char *) wdscan(sp, CSUBST); + 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 += 2; + /* :[-+=?] or double [#%] */ + if (stype & 0x80) + sp += 2; + } + switch (stype & 0x7f) { + case '#': + case '%': + /* ! DOBLANK,DOBRACE_,DOTILDE */ + f = DOPAT | (f&DONTRUNCOMMAND) + | DOTEMP_; + quote = 0; + 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 !defined(__hppa) || __GNUC__ != 2 /* gcc 2.3.3 on hp-pa dies on this - ifdef goes away as soon as I get a new version of gcc.. */ + if (!(x.var->flag & INTEGER)) + f |= DOASNTILDE|DOTILDE; + f |= DOTEMP_; +#else + f |= DOTEMP_|DOASNTILDE|DOTILDE; +#endif + /* 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 */ + 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 '%': + 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 '=': + if (st->var->flag & RDONLY) + /* XXX POSIX says this is only + * fatal for special builtins + */ + errorf("%s: is read only", + st->var->name); + /* 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. + */ + setstr(st->var, debunk( + (char *) alloc(strlen(dp) + 1, + ATEMP), dp)); + 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 seperator (|) */ + 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) + Expand *xp; + char *sp; + char *word; + int *stypep; +{ + int c; + int state; /* next state: XBASE, XARG, XSUB, XNULLSUB */ + int stype; /* substitution type */ + 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]==']') { + c = 0; + vp = global(arrayname(sp)); + if (vp->flag & (ISSET|ARRAY)) + zero_ok = 1; + for (; vp; vp = vp->u.array) + if (vp->flag&ISSET) + c = vp->index+1; + } 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: unset variable", 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 == CHAR ? word[1] : 0; + if (c == ':') { + stype = 0x80; + c = word[2] == CHAR ? word[3] : 0; + } + if (ctype(c, C_SUBOP1)) + stype |= c; + else if (stype) /* :, :# or :% is not ok */ + return -1; + else if (ctype(c, C_SUBOP2)) { + stype = c; + if (word[2] == CHAR && c == word[3]) + stype |= 0x80; + } + if (!stype && *word != CSUBST) + return -1; + *stypep = stype; + + c = sp[0]; + if (c == '*' || c == '@') { + switch (stype & 0x7f) { + case '=': /* can't assign to a vector */ + case '%': /* can't trim a vector */ + 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 */ + 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: unset variable", 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 && /* $(<file) */ + *t->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 begining */ + 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 begining */ + 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 existance 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: *+?@! */ + *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/bin/pdksh/exec.c b/bin/pdksh/exec.c new file mode 100644 index 00000000000..7563b2bb9d0 --- /dev/null +++ b/bin/pdksh/exec.c @@ -0,0 +1,1578 @@ +/* $OpenBSD: exec.c,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* + * execute command tree + */ + +#include "sh.h" +#include "c_test.h" +#include <ctype.h> +#include "ksh_stat.h" + +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)); +static int herein ARGS((char *hname, int sub)); +#ifdef KSH +static char *do_selectargs ARGS((char **ap)); +#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)); +#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; + + if ((flags&XFORK) && !(flags&XEXEC) && t->type != TPIPE) + return exchild(t, flags, -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 by c_set(). + */ + subst_exstat = 0; + + /* POSIX says expand command words first, then redirections, + * and assignments last.. + */ + ap = eval(t->args, t->evalflags | DOBLANK | DOGLOB | DOTILDE); + if (Flag(FXTRACE) && ap[0]) { + shf_fprintf(shl_out, "%s", + substitute(str_val(global("PS4")), 0)); + 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); + } + + 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) < 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: + { + if (coproc.job && coproc.write >= 0) + errorf("coprocess already exists"); + /* Can we re-use the existing co-process pipe? */ + cleanup_coproc(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]; + + 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 */ + } + + /* exchild() closes coproc.* in child after fork */ + 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, 0); + 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: +#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) { + struct tbl *vq; + + while (*ap != NULL) { + vq = global(t->str); + if (vq->flag & RDONLY) + errorf("%s is read only", t->str); + setstr(vq, *ap++); + rv = execute(t->left, flags & XERROK); + } + } +#ifdef KSH + else { /* TSELECT */ + struct tbl *vq; + + for (;;) { + if ((cp = do_selectargs(ap)) == (char *) 0) { + rv = 1; + break; + } + vq = global(t->str); + if (vq->flag & RDONLY) + errorf("%s is read only", t->str); + setstr(vq, cp); + 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->left); + break; + + case TTIME: + rv = timex(t, flags); + 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(); + ksh_execve(t->str, t->args, ap); + if (errno == ENOEXEC) + scriptexec(t, ap); + else + errorf("%s: %s", s, strerror(errno)); + } + Break: + exstat = rv; + + quitenv(); /* restores IO */ + if ((flags&XEXEC)) + exit(rv); /* 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; + 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; + + /* 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 (*(lastp = ap)) { + while (*++lastp) + ; + setstr(typeset("_", LOCAL, 0, 0, 0), *--lastp); + } + + /* 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 loose 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)); + } + /* todo: POSIX says assignments preceding a function are kept, at&t + * ksh does not do this + */ + if (keepasn_ok && (!ap[0] || (tp && tp->flag & KEEPASN))) + type_flags = 0; + else { + /* create new variable/function block */ + newblock(); + 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", + substitute(str_val(global("PS4")), 0)); + 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 int old_inuse; + const char *volatile old_kshname; + + if (!(tp->flag & ISSET)) { + struct tbl *ftp; + + if (!tp->u.fpath) { + /* XXX: exit code 126 vs 127 */ + 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 = 126; + 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; + } + + /* posix says $0 remains unchanged, at&t ksh changes it */ + old_kshname = kshname; + if (!Flag(FPOSIX)) + kshname = ap[0]; + e->loc->argv = ap; + for (i = 0; *ap++ != NULL; i++) + ; + e->loc->argc = i - 1; + 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. Unfortunately, the table entry is never re-used. + */ + 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)) { + /* + * mlj addition: + * + * If you specify a full path to a file + * (or type the name of a file in .) which + * doesn't have execute priv's, it used to + * just say "not found". Kind of annoying, + * particularly if you just wrote a script + * but forgot to say chmod 755 script. + * + * This should probably be done in eaccess(), + * but it works here (at least I haven't noticed + * changing errno here breaking something + * else). + * + * So, we assume that if the file exists, it + * doesn't have execute privs; else, it really + * is not found. + */ + if (access(cp, F_OK) < 0) + warningf(TRUE, "%s: not found", cp); + else + warningf(TRUE, "%s: cannot execute", cp); + /* XXX posix says 126 if in path and cannot execute */ + rv = 127; + break; + } + + /* set $_ to program's full path */ + setstr(typeset("_", LOCAL|EXPORT, 0, 0, 0), tp->val.s); + + 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); + 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 && search_access(a0, X_OK)) + a0 = a2; +# 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 separater. + */ + char *p = shell; + + shell = str_val(global("EXECSHELL")); + if (shell && *shell) + shell = search(shell, path, X_OK); + 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); + + /* 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 && (tp->flag & DEFINED)) + 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 + */ +int +define(name, t) + const char *name; + struct op *t; +{ + register struct tbl *tp; + + tp = findfunc(name, hash(name), TRUE); + + /* 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; + return define(name, t); + } + + if (tp->flag & ALLOC) { + tp->flag &= ~(ISSET|ALLOC); + tfree(tp->val.t, tp->areap); + } + + if (t == NULL) { /* undefine */ + tdelete(tp); + return 0; + } + + tp->val.t = tcopy(t, tp->areap); + tp->flag |= (ISSET|ALLOC); + + return 0; +} + +/* + * add builtin + */ +void +builtin(name, func) + const char *name; + int (*func) ARGS((char **)); +{ + register struct tbl *tp; + int 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; + else + tp->u.fpath = search(name, fpath, R_OK); + } + } + 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); + 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)) != (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) + const char *path; + int mode; +{ +#ifndef OS2 + int ret = eaccess(path, mode); + struct stat statb; + + /* if executable pipes come along, this will have to change */ + if (ret == 0 && (mode == X_OK) + && (stat(path, &statb) < 0 || !S_ISREG(statb.st_mode) + /* This 'cause access() says root can execute everything */ + || !(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) + ret = -1; + 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); + } + /* 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) == 0) + return 0; + *tp = '\0'; + } + return -1; +#endif /* !OS2 */ +} + +#ifdef OS2 +static int +search_access1(path, mode) + const char *path; + int mode; +{ + int ret = eaccess(path, mode); + struct stat statb; + + /* if executable pipes come along, this will have to change */ + if (ret == 0 && (mode == X_OK || mode == R_OK) + && (stat(path, &statb) < 0 || !S_ISREG(statb.st_mode))) + ret = -1; + return ret; +} +#endif /* OS2 */ + +/* + * search for command with PATH + */ +char * +search(name, path, mode) + const char *name; + const char *path; + int mode; /* R_OK or X_OK */ +{ + const char *sp, *p; + char *xp; + XString xs; + int namelen; + +#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) >= 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) == 0) + return Xstring(xs, xp); /* not Xclose() - xp may be wrong */ +#else /* OS2 */ + if (ksh_strchr_dirsep(name)) { + if (search_access(name, mode) == 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) == 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) + register struct ioword *iop; +{ + 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) ? 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", + substitute(str_val(global("PS4")), 0), + 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; + 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(cp, 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) + /* c_exec() assumes e->savefd[fd] set for any redirections. + * Ask savefd() not to close iop->unit - 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) { +#ifdef KSH + if (iop->flag & IORDUP) /* possible <&p */ + /* Ensure other side of read pipe is + * closed so EOF can be read properly + */ + coproc_readw_close(u); + else /* possible >&p */ + /* If co-process input is duped, + * close shell's copy + */ + coproc_write_close(u); +#else /* KSH */ + ; +#endif /* KSH */ + } else + close(u); + } + if (u == 2) /* Clear any write errors */ + shf_reopen(2, SHF_WR, shl_out); + return 0; +} + +/* + * open here document temp file. + * if unquoted here, expand here temp file into second temp file. + */ +static int +herein(hname, sub) + char *hname; + int sub; +{ + int fd; + + /* ksh -c 'cat << EOF' can cause this... */ + if (hname == (char *) 0) { + warningf(TRUE, "here document missing"); + return -2; /* special to iosetup(): don't print error */ + } + if (sub) { + char *cp; + struct source *s, *volatile osource = source; + struct temp *h; + struct shf *volatile shf; + int i; + + /* must be before newenv() 'cause shf uses ATEMP */ + shf = shf_open(hname, O_RDONLY, 0, SHF_MAPHI|SHF_CLEXEC); + if (shf == NULL) + return -1; + newenv(E_ERRH); + i = ksh_sigsetjmp(e->jbuf, 0); + if (i) { + if (shf) + shf_close(shf); + source = osource; + quitenv(); /* after shf_close() due to alloc */ + return -2; /* special to iosetup(): don't print error */ + } + /* set up yylex input from here file */ + s = pushs(SFILE, ATEMP); + s->u.shf = shf; + source = s; + if (yylex(ONEWORD) != LWORD) + internal_errorf(1, "herein: yylex"); + shf_close(shf); + shf = (struct shf *) 0; + cp = evalstr(yylval.cp, 0); + + /* write expanded input to another temp file */ + h = maketemp(ATEMP); + h->next = e->temps; e->temps = h; + if (!(shf = h->shf) || (fd = open(h->name, O_RDONLY, 0)) < 0) + /* shf closeed by error handler */ + errorf("%s: %s", h->name, strerror(errno)); + shf_puts(cp, shf); + if (shf_close(shf) == EOF) { + close(fd); + shf = (struct shf *) 0; + errorf("error writing %s: %s", h->name, + strerror(errno)); + } + shf = (struct shf *) 0; + + quitenv(); + } else { + fd = open(hname, O_RDONLY, 0); + if (fd < 0) + return -1; + } + + return fd; +} + +#ifdef KSH +/* + * ksh special - the select command processing section + * print the args in column form - assuming that we can + */ +static char * +do_selectargs(ap) + register char **ap; +{ + static const char *const read_args[] = { + "read", "-r", "REPLY", (char *) 0 + }; + char *s; + int i, UNINITIALIZED(argct); + + while (1) { + argct = 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")); + while (*s && isspace(*s)) + s++; + if (*s) { + i = atoi(s); + return (i >= 1 && i <= argct) ? ap[i - 1] : null; + } + } +} + +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); + + return n; +} +#endif /* KSH */ +#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/bin/pdksh/expand.h b/bin/pdksh/expand.h new file mode 100644 index 00000000000..d0ab5937c1d --- /dev/null +++ b/bin/pdksh/expand.h @@ -0,0 +1,106 @@ +/* $OpenBSD: expand.h,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* + * Expanding strings + */ + + +#if 0 /* Usage */ + XString xs; + char *xp; + + Xinit(xs, xp, 128, ATEMP); /* allocate initial string */ + while ((c = generate()) { + Xcheck(xs, xp); /* expand string if neccessary */ + Xput(xs, xp, c); /* add character */ + } + return Xclose(xs, xp); /* resize string */ +/* + * NOTE: + * The Xcheck and Xinit macros have a magic + 8 in the lengths. This is + * so that you can put up to 4 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 + 8, (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/bin/pdksh/expr.c b/bin/pdksh/expr.c new file mode 100644 index 00000000000..bd94ca68f27 --- /dev/null +++ b/bin/pdksh/expr.c @@ -0,0 +1,538 @@ +/* $OpenBSD: expr.c,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* + * Korn expression evaluation + */ +/* + * todo: better error handling: if in builtin, should be builtin error, etc. + * todo: add ++ -- + * todo: recursive variable expansion (y=1;x=y; let x) + how to deal with allowing: + i=0 + set -A x 'x[1]' 'x[2]' 'x[3]' 99 + let z=x[i+=1] + echo $z + 99 + and disallowing: + x='y[x]' + let z=x + */ + +#include "sh.h" +#include <ctype.h> + + +/* The order of these enums is constrained by the order of opinfo[] */ +enum token { + /* binary operators */ + O_EQ = 0, 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, + /* 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 LAST_BINOP O_TERN +#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 /* = *= /= %= += -= <<= >>= &= ^= |= */ + }; +#define MAX_PREC P_ASSIGN + +struct opinfo { + char name[4]; + int len; /* name length */ + enum prec prec; /* precidence: 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_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_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 assignments (for ?:) */ + struct tbl *val; /* value from token() */ + Expr_state *volatile prev; /* previous state */ +}; + +enum error_type { ET_UNEXPECTED, ET_BADLIT, ET_BADVAR, ET_STR }; + +static Expr_state *es; + +static void evalerr ARGS((enum error_type type, const char *str)) + GCC_FUNC_ATTR(noreturn); +static struct tbl *evalexpr ARGS((enum prec prec)); +static void token ARGS((void)); +static struct tbl *tempvar ARGS((void)); +static struct tbl *intvar ARGS((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; + int i; + + /* save state to allow recursive calls */ + curstate.expression = curstate.tokp = expr; + curstate.noassign = 0; + curstate.prev = es; + es = &curstate; + + newenv(E_ERRH); + i = ksh_sigsetjmp(e->jbuf, 0); + if (i) { + quitenv(); + es = curstate.prev; + if (i == LAEXPR) { + if (error_ok) + return 0; + errorf(null); + } + unwind(i); + /*NOTREACHED*/ + } + + token(); +#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(evalexpr(MAX_PREC)); + + if (es->tok != END) + evalerr(ET_UNEXPECTED, (char *) 0); + + if (vp->flag & INTEGER) + setint_v(vp, v); + else + setstr(vp, str_val(v)); + + es = curstate.prev; + quitenv(); + + return 1; +} + +static void +evalerr(type, str) + 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_BADVAR: + warningf(TRUE, "%s: value of variable `%s' not a number", + 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(prec) + enum prec prec; +{ + register struct tbl *vl, UNINITIALIZED(*vr), *vasn; + register 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(); + vl = intvar(evalexpr(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(); + vl = evalexpr(MAX_PREC); + if (es->tok != CLOSE_PAREN) + evalerr(ET_STR, "missing )"); + token(); + } else if (op == VAR || op == LIT) { + vl = es->val; + token(); + } else { + evalerr(ET_UNEXPECTED, (char *) 0); + /*NOTREACHED*/ + } + return vl; + } + vl = evalexpr(((int) prec) - 1); + while ((int) (op = es->tok) <= (int) LAST_BINOP && opinfo[(int) op].prec == prec) { + token(); + vasn = vl; + if (op != O_ASN) /* vl may not have a value yet */ + vl = intvar(vl); + if (IS_ASSIGNOP(op)) { + if (vasn->name[0] == '\0') + evalerr(ET_STR, "assignment to non-lvalue"); + else if (vasn->flag & RDONLY) + evalerr(ET_STR, + "assignment to read only variable"); + vr = intvar(evalexpr(P_ASSIGN)); + } else if (op != O_TERN && op != O_LAND && op != O_LOR) + vr = intvar(evalexpr(((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(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(evalexpr(((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(evalexpr(((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(MAX_PREC); + if (!e) + es->noassign--; + if (es->tok != CTERN) + evalerr(ET_STR, "missing :"); + token(); + if (e) + es->noassign++; + vr = evalexpr(MAX_PREC); + if (e) + es->noassign--; + vl = e ? vl : vr; + } + break; + case O_ASN: + 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() +{ + register const char *cp; + register 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++) + ; + if (c == '[') { + int len; + + len = array_ref_len(cp - 1); + if (len == 0) + evalerr(ET_STR, "missing ]"); + cp += len; + } + if (es->noassign) + es->val = tempvar(); + 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(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; +} + +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(vp) + register struct tbl *vp; +{ + register struct tbl *vq; + + /* try to avoid replacing a temp var with another temp var */ + if (vp->name[0] == '\0' + && (vp->flag & (ISSET|INTEGER)) == (ISSET|INTEGER)) + return vp; + + vq = tempvar(); + vq->type = 0; + if (setint_v(vq, vp) == NULL) { + evalerr(ET_BADVAR, vp->name); + /* + if ((vp->flag&ISSET) && vp->val.s && *(vp->val.s)) { + evalerr("bad number"); + } else { + vq->flag |= (ISSET|INTEGER); + vq->type = 10; + vq->val.i = 0; + } + */ + } + return vq; +} diff --git a/bin/pdksh/history.c b/bin/pdksh/history.c new file mode 100644 index 00000000000..0f7d9ab176a --- /dev/null +++ b/bin/pdksh/history.c @@ -0,0 +1,1192 @@ +/* $OpenBSD: history.c,v 1.1 1996/08/14 06:19:11 downsj 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_hist" +# endif /* OS2 */ +# endif + +# else +/* Defines and includes for the complicated case */ + +# include <sys/file.h> +# include <sys/mman.h> + +/* + * 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 postition 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); + tf->next = e->temps; e->temps = tf; + 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; + } + + setstr(local("_", FALSE), tf->name); + + /* 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 unecessary 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_seperate) + const char *cmd; + int nl_seperate; +{ + 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_seperate) + *p++ = '\n'; + memcpy(p, cmd, clen); + p[clen] = '\0'; +} + +/* + * 92-04-25 <sjg@zen> + * 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 + * <command byte><command number(4 bytes)><bytes><null> + */ +# 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 ((int)base == -1 || *base != HMAGIC1 || base[1] != HMAGIC2) { + if ((int)base != -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; + register 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 ((int)base == -1) + 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/bin/pdksh/io.c b/bin/pdksh/io.c new file mode 100644 index 00000000000..492c82e3b99 --- /dev/null +++ b/bin/pdksh/io.c @@ -0,0 +1,473 @@ +/* $OpenBSD: io.c,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* + * shell buffered IO and formatted output + */ + +#include <ctype.h> +#include "sh.h" +#include "ksh_stat.h" + +/* + * formatted output functions + */ + + +/* A shell error occured (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; +{ + 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; + + 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); +} + +/* 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 */ +} + +/* 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 get_coproc_fd(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; +} + +/* 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) { + close(coproc.read); + coproc.read = -1; + if (coproc.readw >= 0) { + close(coproc.readw); + coproc.readw = -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.read >= 0 && fd == coproc.read && coproc.readw >= 0) { + 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 existance of/value of the co-process file descriptor. + * (Used by check_fd() and by c_read/c_print to deal with -p option). + */ +int +get_coproc_fd(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) */ +void +cleanup_coproc(reuse) + int reuse; +{ + coproc.job = (void *) 0; + /* 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) + Area *ap; +{ + static unsigned int inc; + struct temp *tp; + int len; + int fd; + char *path; + const char *tmp; + + tmp = tmpdir ? tmpdir : "/tmp"; + /* The 20 + 20 is a paranoid worst case for pid/inc */ + len = strlen(tmp) + 3 + 20 + 20 + 1; + tp = (struct temp *) alloc(sizeof(struct temp) + len, ap); + tp->name = path = (char *) &tp[1]; + tp->shf = (struct shf *) 0; + while (1) { + /* Note that temp files need to fit 8.3 DOS limits */ + shf_snprintf(path, len, "%s/sh%05u.%03x", + tmp, (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 called: don't know here if + * errorf() or bi_errorf() should be used. + */ + break; + } + tp->next = NULL; + tp->pid = procpid; + return tp; +} diff --git a/bin/pdksh/jobs.c b/bin/pdksh/jobs.c new file mode 100644 index 00000000000..d40a092ac4b --- /dev/null +++ b/bin/pdksh/jobs.c @@ -0,0 +1,1815 @@ +/* $OpenBSD: jobs.c,v 1.1 1996/08/14 06:19:11 downsj 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 */ + +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 TTY_PGRP + TTY_state ttystate; /* saved tty state 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 +static sigset_t sm_default, sm_sigchld; +/* 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 void kill_job ARGS((Job *j)); +static void fill_command ARGS((char *c, int len, struct op *t)); + +/* 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); +#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; + + /* j_change() sets these to SS_RESTORE_DFL if FMONITOR */ + for (i = NELEM(tt_sigs); --i >= 0; ) { + sigtraps[tt_sigs[i]].flags |= TF_SHELL_USES; + 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; + killpg(j->pgrp, SIGHUP); +#ifdef JOBS + if (j->state == PSTOPPED) + 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; + /* the TF_SHELL_USES test is a kludge that lets us know if + * if the signals have been changed by the shell. + */ + 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_CURR|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 orig_flags = flags; + int ischild; + + flags &= ~(XFORK|XPCLOSE|XCCLOSE|XCOPROC); + if (flags & XEXEC) + return execute(t, flags); + +#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 */ + j = last_job; + last_proc->next = p; + last_proc = p; + } else { +#ifdef NEED_PGRP_SYNC + if (j_sync_open) { + closepipe(j_sync_pipe); + j_sync_open = 0; + } + /* 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; + last_job = j; + last_proc = p; + if (flags & XXCOM) + j->flags |= JF_XXCOM; + else if (!(flags & XBGND)) + j->flags |= JF_FG; + put_job(j, PJ_PAST_STOPPED); + } + + fill_command(p->command, sizeof(p->command), 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); + 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 dosync = 0; +# endif /* NEED_PGRP_SYNC */ + + if (j->pgrp == 0) { /* First process */ + j->pgrp = p->pid; + dotty = 1; +# ifdef NEED_PGRP_SYNC + if (j_sync_open) { + close(j_sync_pipe[ischild ? 1 : 0]); + j_sync_pipe[ischild ? 1 : 0] = -1; + dosync = ischild; + } +# endif /* NEED_PGRP_SYNC */ + } + + /* 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 (ischild && j_sync_open) { + if (dosync) { + 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 && (((orig_flags & XPCLOSE) && i != 0) + || ((orig_flags & XCCLOSE) && i == 0))) + close(close_fd); + if (i == 0) { /* child */ +#ifdef JOB_SIGS + sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); +#endif /* JOB_SIGS */ + cleanup_parents_env(); +#ifdef KSH + if (orig_flags & XCOPROC) + cleanup_coproc(FALSE); +#endif /* KSH */ +#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 (!(orig_flags & (XPIPEI | XCOPROC))) { + i = open("/dev/null", 0); + (void) ksh_dup2(i, 0, TRUE); + close(i); + } + } + remove_job(j, "child"); /* in case of `jobs` command */ + nzombie = 0; +#ifdef JOBS + ttypgrp_ok = 0; + Flag(FMONITOR) = 0; +#endif /* JOBS */ + Flag(FTALKING) = 0; + tty_close(); + cleartraps(); + execute(t, flags|XEXEC); /* no return */ + internal_errorf(0, "exchild: execute() returned"); + unwind(LLEAVE); + /* NOTREACHED */ + } + + /* shell (parent) stuff */ + 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) + coproc.job = (void *) j; +#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) + return -1; + bi_errorf("%s: %s", cp, lookup_msgs[ecode]); + } + + /* 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) */ + for (p=j->proc_list; p != (Proc *) 0; p = p->next) + if (kill(p->pid, 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); + } + if (ttypgrp_ok && tcsetpgrp(tty_fd, 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->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; + } +# 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 asynchornous 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) { + closepipe(j_sync_pipe); + j_sync_open = 0; + } +#endif /* NEED_PGRP_SYNC */ +#ifdef JOB_SIGS + if (held_sigchld) { + held_sigchld = 0; + /* Don't call j_sigchild() 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) { + 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; + + trapsig(sig); + +#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_sigchild()). 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) + && coproc.job == (void *) j) + coproc.job = (void *) 0; +#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: + strcpy(buf, "Running"); + break; + case PSTOPPED: + strcpy(buf, sigtraps[WSTOPSIG(p->status)].mess); + break; + case PEXITED: + if (how == JP_SHORT) + buf[0] = '\0'; + else if (WEXITSTATUS(p->status) == 0) + strcpy(buf, "Done"); + 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 + strcpy(buf, sigtraps[WTERMSIG(p->status)].mess); + 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 void +kill_job(j) + Job *j; +{ + Proc *p; + + for (p = j->proc_list; p != (Proc *) 0; p = p->next) + if (p->pid != 0) + (void) kill(p->pid, SIGKILL); +} + +/* put a more useful name on a process than snptreef does (in certain cases) */ +static void +fill_command(c, len, t) + char *c; + int len; + struct op *t; +{ + int alen; + char **ap; + + if (t->type == TEXEC || t->type == TCOM) { + if (t->type == TCOM) + ap = eval(t->args, DOBLANK|DONTRUNCOMMAND); + else + ap = t->args; + --len; /* save room for the null */ + while (len > 0 && *ap != (char *) 0) { + alen = strlen(*ap); + if (alen > len) + alen = len; + memcpy(c, *ap, alen); + c += alen; + len -= alen; + if (len > 0) { + *c++ = ' '; len--; + } + ap++; + } + *c = '\0'; + } else + snptreef(c, len, "%T", t); +} diff --git a/bin/pdksh/ksh.1 b/bin/pdksh/ksh.1 new file mode 100644 index 00000000000..a23f13d9594 --- /dev/null +++ b/bin/pdksh/ksh.1 @@ -0,0 +1,3224 @@ +'\" t +.\" $OpenBSD: ksh.1,v 1.1 1996/08/14 06:19:12 downsj Exp $ +.\"{{{}}} +.\"{{{ Notes about man page +.\" - use the pseudo-macros .sh( and .sh) to begin and end sh-specific +.\" text and .ksh( and .ksh) for ksh specific text. +.\"}}} +.\"{{{ To do +.\" todo: Things not covered that should be: +.\" - distinguish (POSIX) special built-in's, (POSIX) regular built-in's, +.\" and sh/ksh weirdo built-in's (put S,R,X superscripts after command +.\" name in built-in commands section?) +.\" - need to be consistent about notation for `See section-name', ` +.\" See description of foobar command', `See section section-name', etc. +.\" - need to use the term `external command' meaning `a command that is +.\" executed using execve(2)' (as opposed to a built-in command or +.\" function) for more clear description. +.\"}}} +.\"{{{ Title +.TH KSH 1 "May 19, 1995" "" "User commands" +.\"}}} +.\"{{{ Name +.SH NAME +ksh \- Public domain Korn shell +.\"}}} +.\"{{{ Synopsis +.SH SYNOPSIS +.ad l +\fBksh\fP +[\fB\(+-abCefhikmnprsuvxX\fP] [\fB\(+-o\fP \fIoption\fP] [ [ \fB\-c\fP \fIcommand-string\fP [\fIcommand-name\fP] | \fB\-s\fP | \fIfile\fP ] [\fIargument\fP ...] ] +.ad b +.\"}}} +.\"{{{ Description +.SH DESCRIPTION +\fBksh\fP is a command interpreter that is intended for both +interactive and shell script use. Its command language is a superset +of the \fIsh\fP(1) shell language. +.\"{{{ Shell Startup +.SS "Shell Startup" +The following options can be specified only on the command line: +.IP "\fB\-c\fP \fIcommand-string\fP" +the shell executes the command(s) contained in \fIcommand-string\fP +.IP \fB\-i\fP +interactive mode \(em see below +.IP \fB\-l\fP +login shell \(em see below +interactive mode \(em see below +.IP \fB\-s\fP +the shell reads commands from standard input; all non-option arguments +are positional parameters +.IP \fB\-r\fP +restricted mode \(em see below +.PP +In addition to the above, the options described in the \fBset\fP built-in +command can also be used on the command line. +.PP +If neither the \fB\-c\fP nor the \fB\-s\fP options are 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 standard input. +The name of the shell (i.e., the contents of the \fB$0\fP) parameter +is determined as follows: if the \fB\-c\fP 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 \fBinteractive\fP if the \fB\-i\fP 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 INT, QUIT and TERM signals, and prints prompts before +reading input (see \fBPS1\fP and \fBPS2\fP parameters). +For non-interactive shells, the \fBtrackall\fP option is on by default +(see \fBset\fP command below). +.PP +A shell is \fBrestricted\fP if the \fB\-r\fP option is used or if either +the basename of the name the shell is invoked with or the \fBSHELL\fP +parameter match the pattern *r*sh (e.g., rsh, rksh, rpdksh, etc.). +The following restrictions come into effect after the shell processes +any profile and \fB$ENV\fP files: +.nr P2 \n(PD +.nr PD 0 +.IP \ \ \(bu +the \fBcd\fP command is disabled +.IP \ \ \(bu +the \fBSHELL\fP, \fBENV\fP and \fBPATH\fP parameters can't be changed +.IP \ \ \(bu +command names can't be specified with absolute or relative paths +.IP \ \ \(bu +the \fB\-p\fP option of the \fBcommand\fP built-in can't be used +.IP \ \ \(bu +redirections that create files can't be used (i.e., \fB>\fP, +\fB>|\fP, \fB>>\fP, \fB<>\fP) +.nr PD \n(P2 +.PP +A shell is \fBprivileged\fP if the \fB\-p\fP option is used or if +the real user-id or group-id does not match the effective user-id +or group-id (see \fIgetuid\fP(2), \fIgetgid\fP(2)). +A privileged shell does not process $HOME/.profile nor the \fBENV\fP +parameter (see below), instead the file /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 \fB\-\fP or if the \fB\-l\fP option is used, the shell is assumed to be +a login shell and the shell reads and executes the contents of +\fB/etc/profile\fP and \fB$HOME/.profile\fP if they exist and are readable. +.PP +If the \fBENV\fP 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 substitution and +the resulting file (if any) is read and executed. +If \fBENV\fP parameter is not set (and not null) and pdksh was compiled +with the \fBDEFAULT_ENV\fP 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. +.\"}}} +.\"{{{ Command Syntax +.SS "Command Syntax" +.\"{{{ words and tokens +The shell begins parsing its input by breaking it into \fIword\fPs. +Words, which are sequences of characters, are delimited by unquoted +\fIwhite-space\fP characters (space, tab and newline) or \fImeta-characters\fP +(\fB<\fP, \fB>\fP, \fB|\fP, \fB;\fP, \fB&\fP, \fB(\fP and \fB)\fP). +Aside from delimiting words, spaces and tabs are ignored, while +newlines usually delimit commands. +The meta-characters are used in building the following tokens: +\fB<\fP, \fB<&\fP, \fB<<\fP, \fB>\fP, \fB>&\fP, \fB>>\fP, etc. are used to +specify redirections (see Input/Output Redirection below); +\fB|\fP is used to create pipelines; +\fB|&\fP is used to create co-processes (see Co-Processes below); +\fB;\fP is used to separate commands; +\fB&\fP is used to create asynchronous pipelines; +\fB&&\fP and \fB||\fP are used to specify conditional execution; +\fB;;\fP is used in \fBcase\fP statements; +\fB(\fP .. \fB)\fP are used to create subshells; +and lastly, +\fB((\fP .. \fB))\fP are used in arithmetic expressions; +.PP +White-space and meta-characters can be quoted individually using +backslash (\fB\e\fP), or in groups using double (\fB"\fP) or single (\fB'\fP) +quotes. +Note that the following characters are also treated specially by the shell and +must be quoted if they are to represent themselves: +\fB\e\fP, \fB"\fP, \fB'\fP, \fB#\fP, \fB$\fP, \fB`\fP, \fB~\fP, \fB{\fP, +\fB}\fP, \fB*\fP, \fB?\fP and \fB[\fP. +The first three of these are the above mentioned quoting characters +(see Quoting below); +\fB#\fP, if used at the beginning of a word, introduces a comment \(em everything +after the \fB#\fP up to the nearest newline is ignored; +\fB$\fP is used to introduce parameter, command and arithmetic substitutions +(see Substitution below); +\fB`\fP introduces an old-style command substitution +(see Substitution below); +\fB~\fP begins a directory expansion (see Tilde Expansion below); +\fB{\fP and \fB}\fP delimit \fIcsh\fP(1) style alternations +(see Brace Expansion below); +and, finally, \fB*\fP, \fB?\fP and \fB[\fP are used in file name generation +(see File Name Patterns below). +.\"}}} +.\"{{{ simple-command +.PP +As words and tokens are parsed, the shell builds commands, of which +there are two basic types: \fIsimple-commands\fP, typically programs +that are executed, and \fIcompound-commands\fP, such as \fBfor\fP and +\fBif\fP statements, grouping constructs and function definitions. +.PP +A simple-command consists of some combination of parameter assignments (see +Parameters below), input/output redirections (see 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 \fIexternal +command\fP, i.e., a separate executable file that is located using the +\fBPATH\fP parameter (see Command Execution below). +Note that all command constructs have an \fIexit status\fP: for external +commands, this is related to the status returned by \fIwait\fP(2); +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 zero if there were no command substitutions. +.\"}}} +.\"{{{ pipeline +.PP +Commands can be chained together using the \fB|\fP token to +form \fIpipelines\fP, in which the standard output of each command but +the last is piped (see \fIpipe\fP(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 \fB!\fP reserved word which +causes the exit status of the pipeline to be logically +complemented: if the original status was 0 the complemented status will +be 1, and if the original status was not 0, then the complemented +status will be 0. +.\"}}} +.\"{{{ lists +.PP +\fILists\fP of commands can be created by separating pipelines by +any of the following tokens: \fB&&\fP, \fB||\fP, \fB&\fP, \fB|&\fP and \fB;\fP. +The first two are for conditional execution: \fIcmd1\fP \fB&&\fP \fIcmd2\fP +executes \fIcmd2\fP only if the exit status of \fIcmd1\fP is zero; +\fB||\fP is the opposite \(em \fIcmd2\fP is executed only if the exit status +of \fIcmd1\fP is non-zero. +\fB&&\fP and \fB||\fP have equal precedence which is higher than that of +\fB&\fP, \fB|&\fP and \fB;\fP, which also have equal precedence. +The \fB&\fP 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 \(em see +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 INT and QUIT ignored +and with input redirected from /dev/null (however, redirections specified in +the asynchronous command have precedence). +The \fB|&\fP operator starts a \fIco-process\fP which is special kind of +asynchronous process (see Co-Processes below). +Note that a command must follow the \fB&&\fP and \fB||\fP operators, while +a command need not follow \fB&\fP, \fB|&\fP and \fB;\fP. +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. +.\"}}} +.\"{{{ compound-commands +.PP +Compound commands are created using the following reserved words \(em 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): +.TS +center; +lfB lfB lfB lfB lfB . +case else function then ! +do esac if time [[ +done fi in until { +elif for select while } +.TE +\fBNote:\fP 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 \fBexec\fP statement should be used instead to redirect +file descriptors before the control structure. +.PP +In the following compound command descriptions, command lists (denoted as +\fIlist\fP) that are followed by reserved words must end with a +semi-colon, a newline or a (syntactically correct) reserved word. +For example, +.RS +\fB{ echo foo; echo bar; }\fP +.br +\fB{ echo foo; echo bar<newline>}\fP +.br +\fB{ { echo foo; echo bar; } }\fP +.RE +are all valid, but +.RS +\fB{ echo foo; echo bar }\fP +.RE +is not. +.\"{{{ ( list ) +.IP "\fB(\fP \fIlist\fP \fB)\fP" +Execute \fIlist\fP in a subshell. There is no implicit way to pass +environment changes from a subshell back to its parent. +.\"}}} +.\"{{{ { list } +.IP "\fB{\fP \fIlist\fP \fB}\fP" +Compound construct; \fIlist\fP is executed, but not in a subshell. +Note that \fB{\fP and \fB}\fP are reserved words, not meta-characters. +.\"}}} +.\"{{{ case word in [ [ ( ] pattern [ | pattern ] ... ) list ;; ] ... esac +.IP "\fBcase\fP \fIword\fP \fBin\fP [ [\fB(\fP] \fIpattern\fP [\fB|\fP \fIpattern\fP] ... \fB)\fP \fIlist\fP \fB;;\fP ] ... \fBesac\fP" +The \fBcase\fP statement attempts to match \fIword\fP against the specified +\fIpattern\fPs; the \fIlist\fP associated with the first successfully matched +pattern is executed. Patterns used in \fBcase\fP statements are the same as +those used for file name patterns except that the restrictions regarding +\fB\&.\fP and \fB/\fP are dropped. Note that any unquoted space before and +after a pattern is stripped; any space with 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 \fBin\fP and \fBesac\fP (e.g., \fBcase $foo { *) echo bar; }\fP). +The exit status of a \fBcase\fP statement is that of the executed \fIlist\fP; +if no \fIlist\fP is executed, the exit status is zero. +.\"}}} +.\"{{{ for name [ in word ... term ] do list done +.IP "\fBfor\fP \fIname\fP [ \fBin\fP \fIword\fP ... \fIterm\fP ] \fBdo\fP \fIlist\fP \fBdone\fP" +where \fIterm\fP is either a newline or a \fB;\fP. +For each \fIword\fP in the specified word list, the parameter \fIname\fP is +set to the word and \fIlist\fP is executed. If \fBin\fP is not used to +specify a word list, the positional parameters (\fB"$1"\fP, \fB"$2"\fP, etc.) +are used instead. +For historical reasons, open and close braces may be used instead +of \fBdo\fP and \fBdone\fP (e.g., \fBfor i; { echo $i; }\fP). +The exit status of a \fBfor\fP statement is the last exit status +of \fIlist\fP; if \fIlist\fP is never executed, the exit status is zero. +.\"}}} +.\"{{{ if list then list [ elif list then list ] ... [ else list ] fi +.IP "\fBif\fP \fIlist\fP \fBthen\fP \fIlist\fP [\fBelif\fP \fIlist\fP \fBthen\fP \fIlist\fP] ... [\fBelse\fP \fIlist\fP] \fBfi\fP" +If the exit status of the first \fIlist\fP is zero, the second \fIlist\fP +is executed; otherwise the \fIlist\fP following the \fBelif\fP, if any, is +executed with similar consequences. If all the lists following the \fBif\fP +and \fBelif\fPs fail (i.e., exit with non-zero status), the \fIlist\fP following +the \fBelse\fP is executed. The exit status of an \fBif\fP statement is that +of non-conditional \fIlist\fP that is executed; if no non-conditional +\fIlist\fP is executed, the exit status is zero. +.\"}}} +.\"{{{ select name [ in word ... ] do list done +.IP "\fBselect\fP \fIname\fP [ \fBin\fP \fIword\fP ... \fIterm\fP ] \fBdo\fP \fIlist\fP \fBdone\fP" +where \fIterm\fP is either a newline or a \fB;\fP. +The \fBselect\fP statement provides an automatic method of presenting +the user with a menu and selecting from it. +An enumerated list of the specified \fIwords\fP are printed on standard +error, followed by a prompt (\fBPS3\fP, normally `\fB#? \fP'). +A number corresponding to one of the enumerated words is then read +from standard input, \fIname\fP is set to the selected word (or is +unset if the selection is not valid), \fBREPLY\fP +is set to what was read, and \fIlist\fP is executed. +This process is repeated until an end-of-file is read, an interrupt is +received or a break statement is executed inside the loop. +If \fBin\fP \fIword\fP \fB\&...\fP is omitted, the positional parameters +are used (i.e., \fB"$1"\fP, \fB"$2"\fP, etc.). +For historical reasons, open and close braces may be used instead +of \fBdo\fP and \fBdone\fP (e.g., \fBselect i; { echo $i; }\fP). +The exit status of a \fBselect\fP statement is zero if a break statement +is used to exit the loop, non-zero otherwise. +.\"}}} +.\"{{{ until list do list done +.IP "\fBuntil\fP \fIlist\fP \fBdo\fP \fIlist\fP \fBdone\fP" +This works like \fBwhile\fP, except that the body is executed only while the +exit status of the first \fIlist\fP is non-zero. +.\"}}} +.\"{{{ while list do list done +.IP "\fBwhile\fP \fIlist\fP \fBdo\fP \fIlist\fP \fBdone\fP" +A \fBwhile\fP is a prechecked loop. Its body is executed as often +as the exit status of the first \fIlist\fP is zero. +The exit status of a \fBwhile\fP statement is the last exit status +of the \fIlist\fP in the body of the loop; if the body is not executed, +the exit status is zero. +.\"}}} +.\"{{{ function name { list } +.IP "\fBfunction\fP \fIname\fP \fB{\fP \fIlist\fP \fB}\fP" +Defines the function \fIname\fP. +See Functions below. +Note that redirections specified after a function definition are +performed whenever the function is executed, not when the function +definition is executed. +.\"}}} +.\"{{{ name () command +.IP "\fIname\fP \fB()\fP \fIcommand\fP" +Mostly the same as \fBfunction\fP. +See Functions below. +.\"}}} +.\"{{{ (( expression )) +.IP "\fB((\fP \fIexpression\fP \fB))\fP" +The arithmetic expression \fIexpression\fP is evaluated; +equivalent to \fBlet "\fP\fIexpression\fP\fB"\fP. +See Arithmetic Expressions and the \fBlet\fP command below. +.\"}}} +.\"{{{ [[ expression ]] +.IP "\fB[[\fP \fIexpression\fP \fB]]\fP" +Similar to the \fBtest\fP and \fB[\fP \&... \fB]\fP commands (described later), +with the following exceptions: +.RS +.nr P2 \n(PD +.nr PD 0 +.IP \ \ \(bu +Field splitting and file name generation are not performed on +arguments. +.IP \ \ \(bu +The \fB\-a\fP (and) and \fB\-o\fP (or) operators are replaced with +\fB&&\fP and \fB||\fP, respectively. +.IP \ \ \(bu +Operators (e.g. \fB\-f\fP, \fB=\fP, \fB!\fP, etc.) must be unquoted. +.IP \ \ \(bu +The second operand of \fB!=\fP and \fB=\fP +expressions are patterns (e.g., the comparison in +.ce +\fB[[ foobar = f*r ]]\fP +succeeds). +.IP \ \ \(bu +There are two additional binary operators: \fB<\fP and \fB>\fP +which return true if their first string operand is less than, +or greater than, their second string operand, respectively. +.IP \ \ \(bu +The single argument form +of \fBtest\fP, which tests if the argument has non-zero length, is not valid +- explicit operators must be always be used, e.g., instead of +.ce +\fB[\fP \fIstr\fP \fB]\fP +use +.ce +\fB[[ \-n \fP\fIstr\fP\fB ]]\fP +.IP \ \ \(bu +Parameter, command and arithmetic substitutions are performed as +expressions are evaluated and lazy expression evaluation is used for +the \fB&&\fP and \fB||\fP operators. +This means that in the statement +.ce +\fB[[ -r foo && $(< foo) = b*r ]]\fP +the \fB$(< foo)\fP is evaluated if and only if the file \fBfoo\fP exists +and is readable. +.nr PD \n(P2 +.RE +.\"}}} +.\"}}} +.\"}}} +.\"{{{ Quoting +.SS Quoting +Quoting is used to prevent the shell from treating characters or words +specially. +There are three methods of quoting: First, \fB\e\fP quotes +the following character, unless it is at the end of a line, in which +case both the \fB\e\fP and the newline are stripped. +Second, a single quote (\fB'\fP) quotes everything up to the next single +quote (this may span lines). +Third, a double quote (\fB"\fP) quotes all characters, +except \fB$\fP, \fB`\fP and \fB\e\fP, up to the next unquoted double quote. +\fB$\fP and \fB`\fP 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 \fB\e\fP inside a double-quoted string is followed by \fB\e\fP, \fB$\fP, +\fB`\fP or \fB"\fP, it is replaced by the second character; if it is +followed by a newline, both the \fB\e\fP and the newline are stripped; +otherwise, both the \fB\e\fP and the character following are unchanged. +.PP +Note: see POSIX Mode below for a special rule regarding sequences +of the form \fB"\fP...\fB`\fP...\fB\e"\fP...\fB`\fP..\fB"\fP. +.\"}}} +.\"{{{ Aliases +.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: +.ft B +.RS +autoload='typeset \-fu' +.br +functions='typeset \-f' +.br +hash='alias \-t' +.br +history='fc \-l' +.br +integer='typeset \-i' +.br +local='typeset' +.br +login='exec login' +.br +newgrp='exec newgrp' +.br +nohup='nohup ' +.br +r='fc \-e \-' +.br +stop='kill \-STOP' +.br +suspend='kill \-STOP $$' +.br +type='whence \-v' +.RE +.ft P +.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 \fBalias +\-t\fP. Note that changing the \fBPATH\fP parameter clears the saved +paths for all tracked aliases. If the \fBtrackall\fP option is set (i.e., +\fBset \-o trackall\fP or \fBset \-h\fP), the shell tracks all +commands. This option is set automatically for non-interactive shells. +For interactive shells, only the following commands are automatically +tracked: \fBcat\fP, \fBcc\fP, \fBchmod\fP, \fBcp\fP, \fBdate\fP, \fBed\fP, +\fBemacs\fP, \fBgrep\fP, \fBls\fP, \fBmail\fP, \fBmake\fP, \fBmv\fP, +\fBpr\fP, \fBrm\fP, \fBsed\fP, \fBsh\fP, \fBvi\fP and \fBwho\fP. +.\"}}} +.\"{{{ Substitution +.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 \fB$name\fP or \fB${\fP...\fB}\fP; command substitutions take +the form \fB$(\fP\fIcommand\fP\fB)\fP or \fB`\fP\fIcommand\fP\fB`\fP; +and arithmetic substitutions take the form \fB$((\fP\fIexpression\fP\fB))\fP. +.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 \fBIFS\fP parameter. +The \fBIFS\fP 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 +IFS characters are called \fIIFS white space\fP. +Sequences of one or more IFS white space characters, in combination with +zero or one non-IFS white space characters delimit a field. +As a special case, leading and trailing IFS white space is stripped (i.e., +no leading or trailing empty field is created by it); leading or trailing +non-IFS white space does create an empty field. +Example: if \fBIFS\fP is set to `<space>:', the sequence of characters +`<space>A<space>:<space><space>B::D' contains four fields: `A', `B', `' and `D'. +Note that if the \fBIFS\fP parameter is set to the 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 \fB$(\fP\fIcommand\fP\fB)\fP substitutions, normal quoting rules +are used when \fIcommand\fP is parsed, however, for the +\fB`\fP\fIcommand\fP\fB`\fP form, a \fB\e\fP followed by any of +\fB$\fP, \fB`\fP or \fB\e\fP is stripped (a \fB\e\fP followed by any other +character is unchanged). +As a special case in command substitutions, a command of the form +\fB<\fP \fIfile\fP is interpreted to mean substitute the contents +of \fIfile\fP ($(< foo) has the same effect as $(cat foo), but it +is carried out more efficiently because no process is started). +.br +.\"todo: fix this( $(..) parenthesis counting). +NOTE: \fB$(\fP\fIcommand\fP\fB)\fP expressions are currently parsed by +finding the matching parenthesis, regardless of quoting. This will hopefully +be fixed soon. +.PP +Arithmetic substitutions are replaced by the value of the specified +expression. +For example, the command \fBecho $((2+3*4))\fP prints 14. +See Arithmetic Expressions for a description of an \fIexpression\fP. +.\"}}} +.\"{{{ Parameters +.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 (`_' counts as a letter). +Parameter substitutions take the form \fB$\fP\fIname\fP or +\fB${\fP\fIname\fP\fB}\fP, where \fIname\fP is a parameter name. +If substitution is performed on a parameter that is not set, a null +string is substituted unless the \fBnounset\fP option (\fBset \-o nounset\fP +or \fBset \-u\fP) is set, in which case an error occurs. +.PP +.\"{{{ parameter assignment +Parameters can be assigned values in a number of ways. +First, the shell implicitly sets some parameters like \fB#\fP, \fBPWD\fP, +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, +`\fBFOO=bar\fP' sets the parameter FOO to 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 \fB=\fP must be unquoted for +the shell to recognize a parameter assignment. +The fourth way of setting a parameter is with the \fBexport\fP, \fBreadonly\fP +and \fBtypeset\fP commands; see their descriptions in the Command Execution +section. +Fifth, \fBfor\fP and \fBselect\fP loops set parameters as well as +the \fBgetopts\fP, \fBread\fP and \fBset \-A\fP commands. +Lastly, parameters can be assigned values using assignment operators +inside arithmetic expressions (see Arithmetic Expressions below) or +using the \fB${\fP\fIname\fP\fB=\fP\fIvalue\fP\fB}\fP form +of parameter substitution (see below). +.\"}}} +.PP +.\"{{{ environment +Parameters with the export attribute (set using the \fBexport\fP or +\fBtypeset \-x\fP commands, or by parameter assignments followed by simple +commands) are put in the environment (see \fIenviron\fP(5)) of commands +run by the shell as \fIname\fP\fB=\fP\fIvalue\fP 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. +.\"}}} +.\"{{{ ${name[:][-+=?]word} +.PP +Modifiers can be applied to the \fB${\fP\fIname\fP\fB}\fP form of parameter +substitution: +.IP \fB${\fP\fIname\fP\fB:-\fP\fIword\fP\fB}\fP +if \fIname\fP is set and not null, it is substituted, otherwise \fIword\fP is +substituted. +.IP \fB${\fP\fIname\fP\fB:+\fP\fIword\fP\fB}\fP +if \fIname\fP is set and not null, \fIword\fP is substituted, otherwise nothing is substituted. +.IP \fB${\fP\fIname\fP\fB:=\fP\fIword\fP\fB}\fP +if \fIname\fP is set and not null, it is substituted, otherwise it is +assigned \fIword\fP and the resulting value of \fIname\fP is substituted. +.IP \fB${\fP\fIname\fP\fB:?\fP\fIword\fP\fB}\fP +if \fIname\fP is set and not null, it is substituted, otherwise \fIword\fP +is printed on standard error (preceded by \fIname\fP:) and an error occurs +(normally causing termination of a shell script, function or \&.-script). +If word is omitted the string `parameter null or not set' is used instead. +.PP +In the above modifiers, the \fB:\fP can be omitted, in which case the +conditions only depend on \fIname\fP being set (as opposed to set and +not null). +If \fIword\fP is needed, parameter, command, arithmetic and tilde substitution +are performed on it; if \fIword\fP is not needed, it is not evaluated. +.\"}}} +.PP +The following forms of parameter substitution can also be used: +.\"{{{ ${#name} +.IP \fB${#\fP\fIname\fP\fB}\fP +The number of positional parameters if \fIname\fP is \fB*\fP, \fB@\fP or +is not specified, +or the length of the string value of parameter \fIname\fP. +.\"}}} +.\"{{{ ${#name[*]}, ${#name[@]} +.IP "\fB${#\fP\fIname\fP\fB[*]}\fP, \fB${#\fP\fIname\fP\fB[@]}\fP" +The number of elements in the array \fIname\fP. +.\"}}} +.\"{{{ ${name#pattern}, ${name##pattern} +.IP "\fB${\fP\fIname\fP\fB#\fP\fIpattern\fP\fB}\fP, \fB${\fP\fIname\fP\fB##\fP\fIpattern\fP\fB}\fP" +If \fIpattern\fP matches the beginning of the value of parameter \fIname\fP, +the matched text is deleted from the result of substitution. A single +\fB#\fP results in the shortest match, two \fB#\fP's results in the +longest match. +.\"}}} +.\"{{{ ${name%pattern}, ${name%%pattern} +.IP "\fB${\fP\fIname\fP\fB%\fP\fIpattern\fP\fB}\fP, \fB${\fP\fIname\fP\fB%%\fP\fIpattern\fP\fB}\fP" +Like \fB${\fP..\fB#\fP..\fB}\fP substitution, but it deletes from the end of the +value. +.\"}}} +.\"{{{ special shell parameters +.PP +The following special parameters are implicitly set by the shell and cannot be +set directly using assignments: +.\"{{{ ! +.IP \fB!\fP +Process id of the last background process started. If no background +processes have been started, the parameter is not set. +.\"}}} +.\"{{{ # +.IP \fB#\fP +The number of positional parameters (i.e., \fB$1\fP, \fB$2\fP, etc.). +.\"}}} +.\"{{{ $ +.IP \fB$\fP +The process ID of the shell, or the PID of the original shell if +it is a subshell. +.\"}}} +.\"{{{ - +.IP \fB\-\fP +The concatenation of the current single letter options +(see \fBset\fP command below for list of options). +.\"}}} +.\"{{{ ? +.IP \fB?\fP +The exit status of the last non-asynchronous command executed. +If the last command was killed by a signal, \fB$?\fP is set to 128 plus +the signal number. +.\"}}} +.\"{{{ 0 +.IP "\fB0\fP" +The name the shell was invoked with (i.e., \fBargv[0]\fP), or the +\fBcommand-name\fP if it was invoked with the \fB\-c\fP option and the +\fBcommand-name\fP was supplied, or the \fIfile\fP argument, if it was +supplied. +If the \fBposix\fP option is not set, \fB$0\fP is the name of the current +function or script. +.\"}}} +.\"{{{ 1-9 +.IP "\fB1\fP ... \fB9\fP" +The first nine positional parameters that were supplied to the shell, +function or \fB.\fP-script. +Further positional parameters may be accessed using +\fB${\fP\fInumber\fP\fB}\fP. +.\"}}} +.\"{{{ * +.IP \fB*\fP +All positional parameters (except parameter 0), +i.e., \fB$1 $2 $3\fP.... +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 \fBIFS\fP parameter +(or the empty string if \fBIFS\fP is null). +.\"}}} +.\"{{{ @ +.IP \fB@\fP +Same as \fB$*\fP, 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 ("$@" can be used +to access arguments, verbatim, without loosing null arguments or +splitting arguments with spaces). +.\"}}} +.\"}}} +.\"{{{ general shell parameters +.PP +The following parameters are set and/or used by the shell: +.\"{{{ _ +.IP "\fB_\fP \fI(underscore)\fP" +In interactive use, this parameter is set to the last word of the +previous command. When a command is executed, this parameter is set to +the full path of the command and is placed in the command's environment. +When \fBMAILPATH\fP messages are evaluated, this parameter contains +the name of the file that changed (see \fBMAILPATH\fP parameter below). +.\"}}} +.\"{{{ CDPATH +.IP \fBCDPATH\fP +Search path for the \fBcd\fP built-in command. Works the same way as +\fBPATH\fP for those directories not beginning with \fB/\fP in \fBcd\fP +commands. +Note that if CDPATH is set and does not contain \fB.\fP nor an empty path, +the current directory is not searched. +.\"}}} +.\"{{{ COLUMNS +.IP \fBCOLUMNS\fP +Set to the number of columns on the terminal or window. +Currently set to the \fBcols\fP value as reported by \fIstty\fP(1) if that +value is non-zero. +This parameter is used by the interactive line editing modes, and by +\fBselect\fP, \fBset \-o\fP and \fBkill \-l\fP commands +to format information in columns. +.\"}}} +.\"{{{ EDITOR +.IP \fBEDITOR\fP +If the \fBVISUAL\fP parameter is not set, this parameter controls the +command line editing mode for interactive shells. +See \fBVISUAL\fP parameter below for how this works. +.\"}}} +.\"{{{ ENV +.IP \fBENV\fP +If this parameter is found to be set after any profile files are +executed, the expanded value is used as a shell start-up file. It +typically contains function and alias definitions. +.\"}}} +.\"{{{ ERRNO +.IP \fBERRNO\fP +Integer value of the shell's errno variable \(em indicates the reason +the last system call failed. +.\" todo: ERRNO variable +.sp +Not implemented yet. +.\"}}} +.\"{{{ EXECSHELL +.IP \fBEXECSHELL\fP +If set, this parameter is assumed to contain the shell that is to be +used to execute commands that \fIexecve\fP(2) fails to execute and +which do not start with a `\fB#!\fP \fIshell\fP' sequence. +.\"}}} +.\"{{{ FCEDIT +.IP \fBFCEDIT\fP +The editor used by the \fBfc\fP command (see below). +.\"}}} +.\"{{{ FPATH +.IP \fBFPATH\fP +Like \fBPATH\fP, 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 \fBPATH\fP. +See Functions below for more information. +.\"}}} +.\"{{{ HISTFILE +.IP \fBHISTFILE\fP +The name of the file used to store 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 +\fBHISTFILE\fP parameters all point at the same file. +.br +NOTE: if HISTFILE isn't set, no history file is used. This is +different from the original Korn shell, which uses \fB$HOME/.sh_history\fP; +in future, pdksh may also use a default history file. +.\"}}} +.\"{{{ HISTSIZE +.IP \fBHISTSIZE\fP +The number of commands normally stored for history, default 128. +.\"}}} +.\"{{{ HOME +.IP \fBHOME\fP +The default directory for the \fBcd\fP command and the value +substituted for an unqualified \fB~\fP (see Tilde Expansion below). +.\"}}} +.\"{{{ IFS +.IP \fBIFS\fP +Internal field separator, used during substitution and by the \fBread\fP +command, to split values into distinct arguments; normally set to +space, tab and newline. See Substitution above for details. +.br +\fBNote:\fP this parameter is not imported from the environment +when the shell is started. +.\"}}} +.\"{{{ KSH_VERSION +.IP \fBKSH_VERSION\fP +The version of shell and the date the version was created (readonly). +See also the version commands in Emacs Interactive Input Line Editing +and Vi Interactive Input Line Editing, below. +.\"}}} +.\"{{{ SH_VERSION +.\"}}} +.\"{{{ LINENO +.IP \fBLINENO\fP +The line number of the function or shell script that is currently being +executed. +.\" todo: LINENO variable +.sp +Not implemented yet. +.\"}}} +.\"{{{ LINES +.IP \fBLINES\fP +Set to the number of lines on the terminal or window. +.\"Currently set to the \fBrows\fP value as reported by \fIstty\fP(1) if that +.\"value is non-zero. +.\" todo: LINES variable +.sp +Not implemented yet. +.\"}}} +.\"{{{ MAIL +.IP \fBMAIL\fP +If set, the user will be informed of the arrival of mail in the named +file. This parameter is ignored if the \fBMAILPATH\fP parameter is set. +.\"}}} +.\"{{{ MAILCHECK +.IP \fBMAILCHECK\fP +How often, in seconds, the shell will check for mail in the file(s) +specified by \fBMAIL\fP or \fBMAILPATH\fP. If 0, the shell checks +before each prompt. The default is 600 (10 minutes). +.\"}}} +.\"{{{ MAILPATH +.IP \fBMAILPATH\fP +A list of files to be checked for mail. The list is colon separated, +and each file may be followed by a \fB?\fP and a message to be printed +if new mail has arrived. Command and parameter substitution is +performed on the message, and, during substitution, the parameter \fB$_\fP +contains the name of the file. +The default message is \fByou have mail in $_\fP. +.\"}}} +.\"{{{ OLDPWD +.IP \fBOLDPWD\fP +The previous working directory. +Unset if \fBcd\fP has not successfully changed directories since the +shell started, or if the shell doesn't know where it is. +.\"}}} +.\"{{{ OPTARG +.IP \fBOPTARG\fP +When using \fBgetopts\fP, it contains the argument for a parsed option, +if it requires one. +.\"}}} +.\"{{{ OPTIND +.IP \fBOPTIND\fP +The index of the last argument processed when using \fBgetopts\fP. +Assigning 1 to this parameter causes \fBgetopts\fP to +process arguments from the beginning the next time it is invoked. +.\"}}} +.\"{{{ PATH +.IP \fBPATH\fP +A colon separated list of directories that are searched when looking +for commands and \fB.\fP'd files. +An empty string resulting from a leading or trailing colon, or two adjacent +colons is treated as a `.', the current directory. +.\"}}} +.\"{{{ POSIXLY_CORRECT +.IP \fBPOSIXLY_CORRECT\fP +If set, this parameter causes the \fBposix\fP option to be enabled. +See POSIX Mode below. +.\"}}} +.\"{{{ PPID +.IP \fBPPID\fP +The process ID of the shell's parent (readonly). +.\"}}} +.\"{{{ PS1 +.IP \fBPS1\fP +\fBPS1\fP is the primary prompt for interactive shells. +Parameter, command and arithmetic substitutions are performed, and +\fB!\fP is replaced with the current command number (see \fBfc\fP +command below). A literal ! can be put in the prompt by placing !! in PS1. +Default is `\fB$\ \fP'. +.\"}}} +.\"{{{ PS2 +.IP \fBPS2\fP +Secondary prompt string, by default `\fB>\fP ', used when more input is +needed to complete a command. +.\"}}} +.\"{{{ PS3 +.IP \fBPS3\fP +Prompt used by \fBselect\fP statement when reading a menu selection. +Default is `\fB#?\ \fP'. +.\"}}} +.\"{{{ PS4 +.IP \fBPS4\fP +Used to prefix commands that are printed during execution tracing +(see \fBset \-x\fP command below). +Parameter, command and arithmetic substitutions are performed +before it is printed. +Default is `\fB+\ \fP'. +.\"}}} +.\"{{{ PWD +.IP \fBPWD\fP +The current working directory. Maybe unset or null if shell doesn't +know where it is. +.\"}}} +.\"{{{ RANDOM +.IP \fBRANDOM\fP +A simple random number generator. Every time \fBRANDOM\fP 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 +\fBRANDOM\fP (see \fIrand\fP(3)). +.\"}}} +.\"{{{ REPLY +.IP \fBREPLY\fP +Default parameter for the \fBread\fP command if no names are given. +Also used in \fBselect\fP loops to store the value that is read from +standard input. +.\"}}} +.\"{{{ SECONDS +.IP \fBSECONDS\fP +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. +.\"}}} +.\"{{{ TMOUT +.IP \fBTMOUT\fP +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 (\fBPS1\fP). If the time is exceeded, the +shell exits. +.\"}}} +.\"{{{ TMPDIR +.IP \fBTMPDIR\fP +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 \fB/tmp\fP. +.\"}}} +.\"{{{ VISUAL +.IP \fBVISUAL\fP +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 \fBvi\fP, \fBemacs\fP or \fBgmacs\fP, the +vi, emacs or gmacs (Gosling emacs) editing mode is enabled, respectively. +.\"}}} +.\"}}} +.\"}}} +.\"{{{ Tilde Expansion +.SS "Tilde Expansion" +Tilde expansion, which is done in parallel with parameter substitution, +is done on words starting with an unquoted \fB~\fP. The characters +following the tilde, up to the first \fB/\fP, if any, are assumed to be +a login name. If the login name is empty, \fB+\fP or \fB\-\fP, the +value of the \fBHOME\fP, \fBPWD\fP, or \fBOLDPWD\fP 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 \fBalias\fP, \fBexport\fP, \fBreadonly\fP, +and \fBtypeset\fP), tilde expansion is done after any unquoted colon +(\fB:\fP), and login names are also delimited by colons. +.PP +The home directory of previously expanded login names are cached and +re-used. The \fBalias \-d\fP command may be used to list, change and +add to this cache (e.g., `alias \-d fac=/usr/local/facilities; cd +~fac/bin'). +.\"}}} +.\"{{{ Brace Expansion +.SS "Brace Expansion (alternation)" +Brace expressions, which take the form +.RS +\fIprefix\fP\fB{\fP\fIstr\fP1\fB,\fP...\fB,\fP\fIstr\fPN\fB}\fP\fIsuffix\fP +.RE +are expanded to N words, each of which is the concatenation of +\fIprefix\fP, \fIstr\fPi and \fIsuffix\fP +(e.g., `a{c,b{X,Y},d}e' expands to four word: ace, abXe, abYe, and 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 (\fB,\fP) for expansion +to occur (i.e., \fB{}\fP and \fB{foo}\fP are not expanded). +Brace expansion is carried out after parameter substitution and before +file name generation. +.\"}}} +.\"{{{ File Name Patterns +.SS "File Name Patterns" +.PP +A file name pattern is a word containing one or more unquoted \fB?\fP or +\fB*\fP characters or \fB[\fP..\fB]\fP sequences. Once brace expansion has +been performed, the shell replaces file name patterns with the sorted names +of all the files that match the pattern (if no files match, the word is +left unchanged). The pattern elements have the following meaning: +.IP \fB?\fP +matches any single character. +.IP \fB*\fP +matches any sequence of characters. +.IP \fB[\fP..\fB]\fP +matches any of the characters inside the brackets. Ranges of characters +can be specified by separating two characters by a \fB\-\fP, e.g., \fB[a0\-9]\fP +matches the letter \fBa\fP or any digit. In order to represent itself, a +\fB\-\fP must either be quoted or the first or last character in the character +list. Similarly, a \fB]\fP must be quoted or the first character in the list +if it is represent itself instead of the end of the list. Also, a \fB!\fP +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. +.IP \fB[!\fP..\fB]\fP +like \fB[\fP..\fB]\fP, except it matches any character not inside the brackets. +.IP "\fB*(\fP\fIpattern\fP\fB|\fP ... \fP|\fP\fIpattern\fP\fB)\fP" +matches any string of characters that matches zero or more occurances +of the specified patterns. +Example: the pattern \fB*(foo|bar)\fP matches the strings +`', `foo', `bar', `foobarfoo', etc.. +.IP "\fB+(\fP\fIpattern\fP\fB|\fP ... \fP|\fP\fIpattern\fP\fB)\fP" +matches any string of characters that matches one or more occurances +of the specified patterns. +Example: the pattern \fB+(foo|bar)\fP matches the strings +`foo', `bar', `foobarfoo', etc.. +.IP "\fB?(\fP\fIpattern\fP\fB|\fP ... \fP|\fP\fIpattern\fP\fB)\fP" +matches the empty string or a string that matches one of the +specified patterns. +Example: the pattern \fB?(foo|bar)\fP only matches the strings +`', `foo' and `bar'. +.IP "\fB@(\fP\fIpattern\fP\fB|\fP ... \fP|\fP\fIpattern\fP\fB)\fP" +matches a string that matches one of the +specified patterns. +Example: the pattern \fB@(foo|bar)\fP only matches the strings +`foo' and `bar'. +.IP "\fB!(\fP\fIpattern\fP\fB|\fP ... \fP|\fP\fIpattern\fP\fB)\fP" +matches any string that does not match one of the specified +patterns. +Examples: the pattern \fB!(foo|bar)\fP matches all strings except +`foo' and `bar'; the pattern \fB!(*)\fP matches no strings; +the pattern \fB!(?)*\fP matches all strings (think about it). +.PP +Note that pdksh currently never matches \fB.\fP and \fB..\fP, but the original +ksh, Bourne sh and bash do, so this may have to change (too bad). +.PP +Note that none of the above pattern elements match either a period (\fB.\fP) +at the start of a file name or a slash (\fB/\fP), even if they are explicitly +used in a \fB[\fP..\fB]\fP sequence; also, the names \fB.\fP and \fB..\fP +are never matched, even by the pattern \fB.*\fP. +.PP +If the \fBmarkdirs\fP option is set, any directories that result from +file name generation are marked with a trailing \fB/\fP. +.PP +.\" todo: implement this ([[:alpha:]], etc.) +The POSIX character classes (i.e., +\fB[:\fP\fIclass-name\fP\fB:]\fP inside a \fB[\fP..\fB]\fP expression) +are not yet implemented. +.\"}}} +.\"{{{ Input/Output Redirection +.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 \fB/dev/null\fP, and commands for which any of the following +redirections have been specified: +.IP "\fB>\fP \fIfile\fP" +standard output is redirected to \fIfile\fP. If \fIfile\fP does not exist, +it is created; if it does exist, is a regular file and the \fBnoclobber\fP +option is set, an error occurs, otherwise the file is truncated. +Note that this means the command \fIcmd < foo > foo\fP will open +\fIfoo\fP for reading and then truncate it when it opens it for writing, +before \fIcmd\fP gets a chance to actually read \fIfoo\fP. +.IP "\fB>|\fP \fIfile\fP" +same as \fB>\fP, except the file is truncated, even if the \fBnoclobber\fP +option is set. +.IP "\fB>>\fP \fIfile\fP" +same as \fB>\fP, except the file an existing file 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 \fIopen\fP(2)). +.IP "\fB<\fP \fIfile\fP" +standard input is redirected from \fIfile\fP, which is opened for reading. +.IP "\fB<>\fP \fIfile\fP" +same as \fB<\fP, except the file is opened for reading and writing. +.IP "\fB<<\fP \fImarker\fP" +after reading the command line containing this kind of redirection (called a +here document), the shell copies lines from the command source into a temporary +file until a line matching \fImarker\fP is read. +When the command is executed, standard input is redirected from the temporary +file. +If \fImarker\fP 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 (\fB\e\fP) escapes for +\fB$\fP, \fB`\fP, \fB\e\fP and \fB\enewline\fP. +If multiple here documents are used on the same command line, they are +saved in order. +.IP "\fB<<-\fP \fImarker\fP" +same as \fB<<\fP, except leading tabs are stripped from lines in the +here document. +.IP "\fB<&\fP \fIfd\fP" +standard input is duplicated from file descriptor \fIfd\fP. +\fIfd\fP can be a single digit, indicating the number of an existing +file descriptor, the letter \fBp\fP, indicating the file descriptor +associated with the output of the current co-process, or +the character \fB\-\fP, indicating standard input is to be closed. +.IP "\fB>&\fP \fIfd\fP" +same as \fB<&\fP, except the operation is done on standard output. +.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 +file name generation are all performed on the \fIfile\fP, \fImarker\fP and +\fIfd\fP 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 +unexpanded 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 (\fBif\fP statements, etc.), any redirections must +appear at the end. +Redirections are processed after pipelines are created and in the order they +are given, so +.RS +\fBcat /foo/bar 2>&1 > /dev/null | cat \-n\fP +.RE +will print an error with a line number prepended to it. +.\"}}} +.\"{{{ Arithmetic Expressions +.SS "Arithmetic Expressions" +Integer arithmetic expressions can be used with the \fBlet\fP command, +inside \fB$((\fP..\fB))\fP expressions, +inside array references (e.g., \fIname\fP\fB[\fP\fIexpr\fP\fB]\fP), +as numeric arguments to the \fBtest\fP command, +and as the value of an assignment to an integer parameter. +.PP +Expression 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). +.TP +Unary operators: +\fB+ \- ! ~\fP +.TP +Binary operators: +\fB= *= /= %= += \-= <<= >>= &= ^= |=\fP +.br +\fB||\fP +.br +\fB&&\fP +.br +\fB|\fP +.br +\fB^\fP +.br +\fB&\fP +.br +\fB== !=\fP +.br +\fB< <= >= >\fP +.br +\fB<< >>\fP +.br +\fB+ \-\fP +.br +\fB* / %\fP +.TP +Ternary operator: +\fB?:\fP (precedence is immediately higher than assignment) +.TP +Grouping operators: +\fB( )\fP +.PP +Integer constants may be specified with arbitrary bases using the notation +\fIbase\fP\fB#\fP\fInumber\fP, where \fIbase\fP is a decimal integer specifying +the base, and \fInumber\fP is a number in the specified base. +.LP +The operators are evaluated as follows: +.RS +.IP "unary \fB+\fP" +result is the argument (included for completeness). +.IP "unary \fB\-\fP" +negation. +.IP "\fB!\fP" +logical not; the result is 1 if argument is zero, 0 if not. +.IP "\fB~\fP" +arithmetic (bit-wise) not. +.IP "\fB=\fP" +assignment; variable on the left is set to the value on the right. +.IP "\fB*= /= %= += \-= <<= >>= &= ^= |=\fP" +assignment operators; \fI<var> <op>\fP\fB=\fP \fI<expr>\fP is the same as +\fI<var>\fP \fB=\fP \fI<var> <op>\fP \fB(\fP \fI<expr>\fP \fB)\fP. +.IP "\fB||\fP" +logical or; the result is 1 if either argument is non-zero, 0 if not. +The right argument is evaluated only if the left argument is zero. +.IP "\fB&&\fP" +logical and; the result is 1 if both arguments are non-zero, 0 if not. +The right argument is evaluated only if the left argument is non-zero. +.IP "\fB|\fP" +arithmetic (bit-wise) or. +.IP "\fB^\fP" +arithmetic (bit-wise) exclusive-or. +.IP "\fB&\fP" +arithmetic (bit-wise) and. +.IP "\fB==\fP" +equal; the result is 1 if both arguments are equal, 0 if not. +.IP "\fB!=\fP" +not equal; the result is 0 if both arguments are equal, 1 if not. +.IP "\fB<\fP" +less than; the result is 1 if the left argument is less than the right, +0 if not. +.IP "\fB<= >= >\fP" +less than or equal, greater than or equal, greater than. See <. +.IP "\fB<< >>\fP" +shift left (right); the result is the left argument with its bits shifted +left (right) by the amount given in the right argument. +.IP "\fB+ - * /\fP" +addition, subtraction, multiplication, and division. +.IP "\fB%\fP" +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. +.IP "\fI<arg1>\fP \fB?\fP \fI<arg2>\fP \fB:\fP \fI<arg3>\fP" +if \fI<arg1>\fP is non-zero, the result is \fI<arg2>\fP, +otherwise \fI<arg3>\fP. +.RE +.\"}}} +.\"{{{ Co-Processes +.SS "Co-Processes" +A Co-process, which is a pipeline created with the \fB|&\fP operator, +is an asynchronous process that the shell can both write to +(using \fBprint \-p\fP) and read from (using \fBread \-p\fP). +Only one co-process may be active at any given time, unless the input +of an existing co-process has been duplicated using a \fB>&p\fP redirection. +In this case, the shell closes its copy of the co-process input (note +that a \fB<&p\fP redirection does not cause the shell to close its copy +of the co-process output). +Once \fBread \-p\fP has read the end-of-file, the co-process input is +closed, and further attempts to read will produce diagnostics. +Similarly, once \fBprint \-p\fP fails due to an EPIPE, the co-process +output is closed. +Note also, that \fBprint \-p\fP will ignore SIGPIPE signals during writes +if the signal is not being trapped or ignored; the same is not true if +the co-process input has been duplicated to another file descriptor and +\fBprint \-u\fP\fIn\fP is used. +.PP +If another co-process is started before doing a read or \fB<&p\fP redirection +from existing co-processes, the output of the co-processes will be shared +(e.g., \fBecho hi |& echo there|& cat <&p\fP will print both "hi" and "there"). +This is slightly different from the original Korn shell in which output is +shared as long as the existing co-process job has not exited. +As a side affect, end-of-file can not be read reliably in the original Korn +shell (e.g., if the co-process closes its output but does not exit, the +end of file will not be read). +.\"}}} +.\"{{{ Functions +.SS "Functions" +Functions are defined using either Korn shell \fBfunction\fP \fIname\fP +syntax or the Bourne/POSIX shell \fIname\fP\fB()\fP syntax. +At the moment, there is no difference between the two, but see below. +Functions are like \fB.\fP-scripts in that they are executed in +the current environment, however, unlike \fB.\fP-scripts, shell arguments +(i.e., positional parameters, \fB$1\fP, etc.) are never visible inside them. +When the shell is determining the location of a command, functions are +searched after special built-in commands, and before regular and non-regular +built-ins, and before the \fBPATH\fP is searched. +.PP +An existing function may be deleted using \fBunset \-f\fP \fIfunction-name\fP. +A list of functions can be obtained using \fBtypeset +f\fP and the +function definitions can be listed using \fBtypeset \-f\fP. +\fBautoload\fP (which is an alias for \fBtypeset \-fu\fP) may be used to +create undefined functions; when an undefined function is executed, the +shell searches the path specified in the \fBFPATH\fP 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 \fBPATH\fP). +Note that if a command is not found using \fBPATH\fP, an attempt is +made to autoload a function using \fBFPATH\fP (this is an undocumented +feature of the original Korn shell). +.PP +Functions can have two attributes, trace and export, which can be set +with \fBtypeset \-ft\fP and \fBtypeset \-fx\fP, respectively. +When a traced function is executed, the shell's \fBxtrace\fP option is turned +on for the functions duration, otherwise the \fBxtrace\fP option is turned off. +The 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 \fBtypeset\fP command can be used +inside a function to create a local parameter. +Note that special parameters (e.g., \fB$$\fP, \fB$!\fP) 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 \fBreturn\fP command; +this may also be used to explicitly specify the exit status. +.PP +In the future, functions defined with the \fBfunction\fP reserved word will +be treated differently in the following ways from functions defined with +the \fB()\fP notation: +.nr P2 \n(PD +.nr PD 0 +.IP \ \ \(bu +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. +.IP \ \ \(bu +The EXIT trap, if set in a function, will be executed after the function +returns. +.IP \ \ \(bu +the \fB$0\fP parameter will be set to the name of the function (this is +currently the case for both styles of function). +.nr PD \n(P2 +.\"}}} +.\"{{{ POSIX mode +.SS "POSIX Mode" +The shell is intended to be POSIX compliant, however, in some cases, POSIX +behaviour is contrary either to the original Korn shell behaviour or to +user convenience. +How the shell behaves in these cases is determined by the state of +the posix option (\fBset \-o posix\fP) \(em if it is on, the POSIX behaviour +is followed, otherwise it is not. +The \fBposix\fP option is set automatically when the shell starts up +if the environment contains the \fBPOSIXLY_CORRECT\fP parameter. +(The shell can also be compiled so that it is in POSIX mode by default, +however this is usually not desirable). +.PP +The following is a list of things that are affected by the state of +the \fBposix\fP option: +.nr P2 \n(PD +.nr PD 0 +.IP \ \ \(bu +\fB\e"\fP inside double quoted \fB`\fP..\fB`\fP command substitutions: +in posix mode, the \fB\e"\fP is interpreted when the command is interpreted; +in non-posix mode, the backslash is stripped before the command substitution +is interpreted. For example, \fBecho "`echo \e"hi\e"`"\fP produces `"hi"' in +posix mode, `hi' in non-posix mode. To avoid problems, use the \fB$(...\fP) +form of command substitution. +.IP \ \ \(bu +\fBkill \-l\fP output: in posix mode, signal names are listed one a single line; +in non-posix mode, signal numbers, names and descriptions are printed in +columns. +In future, a new option (\fB\-v\fP perhaps) will be added to distinguish +the two behaviours. +.IP \ \ \(bu +\fBfg\fP exit status: in posix mode, the exit status is 0 if no errors occur; +in non-posix mode, the exit status is that of the last foregrounded job. +.IP \ \ \(bu +\fB$0\fP: in posix mode, it is always the name of the shell; in non-posix +mode, it is the name of the current function or script while a function or +script is being executed. +.IP \ \ \(bu +\fBgetopts\fP: in posix mode, options must start with a \fB\-\fP; in non-posix +mode, options can start with either \fB\-\fP or \fB+\fP. +.IP \ \ \(bu +brace expansion (also known as alternation): in posix mode, brace expansion +is disabled; in non-posix mode, brace expansion enabled. +Note that \fBset \-o posix\fP (or setting the \fBPOSIXLY_CORRECT\fP parameter) +automatically turns the \fBbraceexpand\fP option off, however it can be +explicitly turned on later. +.IP \ \ \(bu +\fBset \-\fP: in posix mode, this does not clear the \fBverbose\fP or +\fBxtrace\fP options; in non-posix mode, it does. +.IP \ \ \(bu +\fBset\fP exit status: in posix mode, the exit status of set is 0 +if there are no errors; in non-posix mode, the exit status is that of +any command substitutions performed in generating the set command. +For example, `\fBset \-\- `false`; echo $?\fP' prints 0 in posix mode, +1 in non-posix mode. This construct is used in most shell scripts that +use the old \fIgetopt\fP(1) command. +.IP \ \ \(bu +argument expansion of \fBalias\fP, \fBexport\fP, \fBreadonly\fP, and +\fBtypeset\fP commands: in posix mode, normal argument expansion done; +in non-posix mode, field splitting, file globing, brace expansion and +(normal) tilde expansion are turned off, and assignment tilde expansion +is turned on. +.IP \ \ \(bu +signal specification: in posix mode, signals can be specified as digits only +if signal numbers match POSIX values (i.e., HUP=1, INT=2, QUIT=3, ABRT=6, +KILL=9, ALRM=14, and TERM=15); in non-posix mode, signals can be always digits. +.IP \ \ \(bu +alias expansion: in posix mode, alias expansion is only carried out when +reading command words; in non-posix mode, alias expansion is carried out +on any word following an alias that ended in a space. +For example, the following for loop +.RS +.ft B +alias a='for ' i='j' +.br +a i in 1 2; do echo i=$i j=$j; done +.ft P +.RE +uses parameter \fBi\fP in posix mode, \fBj\fP in non-posix mode. +.nr PD \n(P2 +.\"}}} +.\"{{{ Command Execution (built-in commands) +.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 \fBPATH\fP parameter. +The checks are made in the above order. +Special built-in commands differ from other commands in that +the \fBPATH\fP parameter is not used to find them, an error +during their execution can cause a non-interactive shell to exit and +parameter assignments that are specified before the command are +kept after the command completes. +Just to confuse things, if the posix option is turned off (see \fBset\fP +command below) some special commands are very special in that +no field splitting, file globing, brace expansion nor tilde expansion +is preformed on arguments that look like assignments. +Regular built-in commands are different only in that the \fBPATH\fP +parameter is not used to find them. +.PP +The original ksh and POSIX differ somewhat in which commands are considered +special or regular: +.IP "POSIX special commands" +.TS +lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB . +\&. continue exit return trap +: eval export set unset +break exec readonly shift +.TE +.IP "Additional ksh special commands" +.TS +lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB . +builtin times typeset +.TE +.IP "Very special commands (non-posix mode)" +.TS +lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB . +alias readonly set typeset +.TE +.IP "POSIX regular commands" +.TS +lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB . +alias command fg kill umask +bg false getopts read unalias +cd fc jobs true wait +.TE +.IP "Additional ksh regular commands" +.TS +lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB lw(8m)fB . +[ let pwd ulimit +echo print test whence +.TE +.PP +In the future, the additional ksh special and regular commands may +be treated differently from the 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: +.\"{{{ . file [ arg1 ... ] +.IP "\fB\&.\fP \fIfile\fP [\fIarg1\fP ...]" +Execute the commands in \fIfile\fP in the current environment. +The file is searched for in the directories of \fBPATH\fP. +If arguments are given, the positional parameters may be used to +access them while \fIfile\fP is being executed. +If no arguments are given, the positional parameters are those of the +environment the command is used in. +.\"}}} +.\"{{{ : [ ... ] +.IP "\fB:\fP [ ... ]" +The null command. +Exit status is set to zero. +.\"}}} +.\"{{{ alias [ -d | -t [ -r ] ] [-x] [name1[=value1] ...] +.IP "\fBalias\fP [ \fB\-d\fP | \fB\-t\fP [\fB\-r\fP] ] [\fB\-x\fP] [\fIname1\fP[\fB=\fP\fIvalue1\fP] ...]" +Without arguments, \fBalias\fP lists all aliases and their values. For +any name without a value, its value is listed. Any name with a value +defines an alias (see Aliases above). +.sp +The \fB\-x\fP option sets the export attribute of an alias, or, if no +names are given, lists the aliases with the export attribute +(exporting an alias currently has no affect). +.sp +The \fB\-t\fP option indicates that tracked aliases are to be listed/set +(values specified on the command line are ignored for tracked aliases). +The \fB\-r\fP option indicates that all tracked aliases are to be reset. +.sp +The \fB\-d\fP causes directory aliases, which are used in tilde expansion, +to be listed or set (see Tilde Expansion above). +.\"}}} +.\"{{{ bg [job ...] +.IP "\fBbg\fP [\fIjob\fP ...]" +Resume the specified stopped job(s) in the background. +If no jobs are specified, \fB%+\fP is assumed. +This command is only available on systems which support job control. +See Job Control below for more information. +.\"}}} +.\"{{{ bind [-l] [-m] [key[=editing-command] ...] +.IP "\fBbind\fP [\fB\-m\fP] [\fIkey\fP[\fB=\fP\fIediting-command\fP] ...]" +Set or view the current emacs command editing key bindings/macros. +See Emacs Interactive Input Line Editing below for a complete description. +.\"}}} +.\"{{{ break [level] +.IP "\fBbreak\fP [\fIlevel\fP]" +\fBbreak\fP exits the \fIlevel\fPth inner most for, select, until, or while +loop. +\fIlevel\fP defaults to 1. +.\"}}} +.\"{{{ builtin command [arg1 ...] +.IP "\fBbuiltin\fP \fIcommand\fP [\fIarg1\fP ...]" +Execute the built-in command \fIcommand\fP. +.\"}}} +.\"{{{ cd [-LP] [dir] +.IP "\fBcd\fP [\fB\-LP\fP] [\fIdir\fP]" +Set the working directory to \fIdir\fP. If the parameter \fBCDPATH\fP +is set, it lists the search path for the directory containing +\fIdir\fP. A null path means the current directory. If \fIdir\fP is +missing, the home directory \fB$HOME\fP is used. If \fIdir\fP is +\fB\-\fP, the previous working directory is used (see OLDPWD parameter). +If \fB\-L\fP option (logical path) is used or if the \fBphysical\fP option +(see \fBset\fP command below) isn't set, references to \fB..\fP in \fIdir\fP +are relative to the path used get to the directory. +If \fB\-P\fP option (physical path) is used or if the \fBphysical\fP option +is set, \fB..\fP is relative to the filesystem directory tree. +The \fBPWD\fP and \fBOLDPWD\fP parameters are updated to reflect the +current and old wording directory, respectively. +.\"}}} +.\"{{{ cd [-LP] old new +.IP "\fBcd\fP [\fB\-LP\fP] \fIold new\fP" +The string \fInew\fP is substituted for \fIold\fP in the current +directory, and the shell attempts to change to the new directory. +.\"}}} +.\"{{{ command [ -pvV ] cmd [arg1 ...] +.IP "\fBcommand\fP [\fB\-pvV\fP] \fIcmd\fP [\fIarg1\fP ...]" +If neither the \fB\-v\fP nor \fB\-V\fP options are given, +\fIcmd\fP +is executed exactly as if the \fBcommand\fP had not been specified, +with two exceptions: first, \fIcmd\fP 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 \fB\-p\fP option is given, a default search path is used instead of +the current value of \fBPATH\fP (the actual value of the default path is +system dependent: on POSIXish systems, it is the value returned by +.ce +\fBgetconf CS_PATH\fP +). +.sp +If the \fB\-v\fP option is given, instead of executing \fIcmd\fP, information +about what would be executed is given (and the same is done for +\fIarg1\fP ...): +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 \fBPATH\fP parameter, +the full path of the command is printed. +If no command is be found, (i.e., the path search fails), nothing is printed +and \fBcommand\fP exits with a non-zero status. +The \fB\-V\fP option is like the \fB\-v\fP option, except it is more verbose. +.\"}}} +.\"{{{ continue [levels] +.IP "\fBcontinue\fP [\fIlevels\fP]" +\fBcontinue\fP jumps to the beginning of the \fIlevel\fPth inner most for, +select, until, or while loop. +\fIlevel\fP defaults to 1. +.\"}}} +.\"{{{ echo [-neE] [arg ...] +.IP "\fBecho\fP [\fB\-neE\fP] [\fIarg\fP ...]" +Prints its arguments (separated by spaces) followed by a newline, to +standard out. +The newline is suppressed if any of the arguments contain the backslash +sequence \fB\ec\fP. +See \fBprint\fP command below for a list of other backslash sequences +that are recognized. +.sp +The options are provided for compatibility with BSD shell scripts: +\fB\-n\fP suppresses the trailing newline, \fB\-e\fP enables backslash +interpretation (a no-op, since this is normally done), and \fB\-E\fP which +suppresses backslash interpretation. +.\"}}} +.\"{{{ eval command ... +.IP "\fBeval\fP \fIcommand ...\fP" +The arguments are concatenated (with spaces between them) to form +a single string which the shell then parses and executes +in the current environment. +.\"}}} +.\"{{{ exec [command [arg ...]] +.IP "\fBexec\fP [\fIcommand\fP [\fIarg\fP ...]]" +The command is executed without forking, replacing the shell process. +.sp +If no arguments are given, any IO redirection is permanent and the shell +is not replaced. +Any file descriptors greater than 2 which are opened or \fIdup\fP(2)-ed +in this way are not made available to other executed commands (i.e., +commands that are not built-in to the shell). +.\"}}} +.\"{{{ exit [status] +.IP "\fBexit\fP [\fIstatus\fP]" +The shell exits with the specified exit status. +If \fIstatus\fP is not specified, the exit status is the current +value of the \fB?\fP parameter. +.\"}}} +.\"{{{ export [-p] [parameter[=value] ...] +.IP "\fBexport\fP [\fB\-p\fP] [\fIparameter\fP[\fB=\fP\fIvalue\fP]] ..." +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 also assigned. +.sp +If no parameters are specified, the names of all parameters with the export +attribute are printed one per line, unless the \fB\-p\fP option is used, +in which case \fBexport\fP commands defining all exported +parameters, including their values, are printed. +.\"}}} +.\"{{{ false +.IP "\fBfalse\fP" +A command that exits with a non-zero status. +.\"}}} +.\"{{{ fc [-e editor | -l [-n]] [-r] [first [ last ]] +.IP "\fBfc\fP [\fB\-e\fP \fIeditor\fP | \fB\-l\fP [\fB\-n\fP]] [\fB\-r\fP] [\fIfirst\fP [\fIlast\fP]]" +\fIfirst\fP and \fIlast\fP 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 \fB\-l\fP option lists the command on stdout, +and \fB\-n\fP inhibits the default command numbers. The \fB\-r\fP +option reverses the order of the list. Without \fB\-l\fP, the selected +commands are edited by the editor specified with the \fB\-e\fP +option, or if no \fB\-e\fP is specified, the editor specified by the +\fBFCEDIT\fP parameter (if this parameter is not set, \fB/bin/ed\fP is used), +and then executed by the shell. +.\"}}} +.\"{{{ fc [-e - | -s] [-g] [old=new] [prefix] +.IP "\fBfc\fP [\fB\-e \-\fP | \fB\-s\fP] [\fB\-g\fP] [\fIold\fP\fB=\fP\fInew\fP] [\fIprefix\fP]" +Re-execute the selected command (the previous command by default) after +performing the optional substitution of \fIold\fP with \fInew\fP. If +\fB\-g\fP is specified, all occurrences of \fIold\fP are replaced with +\fInew\fP. This command is usually accessed with the predefined alias +\fBr='fc \-e \-'\fP. +.\"}}} +.\"{{{ fg [job ...] +.IP "\fBfg\fP [\fIjob\fP ...]" +Resume the specified job(s) in the foreground. +If no jobs are specified, \fB%+\fP is assumed. +This command is only available on systems which support job control. +See Job Control below for more information. +.\"}}} +.\"{{{ getopts optstring name [arg ...] +.IP "\fBgetopts\fP \fIoptstring\fP \fIname\fP [\fIarg\fP ...]" +\fBgetopts\fP is used by shell procedures to parse the specified arguments +(or positional parameters, if no arguments are given) and to check for legal +options. +\fIoptstring\fP contains the option letters that +\fBgetopts\fP is to recognize. If a letter is followed by a colon, the +option is expected to have an argument. +Arguments containing options must all start with either a \fB\-\fP or +a \fB+\fP, 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. +.sp +Each time \fBgetopts\fP is invoked, it places the next option in +the shell parameter \fIname\fP and the index of the next argument to be +processed in the shell parameter \fBOPTIND\fP. +If the option was introduced with a \fB+\fP, the option placed in +\fIname\fP is prefixed with a \fB+\fP. +When an option requires an argument, \fBgetopts\fP places it in the +shell parameter \fBOPTARG\fP. +When an illegal option or a missing option argument is +encountered a question mark or a colon is placed in \fIname\fP +(indicating an illegal option or missing argument, respectively) +and \fBOPTARG\fP is set to the option character that caused the problem. +An error message is also printed to standard error if \fIoptstring\fP +does not begin with a colon. +.sp +When the end of the options is encountered, \fBgetopts\fP exits with a +non-zero exit status. +Options end at the first (non-option argument) argument that does not +start with a \-, or when a \fB\-\-\fP argument is encountered. +.sp +Option parsing can be reset by setting \fBOPTIND\fP to 1 (this is done +automatically whenever the shell or a shell procedure is invoked). +.sp +Warning: Changing the value of the shell parameter \fBOPTIND\fP to +a value other than 1, or parsing different sets of arguments without +resetting \fBOPTIND\fP may lead to unexpected results. +.\"}}} +.\"{{{ hash [-r] [name ...] +.IP "\fBhash\fP [\fB\-r\fP] [\fIname ...\fP]" +Without arguments, any hashed executable command pathnames are listed. +The \fB\-r\fP option causes all hashed commands to be removed +from the hash table. +Each \fIname\fP is searched as if it where a command name and added to the +hash table if it is an executable command. +.\"}}} +.\"{{{ jobs [-lpn] [job ...] +.IP "\fBjobs\fP [\fB\-lpn\fP] [\fIjob\fP ...]" +Display information about the specified jobs; if no jobs are specified, +all jobs are displayed. +The \fB\-n\fP option causes information to be displayed only for jobs +that have changed state since the last notification. +If the \fB\-l\fP option is used, the process-id of each process in a job +is also listed. +The \fB\-p\fP option causes only the process group of each job to be printed. +See Job Control below for the format of \fIjob\fP and the displayed job. +.\"}}} +.\"{{{ kill [-s signame | -signum | -signame] { job | pid | -pgrp } ... +.IP "\fBkill\fP [\fB\-s\fP \fIsigname\fP | \fB\-signum\fP | \fB\-signame\fP ] { \fIjob\fP | \fIpid\fP | \fB\-\fP\fIpgrp\fP } ..." +Send the specified signal to the specified jobs, process ids, or process groups. +If no signal is specified, the signal TERM is sent. +If a job is specified, the signal is sent to the job's process group. +See Job Control below for the format of \fIjob\fP. +.\"}}} +.\"{{{ kill -l [exit-status ...] +.IP "\fBkill \-l\fP [\fIexit-status\fP ...]" +Print the name of the signal that killed a process which exited with +the specified \fIexit-status\fPes. +If no arguments are specified, a list of all the signals, their numbers and +a short description of them are printed. +.\"}}} +.\"{{{ let [expression ...] +.IP "\fBlet\fP [\fIexpression\fP ...]" +Each expression is evaluated, see 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, \fB((\fP \fIexpr\fP \fB))\fP is syntactic sugar for \fBlet +"\fP\fIexpr\fP\fB"\fP. +.\"}}} +.\"{{{ print [-nprsun | -R [-en]] [argument ...] +.IP "\fBprint\fP [\fB\-nprsu\fP\fIn\fP | \fB\-R\fP [\fB\-en\fP]] [\fIargument ...\fP]" +\fBPrint\fP prints its arguments on the standard output, separated by +spaces, and terminated with a newline. The \fB\-n\fP option suppresses +the newline. By default, certain C escapes are translated. These +include \eb, \ef, \en, \er, \et, \ev, and \e0### (# is an octal digit, of +which there may be 0 to 3). +\ec is equivalent to using the \fB\-n\fP option. \e expansion may be +inhibited with the \fB\-r\fP option. +The \fB\-s\fP option prints to the history file instead of standard output, +the \fB\-u\fP option prints to file descriptor \fIn\fP (\fIn\fP +defaults to 1 if omitted), and the \fB\-p\fP option prints to the co-process +(see Co-Processes above). +.sp +The \fB\-R\fP option is used to emulate, to some degree, the BSD echo +command, which does not process \e sequences unless the \fB\-e\fP option +is given. +As above, the \fB\-n\fP option suppresses the trailing newline. +.\"}}} +.\"{{{ pwd [-LP] +.IP "\fBpwd\fP [\fB\-LP\fP]" +Print the present working directory. +If \fB\-L\fP option is used or if the \fBphysical\fP option +(see \fBset\fP command below) isn't set, the logical path is printed +(i.e., the path used to \fBcd\fP to the current directory). +If \fB\-P\fP option (physical path) is used or if the \fBphysical\fP option +is set, the path determined from the filesystem (by following \fB..\fP +directories to the root directory) is printed. +.\"}}} +.\"{{{ read [-prsun] [parameter ...] +.IP "\fBread\fP [\fB\-prsu\fP\fIn\fP] [\fIparameter ...\fP]" +Reads a line of input from standard input, separate the line into fields using +the \fBIFS\fP parameter (see Substitution above), and assign each field to the +specified parameters. +If there are more parameters than fields, the extra parameters are set to 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 \fBREPLY\fP parameter is used. +If the input line ends in a backslash and the \fB\-r\fP option was not used, the +backslash and newline are stripped and more input is read. +If no input is read, \fBread\fP exits with a non-zero status. +.sp +A prompt, which is printed to standard error before any input is read, may be +specified by appending and question mark and the prompt to the +first parameter (e.g., \fBread nfoo?'number of foos: '\fP). +.sp +The \fB\-u\fP\fIn\fP and \fB\-p\fP options cause input to be read +from file descriptor \fIn\fP or the current co-process, respectively. +If the \fB\-s\fP option is used, input is saved to the history file. +.\"}}} +.\"{{{ readonly [-p] [parameter[=value] ...] +.IP "\fBreadonly\fP [\fB\-p\fP] [\fIparameter\fP[\fB=\fP\fIvalue\fP]] ..." +Sets the readonly attribute of the named parameters. If values are given, +parameters are set to them before setting the attribute. +Once a parameter is made readonly, it cannot be unset and its value cannot +be changed. +.sp +If no parameters are specified, the names of all parameters with the readonly +attribute are printed one per line, unless the \fB\-p\fP option is used, +in which case \fBreadonly\fP commands defining all readonly +parameters, including their values, are printed. +.\"}}} +.\"{{{ return [status] +.IP "\fBreturn\fP [\fIstatus\fP]" +Returns from a function or \fB.\fP script, with exit status \fIstatus\fP. +If no \fIstatus\fP is given, the exit status of the last executed command +is used. +If used outside of a function or \fB.\fP script, it has the same effect +as \fBexit\fP. +Note that pdksh treats both profile and \fB$ENV\fP files as \fB.\fP scripts, +while the original Korn shell only treats profiles as \fB.\fP scripts. +.\"}}} +.\"{{{ set [+-abCefhkmnpsuvxX] [+-o [option]] [+-A name] [--] [arg ...] +.IP "\fBset\fP [\fB\(+-abCefhkmnpsuvxX\fP] [\fB\(+-o\fP [\fIoption\fP]] [\fB\(+-A\fP \fIname\fP] [\fB\-\-\fP] [\fIarg\fP ...]" +The set command can be used to set (\fB\-\fP) or clear (\fB+\fP) shell options, +set the positional parameters, or set an array parameter. +Options can be changed using the \fB\(+-o\fP \fIoption\fP syntax, +where \fIoption\fP is the long name of an option, or using +the \fB\(+-\fP\fIletter\fP syntax, where \fIletter\fP 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. +.sp +.TS +expand; +afB lfB lw(3i). +\-A T{ +Sets the elements of the array parameter \fIname\fP to \fIarg\fP ...; +If \fB\-A\fP is used, the array is reset (i.e., emptied) first; +if \fB+A\fP is used, the first N elements are set (where N is the number +of \fIarg\fPs), the rest are left untouched. +T} +\-a allexport T{ +all new parameters are created with the export attribute +T} +\-b notify T{ +Print job notification messages asynchronously, instead of just before the +prompt. +Only used if job control is enabled (\fB\-m\fP). +T} +\-C noclobber T{ +Prevent \fB>\fP redirection from overwriting existing files (\fB>|\fP must +be used to force an overwrite). +T} +\-e errexit T{ +Exit (after executing the \fBERR\fP 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 \fBif\fP, \fBuntil\fP, \fBwhile\fP, \fB&&\fP or +\fB||\fP statements. +T} +\-f noglob T{ +Do not expand file name patterns. +T} +\-h trackall T{ +Create tracked aliases for all executed commands (see Aliases above). +On by default for non-interactive shells. +T} +\-i interactive T{ +Enable interactive mode \- this can only be set/unset when the shell is +invoked. +T} +\-k keyword T{ +Parameter assignments are recognized anywhere in a command. +T} +\-l login T{ +The shell is a login shell \- this can only be set/unset when the shell is +invoked (see Shell Startup above). +T} +\-m monitor T{ +Enable job control (default for interactive shells). +T} +\-n noexec T{ +Do not execute any commands \- useful for checking the syntax of scripts +(ignored if interactive). +T} +\-p privileged T{ +Set automatically if, when the shell starts, the read uid or gid does not +match the effective uid or gid, respectively. +See Shell Startup above for a description of what this +means. +T} +-r restricted T{ +Enable restricted mode \(em this option can only be used when the shell is +invoked. See Shell Startup above for a description of what this +means. +T} +\-s stdin T{ +If used when the shell is invoked, commands are read from standard input. +Set automatically if the shell is invoked with no arguments. +.sp +When \fB\-s\fP is used in the \fBset\fP command, it causes the specified +arguments to be sorted before assigning them to the positional parameters +(or to array \fIname\fP, if \fB\-A\fP is used). +T} +\-u nounset T{ +Referencing of an unset parameter is treated as an error, unless +one of the \fB\-\fP, \fB+\fP or \fB=\fP modifiers is used. +T} +\-v verbose T{ +Write shell input to standard error as it is read. +T} +\-x xtrace T{ +Print commands and parameter assignments when they are executed, +preceded by the value of \fBPS4\fP. +T} +\-X markdirs T{ +Mark directories with a trailing \fB/\fP during file name generation. +T} + bgnice T{ +Background jobs are run with lower priority. +T} + braceexpand T{ +Enable brace expansion (aka, alternation). +T} + emacs T{ +Enable BRL emacs-like command line editing (interactive shells only); +see Emacs Interactive Input Line Editing. +T} + gmacs T{ +Enable gmacs-like (Gosling emacs) command line editing (interactive shells +only); +currently identical to emacs editing except that transpose (^T) acts +slightly differently. +T} + ignoreeof T{ +The shell will not exit on when end-of-file is read, \fBexit\fP must be used. +T} + nohup T{ +Do not kill running jobs with a \fBHUP\fP 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 \fBHUP\fP signal). +T} + nolog T{ +No effect \- in the original Korn shell, this prevents function definitions +from being stored in the history file. +T} + physical T{ +Causes the \fBcd\fP and \fBpwd\fP commands to use `physical' +(i.e., the filesystem's) \fB..\fP directories instead of `logical' directories +(i.e., the shell handles \fB..\fP, which allows the user to be obliveous of +symlink links to directories). Clear by default. Note that setting +this option does not effect the current value of the \fBPWD\fP parameter; +only the \fBcd\fP command changes \fBPWD\fP. +See the \fBcd\fP and \fBpwd\fP commands above for more details. +T} + posix T{ +Enable posix mode. See POSIX Mode above. +T} + vi T{ +Enable vi-like command line editing (interactive shells only). +T} + viraw T{ +No effect \- in the original Korn shell, unless viraw was set, the vi command +line mode would let the tty driver do the work until ESC (^[) was entered. +pdksh is always in viraw mode. +T} + vi-show8 T{ +Prefix characters with the eighth bit set with `M-'. +If this option is not set, characters in the range +128-160 are printed as is, which may cause problems. +T} + vi-tabcomplete T{ +In vi command line editing, do command / file name completion when +tab (^I) is entered in insert mode. +T} +.TE +.sp +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 \fB\-\fP. +\fBset -o\fP with no option name will list all the options and whether each +is on or off; \fBset +o\fP will print the long names of all options that +are currently on. +.sp +Remaining arguments, if any, are positional parameters and are assigned, +in order, to the +positional parameters (i.e., \fB1\fP, \fB2\fP, etc.). +If options are ended with \fB\-\-\fP and there are no remaining arguments, +all positional parameters are cleared. +If no options or arguments are given, then the values of all names are printed. +For unknown historical reasons, a lone \fB\-\fP option is treated specially: +it clears both the \fB\-x\fP and \fB\-v\fP options. +.\"}}} +.\"{{{ shift [number] +.IP "\fBshift\fP [\fInumber\fP]" +The positional parameters \fInumber\fP+1, \fInumber\fP+2 etc.\& are renamed +to \fB1\fP, \fB2\fP, etc. +\fInumber\fP defaults to 1. +.\"}}} +.\"{{{ test expression, [ expression ] +.IP "\fBtest\fP \fIexpression\fP" +.IP "\fB[\fP \fIexpression\fP \fB]\fP" +\fBtest\fP evaluates the \fIexpression\fP and returns zero status if +true, and 1 status if false and greater than 1 if there was an error. +It is normally used as the +condition command of \fBif\fP and \fBwhile\fP statements. +The following basic expressions are available: +.sp +.TS +afB ltw(2.8i). +\fIstr\fP T{ +\fIstr\fP has non-zero length. Note that there is the potential +for problems if \fIstr\fP turns out to be an operator (e.g., \fB-r\fP) +- it is generally better to use a test like +.RS +\fB[ X"\fP\fIstr\fP\fB" != X ]\fP +.RE +instead (double quotes are used in case \fIstr\fP contains spaces or file +globing characters). +T} +\-r \fIfile\fP T{ +\fIfile\fP exists and is readable +T} +\-w \fIfile\fP T{ +\fIfile\fP exists and is writable +T} +\-x \fIfile\fP T{ +\fIfile\fP exists and is executable +T} +\-a \fIfile\fP T{ +\fIfile\fP exists +T} +\-e \fIfile\fP T{ +\fIfile\fP exists +T} +\-f \fIfile\fP T{ +\fIfile\fP is a regular file +T} +\-d \fIfile\fP T{ +\fIfile\fP is a directory +T} +\-c \fIfile\fP T{ +\fIfile\fP is a character special device +T} +\-b \fIfile\fP T{ +\fIfile\fP is a block special device +T} +\-p \fIfile\fP T{ +\fIfile\fP is a named pipe +T} +\-u \fIfile\fP T{ +\fIfile\fP's mode has setuid bit set +T} +\-g \fIfile\fP T{ +\fIfile\fP's mode has setgid bit set +T} +\-k \fIfile\fP T{ +\fIfile\fP's mode has sticky bit set +T} +\-s \fIfile\fP T{ +\fIfile\fP is not empty +T} +\-O \fIfile\fP T{ +\fIfile\fP's owner is the shell's effective user-ID +T} +\-G \fIfile\fP T{ +\fIfile\fP's group is the shell's effective group-ID +T} +\-h \fIfile\fP T{ +\fIfile\fP is a symbolic link +T} +\-H \fIfile\fP T{ +\fIfile\fP is a context dependent directory (only useful on HP-UX) +T} +\-L \fIfile\fP T{ +\fIfile\fP is a symbolic link +T} +\-S \fIfile\fP T{ +\fIfile\fP is a socket +T} +\-o \fIoption\fP T{ +shell \fIoption\fP is set (see \fBset\fP command above for list of options). +As a non-standard extension, if the option starts with a \fB!\fP, the test +is negated; the test always fails if option doesn't exist (thus +.RS +\fB[ -o \fP\fIfoo\fP \fB-o -o !\fP\fIfoo\fP \fB]\fP +.RE +returns true if and only if option \fIfoo\fP exists). +T} +\fIfile\fP \-nt \fIfile\fP T{ +first \fIfile\fP is newer than second \fIfile\fP +T} +\fIfile\fP \-ot \fIfile\fP T{ +first \fIfile\fP is older than second \fIfile\fP +T} +\fIfile\fP \-ef \fIfile\fP T{ +first \fIfile\fP is the same file as second \fIfile\fP +T} +\-t [\fIfd\fP] T{ +file descriptor is a tty device. +Default value of \fIfd\fP is 1. +T} +\fIstring\fP T{ +\fIstring\fP is not empty +T} +\-z \fIstring\fP T{ +\fIstring\fP is empty +T} +\-n \fIstring\fP T{ +\fIstring\fP is not empty +T} +\fIstring\fP = \fIstring\fP T{ +strings are equal +T} +\fIstring\fP != \fIstring\fP T{ +strings are not equal +T} +\fInumber\fP \-eq \fInumber\fP T{ +numbers compare equal +T} +\fInumber\fP \-ne \fInumber\fP T{ +numbers compare not equal +T} +\fInumber\fP \-ge \fInumber\fP T{ +numbers compare greater than or equal +T} +\fInumber\fP \-gt \fInumber\fP T{ +numbers compare greater than +T} +\fInumber\fP \-le \fInumber\fP T{ +numbers compare less than or equal +T} +\fInumber\fP \-lt \fInumber\fP T{ +numbers compare less than +T} +.TE +.sp +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): +.sp +.TS +afB l. +\fIexpr\fP \-o \fIexpr\fP logical or +\fIexpr\fP \-a \fIexpr\fP logical and +! \fIexpr\fP logical not +( \fIexpr\fP ) grouping +.TE +.sp +On operating systems not supporting \fB/dev/fd/\fP\fIn\fP devices +(where \fIn\fP is a file descriptor number), +the \fBtest\fP command will attempt to fake it for all tests that +operate on files (except the \fB-e\fP test). +I.e., \fB[ -w /dev/fd/2 ]\fP tests if file descriptor 2 is writable. +.sp +Note that some special rules are applied (courtesy of POSIX) if the +number of arguments to \fBtest\fP or \fB[\fP \&... \fB]\fP is less than +five: if leading \fB!\fP 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 \fB!\fP 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 first argument is a unary +operator, including an unstripped \fB!\fP). +.sp +\fBNote:\fP A common mistake is to use \fBif [ $foo = bar ]\fP which +fails if parameter \fBfoo\fP is null or unset, if it has embedded spaces +(i.e., \fBIFS\fP characters), or if it is a unary operator like \fB!\fP or +\fB\-n\fP. Use tests like \fBif [ "X$foo" = Xbar ]\fP instead. +.\"}}} +.\"{{{ times +.IP \fBtimes\fP +Print the accumulated user and system times used by the shell and by +processes which have exited that the shell started. +.\"}}} +.\"{{{ trap [handler signal ...] +.IP "\fBtrap\fP [\fIhandler\fP \fIsignal ...\fP]" +Sets trap handler that is to be executed when any of the specified signals +are received. +\fBHandler\fP is either a null string, indicating the signals are to +be ignored, a minus (\fB\-\fP), indicating that the default action is to +be taken for the signals (see signal(2 or 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 \fBPS1\fP prompt) after +receipt of one of the signals. +\fBSignal\fP is the name of a signal (e.g., PIPE or ALRM) or the number +of the signal (see \fBkill \-l\fP command above). +There are two special signals: \fBEXIT\fP (also known as \fB0\fP), which +is executed when the shell is about to exit, and \fBERR\fP which is +executed after an error occurs (an error is something that would cause +the shell to exit if the \fB\-e\fP or \fBerrexit\fP option were set \(em +see \fBset\fP command above). +\fBEXIT\fP 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. +.sp +With no arguments, \fBtrap\fP lists, as a series of \fBtrap\fP commands, +the current state of the traps that have been set since the shell started. +.sp +.\" todo: add these features (trap DEBUG, trap ERR/EXIT in function) +The original Korn shell's \fBDEBUG\fP trap and the handling of \fBERR\fP and +\fBEXIT\fP traps in functions are not yet implemented. +.\"}}} +.\"{{{ true +.IP \fBtrue\fP +A command that exits with a zero value. +.\"}}} +.\"{{{ typeset [[+-Ulrtux] [-L[n]] [-R[n]] [-Z[n]] [-i[n]] | -f [-tux]] [name[=value] ...] +.IP "\fBtypeset\fP [[\(+-Ulrtux] [\fB\-L\fP[\fIn\fP]] [\fB\-R\fP[\fIn\fP]] [\fB\-Z\fP[\fIn\fP]] [\fB\-i\fP[\fIn\fP]] | \fB\-f\fP [\fB\-tux\fP]] [\fIname\fP[\fB=\fP\fIvalue\fP] ...]" +Display or set parameter attributes. +With no \fIname\fP arguments, parameter attributes are displayed: if no options +arg used, the current attributes of all parameters are printed as typeset +commands; if an option is given (or \fB\-\fP with no option letter) +all parameters and their values with the specified attributes are printed; +if options are introduced with \fB+\fP, parameter values are not printed. +.sp +If \fIname\fP arguments are given, the attributes of the named parameters +are set (\fB\-\fP) or cleared (\fB+\fP). +Values for parameters may optionally be specified. +If typeset is used inside a function, any newly created parameters are local +to the function. +.sp +When \fB\-f\fP is used, typeset operates on the attributes of functions. +As with parameters, if no \fIname\fPs are given, functions are listed +with their values (i.e., definitions) unless options are introduced with +\fB+\fP, in which case only the function names are reported. +.sp +.TS +expand; +afB lw(4.5i). +\-L\fIn\fP T{ +Left justify attribute: \fIn\fP specifies the field width. +If \fIn\fP is not specified, the current width of a parameter (or the +width of its first assigned value) is used. +Leading white space (and zeros, if used with the \fB\-Z\fP option) is stripped. +If necessary, values are either truncated or space padded to fit the +field width. +T} +\-R\fIn\fP T{ +Right justify attribute: \fIn\fP specifies the field width. +If \fIn\fP is not specified, the current width of a parameter (or the +width of its first assigned value) is used. +Trailing white space are stripped. +If necessary, values are either stripped of leading characters +or space padded to make them fit the field width. +T} +\-Z\fIn\fP T{ +Zero fill attribute: if not combined with \fB\-L\fP, this is the +same as \fB\-R\fP, except zero padding is used instead of space padding. +T} +\-i\fIn\fP T{ +integer attribute: +\fIn\fP 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. +T} +\-U T{ +unsigned integer attribute: integers are printed as unsigned values +(only useful when combined with the \fB\-i\fP option). +This option is not in the original Korn shell. +T} +\-f T{ +Function mode: display or set functions and their attributes, instead of +parameters. +T} +\-l T{ +Lower case attribute: all upper case characters in values are converted to +lower case. +(In the original Korn shell, this parameter meant `long integer' when used +with the \fB\-i\fP option). +T} +\-r T{ +Readonly attribute: parameters with the this attribute may not be assigned to +or unset. +Once this attribute is set, it can not be turned off. +T} +\-t T{ +Tag attribute: has no meaning to the shell; provided for application use. +.sp +For functions, \fB\-t\fP is the trace attribute. +When functions with the trace attribute are executed, the \fBxtrace\fP (\fB\-x\fP) shell option is temporarily turned on. +T} +\-u T{ +Upper case attribute: all lower case characters in values are converted to +upper case. +(In the original Korn shell, this parameter meant `unsigned integer' when used +with the \fB\-i\fP option, which meant upper case letters would never be used +for bases greater than 10. See the \fB\-U\fP option). +.sp +For functions, \fB\-u\fP is the undefined attribute. See Functions above +for the implications of this. +T} +\-x T{ +Export attribute: parameters (or functions) are placed in the environment of +any executed commands. Exported functions are not implemented yet. +T} +.TE +.\"}}} +.\"{{{ ulimit [-acdfHlmnpsStvw] [value] +.IP "\fBulimit\fP [\fB\-acdfHlmnpsStvw\fP] [\fIvalue\fP]" +Display or set process limits. +If no options are used, the file size limit (\fB\-f\fP) is assumed. +\fBvalue\fP, if specified, may be either be an arithmetic expression or the +word \fBunlimited\fP. +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 \fB\-f\fP limit. +.RS +.IP \fB\-a\fP +Displays all limits; unless \fB\-H\fP is used, soft limits are displayed. +.IP \fB\-H\fP +Set the hard limit only (default is to set both hard and soft limits). +.IP \fB\-S\fP +Set the soft limit only (default is to set both hard and soft limits). +.IP \fB\-c\fP +Impose a size limit of \fIn\fP blocks on the size of core dumps. +.IP \fB\-d\fP +Impose a size limit of \fIn\fP kbytes on the size of the data area. +.IP \fB\-f\fP +Impose a size limit of \fIn\fP blocks on files written by the shell and +its child processes (files of any size may be read). +.IP \fB\-l\fP +Impose a limit of \fIn\fP kbytes on the amount of locked (wired) physical +memory. +.IP \fB\-m\fP +Impose a limit of \fIn\fP kbytes on the amount of physical memory used. +.IP \fB\-n\fP +Impose a limit of \fIn\fP file descriptors that can be open at once. +.IP \fB\-p\fP +Impose a limit of \fIn\fP processes that can be run by the user at any one +time. +.IP \fB\-s\fP +Impose a size limit of \fIn\fP kbytes on the size of the stack area. +.IP \fB\-t\fP +Impose a time limit of \fIn\fP cpu seconds to be used by each process. +.IP \fB\-v\fP +Impose a limit of \fIn\fP kbytes on the amount of virtual memory used; +on some systems this is the maximum allowable virtual address (in bytes, +not kbytes). +.IP \fB\-w\fP +Impose a limit of \fIn\fP kbytes on the amount of swap space used. +.PP +As far as \fBulimit\fP is concerned, a block is 512 bytes. +.RE +.\"}}} +.\"{{{ umask [-S] [mask] +.IP "\fBumask\fP [\fB\-S\fP] [\fImask\fP]" +.RS +Display or set the file permission creation mask, or umask (see \fIumask\fP(2)). +If the \fB\-S\fP option is used, the mask displayed or set is symbolic, +otherwise it is an octal number. +.sp +Symbolic masks are like those used by \fIchmod\fP(1): +.RS +[\fBugoa\fP]{{\fB=+-\fP}{\fBrwx\fP}*}+[\fB,\fP...] +.RE +in which the first group of characters is the \fIwho\fP part, the second +group is the \fIop\fP part, and the last group is the \fIperm\fP part. +The \fIwho\fP part specifies which part of the umask is to be modified. +The letters mean: +.RS +.IP \fBu\fP +the user permissions +.IP \fBg\fP +the group permissions +.IP \fBo\fP +the other permissions (non-user, non-group) +.IP \fBa\fP +all permissions (user, group and other) +.RE +.sp +The \fIop\fP part indicates how the \fIwho\fP permissions are to be modified: +.RS +.IP \fB=\fP +set +.IP \fB+\fP +added to +.IP \fB\-\fP +removed from +.RE +.sp +The \fIperm\fP part specifies which permissions are to be set, added or removed: +.RS +.IP \fBr\fP +read permission +.IP \fBw\fP +write permission +.IP \fBx\fP +execute permission +.RE +.sp +When symbolic masks are 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). +Example: `ug=rwx,o=' sets the mask so files will not be readable, writable +or executable by `others', and is equivalent (on most systems) to the octal +mask `07'. +.RE +.\"}}} +.\"{{{ unalias [-adt] name ... +.IP "\fBunalias\fP [\fB\-adt\fP] [\fIname1\fP ...]" +The aliases for the given names are removed. +If the \fB\-a\fP option is used, all aliases are removed. +If the \fB\-t\fP or \fB\-d\fP options are used, the indicated operations +are carried out on tracked or directory aliases, respectively. +.\"}}} +.\"{{{ unset [-fv] parameter ... +.IP "\fBunset\fP [\fB\-fv\fP] \fIparameter\fP ..." +Unset the named parameters (\fB\-v\fP, the default) or functions (\fB\-f\fP). +.\"}}} +.\"{{{ wait [job] +.IP "\fBwait\fP [\fIjob\fP]" +Wait for the specified job(s) to finish. +The exit status of 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 \fBkill \-l\fP \fIexit-status\fP above); if the last +specified job can't be found (because it never existed, or had already +finished), the exit status of wait is 127. +See Job Control below for the format of \fIjob\fP. +\fBWait\fP will return if a signal for which a trap has been set is received, +or if a HUP, INT or QUIT signal is received. +.sp +If no jobs are specified, \fBwait\fP 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). +.\"}}} +.\"{{{ whence [-pv] [name ...] +.IP "\fBwhence\fP [\fB\-pv\fP] [name ...]" +For each name, the type of command is listed (reserved word, built-in, alias, +function, tracked alias or executable). +If the \fB\-p\fP option is used, a path search done even if \fIname\fP +is a reserved word, alias, etc. +Without the \fB\-v\fP option, \fBwhence\fP is similar to \fBcommand \-v\fP +except that \fBwhence\fP will find reserved words and won't print aliases +as alias commands; +with the \fB\-v\fP option, \fBwhence\fP is the same as \fBcommand \-V\fP. +Note that for \fBwhence\fP, the \fB\-p\fP option does not affect the search +path used, as it does for \fBcommand\fP. +If the type of one or more of the names could not be determined, the +exit status is non-zero. +.\"}}} +.\"}}} +.\"{{{ job control (and its built-in commands) +.SS "Job Control" +Job control refers to the shell's ability to monitor and control \fBjobs\fP, +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 \fBjobs\fP command. +If job control is fully enabled (using \fBset \-m\fP or +\fBset \-o monitor\fP), 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 \fBfg\fP and \fBbg\fP commands, respectively, +and the state of the terminal is saved or restored when a foreground +job is stopped or restarted, respectively. +.sp +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 \fBread\fP cannot be. +.sp +When a job is created, it is assigned a job-number. +For interactive shells, this number is printed inside \fB[\fP..\fB]\fP, +followed by the process-ids of the processes in the job when an asynchronous +command is run. +A job may be referred to in \fBbg\fP, \fBfg\fP, \fBjobs\fP, \fBkill\fP and +\fBwait\fP commands either by the process id of the last process in the +command pipeline (as stored in the \fB$!\fP parameter) or by prefixing the +job-number with a percent sign (\fB%\fP). +Other percent sequences can also be used to refer to jobs: +.sp +.TS +expand; +afB lw(4.5i). +%+ T{ +The most recently stopped job, or, if there are no stopped jobs, the oldest +running job. +T} +%%\fR, \fP% T{ +Same as \fB%+\fP. +T} +%\- T{ +The job that would be the \fB%+\fP job, if the later did not exist. +T} +%\fIn\fP T{ +The job with job-number \fIn\fP. +T} +%?\fIstring\fP T{ +The job containing the string \fIstring\fP (an error occurs if multiple jobs +are matched). +T} +%\fIstring\fP T{ +The job starting with string \fIstring\fP (an error occurs if multiple jobs +are matched). +T} +.TE +.sp +When a job changes state (e.g., a background job finishes or foreground +job is stopped), the shell prints the following status information: +.RS +\fB[\fP\fInumber\fP\fB]\fP \fIflag status command\fP +.RE +where +.IP "\ \fInumber\fP" +is the job-number of the job. +.IP "\ \fIflag\fP" +is \fB+\fP or \fB-\fP if the job is the \fB%+\fP or \fB%-\fP job, +respectively, or space if it is neither. +.IP "\ \fIstatus\fP" +indicates the current state of the job and can be +.RS +.IP "\fBRunning\fP" +the job has neither stopped or exited (note that running does not +necessarily mean consuming CPU time \(em the process could be blocked waiting +for some event). +.IP "\fBDone\fP [\fB(\fP\fInumber\fP\fB)\fP]" +the job exited. \fInumber\fP is the exit status of the job, which is +omitted if the status is zero. +.IP "\fBStopped\fP [\fB(\fP\fIsignal\fP\fB)\fP]" +the job was stopped by the indicated \fIsignal\fP (if no signal is given, +the job was stopped by SIGTSTP). +.IP "\fIsignal-description\fP [\fB(core dumped)\fP]" +the job was killed by a signal (e.g., Memory\ fault, Hangup, etc. \(em use +\fBkill \-l\fP for a list of signal descriptions). +The \fB(core\ dumped)\fP message indicates the process created a core file. +.RE +.IP "\ \fIcommand\fP" +is the command that created the process. +If there are multiple processes in the job, then each process will +have a line showing its \fIcommand\fP and possibly its \fIstatus\fP, +if it is different from the status of the previous process. +.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 \fBHUP\fP signal and the shell exits. +Similarly, if the \fBnohup\fP 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 \fBHUP\fP signal and the shell exits. +.\"}}} +.\"{{{ Emacs Interactive Input Line Editing +.SS "Emacs Interactive Input Line Editing" +When the \fBemacs\fP option is set, interactive input line editing is +enabled. \fBWarning\fP: 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 +new-line. Several editing commands are bound to particular control +characters when the shell is invoked; these bindings can be changed +using the following commands: +.\"{{{ bind +.IP \fBbind\fP +The current bindings are listed. +.\"}}} +.\"{{{ bind string=[editing-command] +.IP "\fBbind\fP \fIstring\fP\fB=\fP[\fIediting-command\fP]" +The specified editing command is bound to the given \fBstring\fP, which +should consist of a control character (which may be written using caret +notation \fB^\fP\fIX\fP), optionally preceded by one of the two prefix +characters. Future input of the \fIstring\fP will cause the editing +command to be immediately invoked. Note that although only two prefix +characters (usually ESC and ^X) are supported, some multi-character +sequences can be supported. The following binds the arrow keys on +an ANSI terminal, or xterm (these are in the default bindings). Of course +some escape sequences won't work out quite this nicely: +.sp +.RS +\fBbind '^[['=prefix\-2 +.br +bind '^XA'=up\-history +.br +bind '^XB'=down\-history +.br +bind '^XC'=forward\-char +.br +bind '^XD'=backward\-char\fP +.RE +.\"}}} +.\"{{{ bind -l +.IP "\fBbind \-l\fP" +Lists the names of the functions to which keys may be bound. +.\"}}} +.\"{{{ bind -m string=[substitute] +.IP "\fBbind \-m\fP \fIstring\fP\fB=\fP[\fIsubstitute\fP]" +The specified input \fIstring\fP will afterwards be immediately +replaced by the given \fIsubstitute\fP string, which may contain +editing commands. +.\"}}} +.PP +The following is a list of editing commands available. +Each description starts with the name of the command, +a \fIn\fP, if the command can be prefixed with a count, +and any keys the command is bound to by default (written using +caret notation, e.g., ASCII ESC character is written as ^[). +A count prefix for a command is entered using the sequence +\fB^[\fP\fIn\fP, where \fIn\fP 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 \fBbind\fP 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., ERASE) are bound to +reasonable substitutes and override the default bindings. +.\"{{{ abort ^G +.IP "\fBabort ^G\fP" +Useful as a response to a request for a \fBsearch-history\fP pattern in +order to abort the search. +.\"}}} +.\"{{{ auto-insert n +.IP "\fBauto-insert\fP \fIn\fP" +Simply causes the character to appear as literal input. Most ordinary +characters are bound to this. +.\"}}} +.\"{{{ backward-char n ^B +.IP "\fBbackward-char\fP \fIn\fP \fB^B\fP" +Moves the cursor backward \fIn\fP characters. +.\"}}} +.\"{{{ backward-word n ^[B +.IP "\fBbackward-word\fP \fIn\fP \fB^[B\fP" +Moves the cursor backward to the beginning of a word; words consist +of alphanumerics, underscore (_) and dollar ($). +.\"}}} +.\"{{{ beginning-of-history ^[< +.IP "\fBbeginning-of-history ^[<\fP" +Moves to the beginning of the history. +.\"}}} +.\"{{{ beginning-of-line ^A +.IP "\fBbeginning-of-line ^A\fP" +Moves the cursor to the beginning of the edited input line. +.\"}}} +.\"{{{ capitalize-word n ^[c, ^[C +.IP "\fBcapitalize-word\fP \fIn\fP \fB^[c\fP, \fB^[C\fP" +Uppercase the first character in the next \fIn\fP words, +leaving the cursor past the end of the last word. +.\"}}} +.\"{{{ complete ^[^[ +.IP "\fBcomplete ^[^[\fP" +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 \fB/\fP 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 audio beep). +.\"}}} +.\"{{{ complete-command ^X^[ +.IP "\fBcomplete-command ^X^[\fP" +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 +\fBcomplete\fP command described above. +.\"}}} +.\"{{{ complete-file ^[^X +.IP "\fBcomplete-file ^[^X\fP" +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 +\fBcomplete\fP command described above. +.\"}}} +.\"{{{ complete-list ^[= +.IP "\fBcomplete-list ^[=\fP" +List the possible completions for the current word. +.\"}}} +.\"{{{ delete-char-backward n ERASE, ^?, ^H +.IP "\fBdelete-char-backward\fP \fIn\fP \fBERASE\fP, \fB^?\fP, \fB^H\fP" +Deletes \fIn\fP characters before the cursor. +.\"}}} +.\"{{{ delete-char-forward n +.IP "\fBdelete-char-forward\fP \fIn\fP" +Deletes \fIn\fP characters after the cursor. +.\"}}} +.\"{{{ delete-word-backward n ^[ERASE, ^[^?, ^[^H, ^[h +.IP "\fBdelete-word-backward\fP \fIn\fP \fB^[ERASE\fP, \fB^[^?\fP, \fB^[^H\fP, \fB^[h\fP" +Deletes \fIn\fP words before the cursor. +.\"}}} +.\"{{{ delete-word-forward n ^[d +.IP "\fBdelete-word-forward\fP \fIn\fP \fB^[d\fP" +Deletes characters after the cursor up to the end of \fIn\fP words. +.\"}}} +.\"{{{ down-history n ^N +.IP "\fBdown-history\fP \fIn\fP \fB^N\fP" +Scrolls the history buffer forward \fIn\fP lines (later). Each input line +originally starts just after the last entry in the history buffer, so +\fBdown-history\fP is not useful until either \fBsearch-history\fP or +\fBup-history\fP has been performed. +.\"}}} +.\"{{{ downcase-word n ^[L, ^[l +.IP "\fBdowncase-word\fP \fIn\fP \fB^[L\fP, \fB^[l\fP" +Lowercases the next \fIn\fP words. +.\"}}} +.\"{{{ end-of-history ^[> +.IP "\fBend-of-history ^[>\fP" +Moves to the end of the history. +.\"}}} +.\"{{{ end-of-line ^E +.IP "\fBend-of-line ^E\fP" +Moves the cursor to the end of the input line. +.\"}}} +.\"{{{ eot ^_ +.IP "\fBeot ^_\fP" +Acts as an end-of-file; this is useful because edit-mode input disables +normal terminal input canonicalization. +.\"}}} +.\"{{{ eot-or-delete n ^D +.IP "\fBeot-or-delete\fP \fIn\fP \fB^D\fP" +Acts as eot if alone on a line; otherwise acts as delete-char-forward. +.\"}}} +.\"{{{ error +.IP "\fBerror\fP" +Error (ring the bell). +.\"}}} +.\"{{{ exchange-point-and-mark ^X^X +.IP "\fBexchange-point-and-mark ^X^X\fP" +Places the cursor where the mark is, and sets the mark to where the +cursor was. +.\"}}} +.\"{{{ expand-file ^[* +.IP "\fBexpand-file ^[*\fP" +Appends a * 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. +.\"}}} +.\"{{{ forward-char n ^F +.IP "\fBforward-char\fP \fIn\fP \fB^F\fP" +Moves the cursor forward \fIn\fP characters. +.\"}}} +.\"{{{ forward-word n ^[f +.IP "\fBforward-word\fP \fIn\fP \fB^[f\fP" +Moves the cursor forward to the end of the \fIn\fPth word. +.\"}}} +.\"{{{ goto-history n ^[g +.IP "\fBgoto-history\fP \fIn\fP \fB^[g\fP" +Goes to history number \fIn\fP. +.\"}}} +.\"{{{ kill-line KILL +.IP "\fBkill-line KILL\fP" +Deletes the entire input line. +.\"}}} +.\"{{{ kill-region ^W +.IP "\fBkill-region ^W\fP" +Deletes the input between the cursor and the mark. +.\"}}} +.\"{{{ kill-to-eol n ^K +.IP "\fBkill-to-eol\fP \fIn\fP \fB^K\fP" +Deletes the input from the cursor to the end of the line if \fIn\fP is +not specified, otherwise deletes characters between the cursor and +column \fIn\fP. +.\"}}} +.\"{{{ list ^[? +.IP "\fBlist ^[?\fP" +Prints a sorted, columnated list of command names or file names +(if any) that can complete the partial word containing the cursor. +Directory names have \fB/\fP appended to them. +.\"}}} +.\"{{{ list-command ^X? +.IP "\fBlist-command ^X?\fP" +Prints a sorted, columnated list of command names (if any) that +can complete the partial word containing the cursor. +.\"}}} +.\"{{{ list-file ^X^Y +.IP "\fBlist-file ^X^Y\fP" +Prints a sorted, columnated list of file names (if any) that can +complete the partial word containing the cursor. File type indicators +are appended as described under \fBlist\fP above. +.\"}}} +.\"{{{ newline ^J and ^M +.IP "\fBnewline ^J\fP, \fB^M\fP" +Causes the current input line to be processed by the shell. The +current cursor position may be anywhere on the line. +.\"}}} +.\"{{{ newline-and-next ^O +.IP "\fBnewline-and-next ^O\fP" +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 up-history or search-history. +.\"}}} +.\"{{{ no-op QUIT +.IP "\fBno-op QUIT\fP" +This does nothing. +.\"}}} +.\"{{{ prefix-1 ^[ +.IP "\fBprefix-1 ^[\fP" +Introduces a 2-character command sequence. +.\"}}} +.\"{{{ prefix-2 ^X and ^[[ +.IP "\fBprefix-2 ^X\fP" +.IP "\fBprefix-2 ^[[\fP" +Introduces a 2-character command sequence. +.\"}}} +.\"{{{ prev-hist-word ^[. ^[_ +.IP "\fBprev-hist-word\fP \fIn\fP \fB^[.\fP, \fB^[_\fP" +The last (\fIn\fPth) word of the previous command is inserted at the cursor. +.\"}}} +.\"{{{ quote ^^ +.IP "\fBquote ^^\fP" +The following character is taken literally rather than as an editing +command. +.\"}}} +.\"{{{ redraw ^L +.IP "\fBredraw ^L\fP" +Reprints the prompt string and the current input line. +.\"}}} +.\"{{{ search-character-backward n ^[^] +.IP "\fBsearch-character-backward\fP \fIn\fP \fB^[^]\fP" +Search backward in the current line for the \fIn\fPth occurance of the +next character typed. +.\"}}} +.\"{{{ search-character-forward n ^] +.IP "\fBsearch-character-forward\fP \fIn\fP \fB^]\fP" +Search forward in the current line for the \fIn\fPth occurance of the +next character typed. +.\"}}} +.\"{{{ search-history ^R +.IP "\fBsearch-history ^R\fP" +Enter incremental search mode. The internal history list is searched +backwards for commands matching the input. An initial \fB^\fP in the +search string anchors the search. The abort key will leave search mode. +Other commands will be executed after leaving search mode. Successive +\fBsearch-history\fP 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. +.\"}}} +.\"{{{ set-mark-command ^[<space> +.IP "\fBset-mark-command ^[\fP<space>" +Set the mark at the cursor position. +.\"}}} +.\"{{{ stuff +.IP "\fBstuff\fP" +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 \fB^T\fP mini-systat feature, for +example. +.\"}}} +.\"{{{ stuff-reset +.IP "\fBstuff-reset\fP" +Acts like \fBstuff\fP, then aborts input the same as an interrupt. +.\"}}} +.\"{{{ transport-chars ^T +.IP "\fBtranspose-chars ^T\fP" +If at the end of line, or if the \fBgmacs\fP 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. +.\"}}} +.\"{{{ up-history n ^P +.IP "\fBup-history\fP \fIn\fP \fB^P\fP" +Scrolls the history buffer backward \fIn\fP lines (earlier). +.\"}}} +.\"{{{ upcase-word n ^[U, ^[u +.IP "\fBupcase-word\fP \fIn\fP \fB^[U\fP, \fB^[u\fP" +Uppercases the next \fIn\fP words. +.\"}}} +.\"{{{ version ^V +.IP "\fBversion ^V\fP" +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). +.\"}}} +.\"{{{ yank ^Y +.IP "\fByank ^Y\fP" +Inserts the most recently killed text string at the current cursor position. +.\"}}} +.\"{{{ yank-pop ^[y +.IP "\fByank-pop ^[y\fP" +Immediately after a \fByank\fP, replaces the inserted text string with +the next previous killed text string. +.\"}}} +.\"}}} +.\"{{{ Vi Interactive Input Line Editing +.\"{{{ introduction +.SS "Vi Interactive Input Line Editing" +The vi command line editor in ksh has basically the same commands as the +vi editor (see \fIvi\fP(1)), with the following exceptions: +.nr P2 \n(PD +.IP \ \ \(bu +you start out in insert mode, +.IP \ \ \(bu +there are file name and command completion commands +(\fB=\fP, \fB\e\fP, \fB*\fP, \fB^X\fP, \fB^E\fP, \fB^F\fP and, +optionally, \fB<tab>\fP), +.IP \ \ \(bu +the \fB_\fP command is different (in ksh it is the last argument command, +in vi it goes to the start of the current line), +.IP \ \ \(bu +the \fB/\fP and \fBG\fP commands move in the opposite direction as the \fBj\fP +command +.IP \ \ \(bu +and commands which don't make sense in a single line editor are not available +(e.g., screen movement commands, ex \fB:\fP commands, etc.). +.nr PD \n(P2 +.LP +Note that the \fB^X\fP stands for control-X; also \fB<esc>\fP, \fB<space>\fP +and \fB<tab>\fP are used for escape, space and tab, respectively (no kidding). +.\"}}} +.\"{{{ modes +.PP +Like vi, there are two modes: insert mode and 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 \fIstty\fP(1)) and have their usual meaning (normal values are in +parentheses): +kill (\fB^U\fP), erase (\fB^?\fP), werase (\fB^W\fP), eof (\fB^D\fP), +intr (\fB^C\fP) and quit (\fB^\e\fP). +In addition to the above, the following characters are also treated +specially in insert mode: +.TS +expand; +afB lw(4.5i). +^H T{ +erases previous character +T} +^V T{ +literal next: the next character typed is not treated specially (can be +used to insert the characters being described here) +T} +^J ^M T{ +end of line: the current line is read, parsed and executed by the shell +T} +<esc> T{ +puts the editor in command mode (see below) +T} +^E T{ +command and file name enumeration (see below) +T} +^F T{ +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. +T} +^X T{ +command and file name expansion (see below) +T} +<tab> T{ +optional file name and command completion (see \fB^F\fP above), enabled with +\fBset \-o vi-tabcomplete\fP +T} +.TE +.\"}}} +.\"{{{ display +.PP +If a line is longer that the screen width (see \fBCOLUMNS\fP parameter), +a \fB>\fP, \fB+\fP or \fB<\fP 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. +.\"}}} +.\"{{{ command mode +.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, a \fIn\fP indicates the +command may be prefixed by a number (e.g., \fB10l\fP moves right 10 +characters); if no number prefix is used, \fIn\fP is assumed to be 1 +unless otherwise specified. +The term `current position' refers to the position between the cursor +and the character preceding the cursor. +A `word' is a sequence of letters, digits and underscore characters or a +sequence of non-letter, non-digit, non-underscore, non-white-space characters +(e.g., ab2*&^ contains two words) and a `big-word' is a sequence of +non-white-space characters. +.\"{{{ Special ksh vi commands +.IP "Special ksh vi commands" +The following commands are not in, or are different from, the normal vi file +editor: +.RS +.IP "\fIn\fP\fB_\fP" +insert a space followed by the \fIn\fPth big-word from the last command in the +history at the current position and enter insert mode; if \fIn\fP is not +specified, the last word is inserted. +.IP "\fB#\fP" +insert the comment character (\fB#\fP) at the start of the current line and +return the line to the shell (equivalent to \fBI#^J\fP). +.IP "\fIn\fP\fBg\fP" +like \fBG\fP, except if \fIn\fP is not specified, it goes to the most recent +remembered line. +.IP "\fIn\fP\fBv\fP" +edit line \fIn\fP using the vi editor; +if \fIn\fP is not specified, the current line is edited. +The actual command executed is +`\fBfc \-e ${VISUAL:-${EDITOR:-vi}}\fP \fIn\fP'. +.IP "\fB*\fP and \fB^X\fP" +command or file name expansion is applied to the current big-word +(with an appended *, if the word contains no file globing 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: \fB;\fP, \fB|\fP, \fB&\fP, \fB(\fP, \fB)\fP) +and does not contain a slash (\fB/\fP) then command expansion is done, +otherwise file name expansion is done. +Command expansion will match the big-word against all aliases, functions +and built-in commands as well as any executable files found by searching +the directories in the \fBPATH\fP parameter. +File name expansion matches the big-word against the files in the +current directory. +After expansion, the cursor is placed just past the last word and the editor +is in insert mode. +.IP "\fIn\fP\fB\e\fP, \fIn\fP\fB^F\fP and \fIn\fP\fB<tab>\fP" +command/file name completion: +replace the current big-word with the longest unique +match obtained after performing command/file name expansion. +\fB<tab>\fP is only recognized if the \fBvi-tabcomplete\fP option +is set (see \fBset \-o\fP). +If \fIn\fP is specified, the \fIn\fPth possible +completion is selected (as reported by the command/file name enumeration +command). +.IP "\fB=\fP and \fB^E\fP" +command/file name enumeration: list all the commands or files that match +the current big-word. +.IP "\fB^V\fP" +display the version of pdksh; it is displayed until another key is pressed +(this key is ignored). +.IP "\fB@\fP\fIc\fP" +macro expansion: execute the commands found in the alias _\fIc\fP. +.RE +.\"}}} +.\"{{{ Intra-line movement commands +.IP "Intra-line movement commands" +.RS +.IP "\fIn\fP\fBh\fP and \fIn\fP\fB^H\fP" +move left \fIn\fP characters. +.IP "\fIn\fP\fBl\fP and \fIn\fP\fB<space>\fP" +move right \fIn\fP characters. +.IP "\fB0\fP" +move to column 0. +.IP "\fB^\fP" +move to the first non white-space character. +.IP "\fIn\fP\fB|\fP" +move to column \fIn\fP. +.IP "\fB$\fP" +move to the last character. +.IP "\fIn\fP\fBb\fP" +move back \fIn\fP words. +.IP "\fIn\fP\fBB\fP" +move back \fIn\fP big-words. +.IP "\fIn\fP\fBe\fP" +move forward to the end the word, \fIn\fP times. +.IP "\fIn\fP\fBE\fP" +move forward to the end the big-word, \fIn\fP times. +.IP "\fIn\fP\fBw\fP" +move forward \fIn\fP words. +.IP "\fIn\fP\fBW\fP" +move forward \fIn\fP big-words. +.IP "\fB%\fP" +find match: the editor looks forward for the nearest parenthesis, +bracket or brace and then moves the to the matching parenthesis, bracket or +brace. +.IP "\fIn\fP\fBf\fP\fIc\fP" +move forward to the \fIn\fPth occurrence of the character \fIc\fP. +.IP "\fIn\fP\fBF\fP\fIc\fP" +move backward to the \fIn\fPth occurrence of the character \fIc\fP. +.IP "\fIn\fP\fBt\fP\fIc\fP" +move forward to just before the \fIn\fPth occurrence of the character \fIc\fP. +.IP "\fIn\fP\fBT\fP\fIc\fP" +move backward to just before the \fIn\fPth occurrence of the character \fIc\fP. +.IP "\fIn\fP\fB;\fP" +repeats the last \fBf\fP, \fBF\fP, \fBt\fP or \fBT\fP command. +.IP "\fIn\fP\fB,\fP" +repeats the last \fBf\fP, \fBF\fP, \fBt\fP or \fBT\fP command, but moves +in the opposite direction. +.RE +.\"}}} +.\"{{{ Inter-line movement commands +.IP "Inter-line movement commands" +.RS +.IP "\fIn\fP\fBj\fP and \fIn\fP\fB+\fP and \fIn\fP\fB^N\fP" +move to the \fIn\fPth next line in the history. +.IP "\fIn\fP\fBk\fP and \fIn\fP\fB-\fP and \fIn\fP\fB^P\fP" +move to the \fIn\fPth previous line in the history. +.IP "\fIn\fP\fBG\fP" +move to line \fIn\fP in the history; if \fIn\fP is not specified, the +number first remembered line is used. +.IP "\fIn\fP\fBg\fP" +like \fBG\fP, except if \fIn\fP is not specified, it goes to the most recent +remembered line. +.IP "\fIn\fP\fB/\fP\fIstring\fP" +search backward through the history for the \fIn\fPth line containing +\fIstring\fP; if \fIstring\fP starts with \fB^\fP, the remainder of the +string must appear at the start of the history line for it to match. +.IP "\fIn\fP\fB?\fP\fIstring\fP" +same as \fB/\fP, except it searches forward through the history. +.IP "\fIn\fP\fBn\fP" +search for the \fIn\fPth occurrence of the last search string; the +direction of the search is the same as the last search. +.IP "\fIn\fP\fBN\fP" +search for the \fIn\fPth occurrence of the last search string; the +direction of the search is the opposite of the last search. +.RE +.\"}}} +.\"{{{ Edit commands +.IP "Edit commands" +.RS +.IP "\fIn\fP\fBa\fP" +append text \fIn\fP times: goes into insert mode just after the current +position. +The append is only replicated if command mode is re-entered (i.e., <esc> is +used). +.IP "\fIn\fP\fBA\fP" +same as \fBa\fP, except it appends at the end of the line. +.IP "\fIn\fP\fBi\fP" +insert text \fIn\fP times: goes into insert mode at the current +position. +The insertion is only replicated if command mode is re-entered (i.e., <esc> is +used). +.IP "\fIn\fP\fBI\fP" +same as \fBi\fP, except the insertion is done just before the first non-blank +character. +.IP "\fIn\fP\fBs\fP" +substitute the next \fIn\fP characters (i.e., delete the characters +and go into insert mode). +.IP "\fBS\fP" +substitute whole line: all characters from the first non-blank character +to the end of line are deleted and insert mode is entered. +.IP "\fIn\fP\fBc\fP\fImove-cmd\fP" +change from the current position to the position resulting from \fIn\fP +\fImove-cmd\fPs (i.e., delete the indicated region and go into insert mode); +if \fImove-cmd\fP is \fBc\fP, the line starting from the first non-blank +character is changed. +.IP "\fBC\fP" +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). +.IP "\fIn\fP\fBx\fP" +delete the next \fIn\fP characters. +.IP "\fIn\fP\fBX\fP" +delete the previous \fIn\fP characters. +.IP "\fBD\fP" +delete to the end of the line. +.IP "\fIn\fP\fBd\fP\fImove-cmd\fP" +delete from the current position to the position resulting from +\fIn\fP \fImove-cmd\fPs; +\fImove-cmd\fP is a movement command (see above) or \fBd\fP, in which case +the current line is deleted. +.IP "\fIn\fP\fBr\fP\fIc\fP" +replace the next \fIn\fP characters with the character \fIc\fP. +.IP "\fIn\fP\fBR\fP" +replace: enter insert mode but overwrite existing characters instead of +inserting before existing characters. The replacement is repeated \fIn\fP +times. +.IP "\fIn\fP\fB~\fP" +change the case of the next \fIn\fP characters. +.IP "\fIn\fP\fBy\fP\fImove-cmd\fP" +yank from the current position to the position resulting from \fIn\fP +\fImove-cmd\fPs into the yank buffer; if \fImove-cmd\fP is \fBy\fP, the +whole line is yanked. +.IP "\fBY\fP" +yank from the current position to the end of the line. +.IP "\fIn\fP\fBp\fP" +paste the contents of the yank buffer just after the current position, +\fIn\fP times. +.IP "\fIn\fP\fBP\fP" +same as \fBp\fP, except the buffer is pasted at the current position. +.RE +.\"}}} +.\"{{{ Miscellaneous vi commands +.IP "Miscellaneous vi commands" +.RS +.IP "\fB^J\fP and \fB^M\fP" +the current line is read, parsed and executed by the shell. +.IP "\fB^L\fP and \fB^R\fP" +redraw the current line. +.IP "\fIn\fP\fB.\fP" +redo the last edit command \fIn\fP times. +.IP "\fBu\fP" +undo the last edit command. +.IP "\fBU\fP" +undo all changes that have been made to the current line. +.IP "\fIintr\fP and \fIquit\fP" +the interrupt and quit terminal characters cause the current line to +be deleted and a new prompt to be printed. +.RE +.\"Has all vi commands except: +.\" movement: { } [[ ]] ^E ^Y ^U ^D ^F ^B H L M () +.\" tag commands: ^T ^] +.\" mark commands: m ` ' +.\" named-buffer commands: " @ +.\" file/shell/ex-commands: Q ZZ ^^ : ! & +.\" multi-line change commands: o O J +.\" shift commands: << >> +.\" status command: ^G +.\"}}} +.\"}}} +.\"}}} +.\"}}} +.\"{{{ Files +.SH FILES +~/.profile +.br +/etc/profile +.br +/etc/suid_profile +.\"}}} +.\"{{{ Bugs +.SH BUGS +Any bugs in pdksh should be reported to pdksh@cs.mun.ca. Please +include the version of pdksh (echo $KSH_VERSION shows it), 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 helpful: options you are using (both options.h options and set +\-o options) and a copy of your config.h (the file generated by the +configure script). New versions of pdksh can be obtained from +ftp.cs.mun.ca:pub/pdksh/. +.\"}}} +.\"{{{ Authors +.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 +was created by Eric Gisin, and it was subsequently maintained by +John R.\& MacMillan (chance!john@sq.sq.com), and +Simon J.\& Gerraty (sjg@zen.void.oz.au). The current maintainer is +Michael Rendell (michael@cs.mun.ca). +The CONTRIBUTORS file in the source distribution contains a more complete +list of people and their part in the shell's development. +.\"}}} +.\"{{{ See also +.SH "SEE ALSO" +awk(1), +sh(1), +csh(1), ed(1), getconf(1), getopt(1), sed(1), stty(1), vi(1), +dup(2), execve(2), getgid(2), getuid(2), open(2), pipe(2), wait(2), +getopt(3), rand(3), signal(3), system(3), +environ(5) +.PP +.IR "The KornShell Command and Programming Language" , +Morris Bolsky and David Korn, 1989, ISBN 0-13-516972-0. +.PP +.\" XXX ISBN missing +.IR "UNIX Shell Programming" , +Stephen G.\& Kochan, Patrick H.\& Wood, Hayden. +.PP +.IR "IEEE Standard for information Technology \- Portable Operating System Interface (POSIX) \- Part 2: Shell and Utilities" , +IEEE Inc, 1993, ISBN 1-55937-255-9. +.\"}}} diff --git a/bin/pdksh/ksh_dir.h b/bin/pdksh/ksh_dir.h new file mode 100644 index 00000000000..34c981fedc1 --- /dev/null +++ b/bin/pdksh/ksh_dir.h @@ -0,0 +1,26 @@ +/* $OpenBSD: ksh_dir.h,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* Wrapper around the ugly dir includes/ifdefs */ + +#if defined(HAVE_DIRENT_H) +# include <dirent.h> +# define NLENGTH(dirent) (strlen(dirent->d_name)) +#else +# define dirent direct +# define NLENGTH(dirent) (dirent->d_namlen) +# ifdef HAVE_SYS_NDIR_H +# include <sys/ndir.h> +# endif /* HAVE_SYS_NDIR_H */ +# ifdef HAVE_SYS_DIR_H +# include <sys/dir.h> +# endif /* HAVE_SYSDIR_H */ +# ifdef HAVE_NDIR_H +# include <ndir.h> +# 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/bin/pdksh/ksh_limval.h b/bin/pdksh/ksh_limval.h new file mode 100644 index 00000000000..828c0f82577 --- /dev/null +++ b/bin/pdksh/ksh_limval.h @@ -0,0 +1,24 @@ +/* $OpenBSD: ksh_limval.h,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* Wrapper around the values.h/limits.h includes/ifdefs */ + +#ifdef HAVE_VALUES_H +# include <values.h> +#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/bin/pdksh/ksh_stat.h b/bin/pdksh/ksh_stat.h new file mode 100644 index 00000000000..0c6e5244f57 --- /dev/null +++ b/bin/pdksh/ksh_stat.h @@ -0,0 +1,58 @@ +/* $OpenBSD: ksh_stat.h,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* Wrapper around the ugly sys/stat includes/ifdefs */ + +/* assumes <sys/types.h> already included */ +#include <sys/stat.h> + +#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_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_IFFIFO) +# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFFIFO) +#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/bin/pdksh/ksh_time.h b/bin/pdksh/ksh_time.h new file mode 100644 index 00000000000..21e98b9d147 --- /dev/null +++ b/bin/pdksh/ksh_time.h @@ -0,0 +1,22 @@ +/* $OpenBSD: ksh_time.h,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* Wrapper around the ugly time.h,sys/time.h includes/ifdefs */ + +#ifdef TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else /* TIME_WITH_SYS_TIME */ +# ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# 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 diff --git a/bin/pdksh/ksh_times.h b/bin/pdksh/ksh_times.h new file mode 100644 index 00000000000..45f1b793821 --- /dev/null +++ b/bin/pdksh/ksh_times.h @@ -0,0 +1,13 @@ +/* $OpenBSD: ksh_times.h,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +#include <sys/times.h> + +#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 */ diff --git a/bin/pdksh/ksh_wait.h b/bin/pdksh/ksh_wait.h new file mode 100644 index 00000000000..16fad066768 --- /dev/null +++ b/bin/pdksh/ksh_wait.h @@ -0,0 +1,51 @@ +/* $OpenBSD: ksh_wait.h,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* Wrapper around the ugly sys/wait includes/ifdefs */ + +#ifdef HAVE_SYS_WAIT_H +# include <sys/wait.h> +#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/bin/pdksh/lex.c b/bin/pdksh/lex.c new file mode 100644 index 00000000000..06e912fae09 --- /dev/null +++ b/bin/pdksh/lex.c @@ -0,0 +1,1204 @@ +/* $OpenBSD: lex.c,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* + * lexical analysis and source input + */ + +#include "sh.h" +#include <ctype.h> + +static void readhere ARGS((struct ioword *iop)); +static int getsc_ ARGS((void)); +static void getsc_line ARGS((Source *s)); +static char *get_brace_var ARGS((XString *wsp, char *wp)); +static int arraysub ARGS((char **strp)); +static const char *ungetsc_ ARGS((int c)); +static int getsc_bn_ ARGS((void)); + +static void gethere ARGS((void)); + +/* optimized getsc_() */ +#define getsc() ((*source->str != '\0') ? *source->str++ : getsc_()) +#define getsc_bn() (*source->str != '\0' && *source->str != '\\' \ + ? *source->str++ : getsc_bn_()) +#define ungetsc(c) (source->str > source->start ? source->str-- : ungetsc_(c)) + + +/* + * 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; +{ + register int c, state; + char states [64], *statep = states; /* XXX overflow check */ + XString ws; /* expandable output word */ + register char *wp; /* output word pointer */ + register char *sp, *dp; + char UNINITIALIZED(*ddparen_start); + int istate; + int UNINITIALIZED(c2); + int UNINITIALIZED(nparen), UNINITIALIZED(csstate); + int UNINITIALIZED(ndparen); + int UNINITIALIZED(indquotes); + + + Again: + Xinit(ws, wp, 64, ATEMP); + + if (cf&ONEWORD) + istate = SWORD; + else if (cf&LETEXPR) { + *wp++ = OQUOTE; /* enclose arguments in (double) quotes */ + istate = SDPAREN; + ndparen = 0; + } else { /* normal lexing */ + istate = (cf & HEREDELIM) ? SHEREDELIM : SBASE; + while ((c = getsc()) == ' ' || c == '\t') + ; + if (c == '#') + while ((c = getsc()) != '\0' && c != '\n') + ; + 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; + } + + /* collect non-special or quoted characters to form word */ + for (*statep = state = istate; + !((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; + *++statep = state = SPATTERN; + break; + } + ungetsc(c2); + } +#endif /* KSH */ + /* fall through.. */ + Sbase2: /* doesn't include *(...|...) pattern (*+?@!) */ + switch (c) { + case '\\': + c = getsc(); + if (c != '\n') { +#ifdef OS2 + if (isalnum(c)) { + *wp++ = CHAR, *wp++ = '\\'; + *wp++ = CHAR, *wp++ = c; + } else +#endif + *wp++ = QCHAR, *wp++ = c; + } else + if (wp == Xstring(ws, wp)) { + Xfree(ws, wp); /* free word */ + goto Again; + } + break; + case '\'': + *++statep = state = SSQUOTE; + *wp++ = OQUOTE; + break; + case '"': + *++statep = state = SDQUOTE; + *wp++ = OQUOTE; + break; + default: + goto Subst; + } + break; + + Subst: + switch (c) { + case '\\': + c = getsc(); + switch (c) { + case '\n': + break; + case '"': case '\\': + case '$': case '`': + *wp++ = QCHAR, *wp++ = c; + break; + default: + Xcheck(ws, wp); + *wp++ = CHAR, *wp++ = '\\'; + *wp++ = CHAR, *wp++ = c; + break; + } + break; + case '$': + c = getsc(); + if (c == '(') /*)*/ { + c = getsc(); + if (c == '(') /*)*/ { + *++statep = state = SDDPAREN; + nparen = 2; + ddparen_start = wp; + *wp++ = EXPRSUB; + } else { + ungetsc(c); + *++statep = state = SPAREN; + nparen = 1; + csstate = 0; + *wp++ = COMSUB; + } + } else if (c == '{') /*}*/ { + *wp++ = OSUBST; + wp = get_brace_var(&ws, wp); + /* If this is a trim operation, + * wrap @(...) around the pattern + * (allows easy handling of ${a#b|c}) + */ + c = getsc_bn(); + if (c == '#' || c == '%') { + *wp++ = CHAR, *wp++ = c; + if ((c2 = getsc_bn()) == c) + *wp++ = CHAR, *wp++ = c; + else + ungetsc(c2); + *wp++ = OPAT, *wp++ = '@'; + *++statep = state = STBRACE; + } else { + ungetsc(c); + *++statep = state = SBRACE; + } + } else if (ctype(c, C_ALPHA)) { + *wp++ = OSUBST; + do { + Xcheck(ws, wp); + *wp++ = c; + c = getsc(); + } while (ctype(c, C_ALPHA|C_DIGIT)); + *wp++ = '\0'; + *wp++ = CSUBST; + ungetsc(c); + } else if (ctype(c, C_DIGIT|C_VAR1)) { + Xcheck(ws, wp); + *wp++ = OSUBST; + *wp++ = c; + *wp++ = '\0'; + *wp++ = CSUBST; + } else { + *wp++ = CHAR, *wp++ = '$'; + ungetsc(c); + } + break; + case '`': + *++statep = state = SBQUOTE; + *wp++ = COMSUB; + /* Need to know if we are inside double quotes + * since sh/at&t-ksh translate the \" to " in + * "`..\"..`". + */ + indquotes = 0; + if (!Flag(FPOSIX)) + for (sp = statep; sp > states; --sp) + if (*sp == SDQUOTE) + indquotes = 1; + break; + default: + *wp++ = CHAR, *wp++ = c; + } + break; + + case SSQUOTE: + if (c == '\'') { + state = *--statep; + *wp++ = CQUOTE; + } else + *wp++ = QCHAR, *wp++ = c; + break; + + case SDQUOTE: + if (c == '"') { + state = *--statep; + *wp++ = CQUOTE; + } else + goto Subst; + break; + + case SPAREN: /* $( .. ) */ + /* 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 (csstate) { + case 0: /* normal */ + switch (c) { + case '(': + nparen++; + break; + case ')': + nparen--; + break; + case '\\': + csstate = 1; + break; + case '"': + csstate = 2; + break; + case '\'': + csstate = 4; + break; + } + break; + + case 1: /* backslash in normal mode */ + case 3: /* backslash in double quotes */ + --csstate; + break; + + case 2: /* double quotes */ + if (c == '"') + csstate = 0; + else if (c == '\\') + csstate = 3; + break; + + case 4: /* single quotes */ + if (c == '\'') + csstate = 0; + break; + } + if (nparen == 0) { + state = *--statep; + *wp++ = 0; /* end of COMSUB */ + } else + *wp++ = c; + break; + + case SDDPAREN: /* $(( .. )) */ + /* todo: deal with $((...); (...)) properly */ + /* XXX should nest using existing state machine + * (embed "..", $(...), etc.) */ + if (c == '(') + nparen++; + else if (c == ')') { + nparen--; + if (nparen == 1) { + /*(*/ + if ((c2 = getsc()) == ')') { + state = *--statep; + *wp++ = 0; /* end of EXPRSUB */ + break; + } else { + ungetsc(c2); + /* mismatched parenthesis - + * assume we were really + * parsing a $(..) expression + */ + memmove(ddparen_start + 1, + ddparen_start, + wp - ddparen_start); + *ddparen_start++ = COMSUB; + *ddparen_start = '('; /*)*/ + wp++; + csstate = 0; + *statep = state = SPAREN; + } + } + } + *wp++ = c; + break; + + case SBRACE: + /*{*/ + if (c == '}') { + state = *--statep; + *wp++ = CSUBST; + } else + goto Sbase1; + break; + + case STBRACE: + /* same as SBRACE, except | is saved as SPAT and + * CPAT is added at the end. + */ + /*{*/ + if (c == '}') { + state = *--statep; + *wp++ = CPAT; + *wp++ = CSUBST; + } else if (c == '|') { + *wp++ = SPAT; + } else + goto Sbase1; + break; + + case SBQUOTE: + if (c == '`') { + *wp++ = 0; + state = *--statep; + } else if (c == '\\') { + switch (c = getsc()) { + case '\n': + break; + case '\\': + case '$': case '`': + *wp++ = c; + break; + case '"': + if (indquotes) { + *wp++ = c; + break; + } + /* fall through.. */ + default: + *wp++ = '\\'; + *wp++ = c; + break; + } + } else + *wp++ = c; + break; + + case SWORD: /* ONEWORD */ + goto Subst; + + case SDPAREN: /* LETEXPR: (( ... )) */ + /*(*/ + if (c == ')') { + if (ndparen > 0) + --ndparen; + /*(*/ + 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 + */ + ++ndparen; + goto Sbase2; + + case SHEREDELIM: /* <<,<<- delimiter */ + /* XXX chuck this state (and the next) - use + * the existing states ($ and \`..` should be + * stripped of their specialness after the + * fact). + */ + /* here delimiters need a special case since + * $ and `..` are not to be treated specially + */ + if (c == '\\') { + c = getsc(); + if (c != '\n') { + *wp++ = QCHAR; + *wp++ = c; + } + } else if (c == '\'') { + *++statep = state = SSQUOTE; + *wp++ = OQUOTE; + } else if (c == '"') { + state = SHEREDQUOTE; + *wp++ = OQUOTE; + } else { + *wp++ = CHAR; + *wp++ = c; + } + break; + + case SHEREDQUOTE: /* " in <<,<<- delimiter */ + if (c == '"') { + *wp++ = CQUOTE; + state = SHEREDELIM; + } else { + if (c == '\\' && (c = getsc()) == '\n') + break; + *wp++ = CHAR; + *wp++ = c; + } + break; + + case SPATTERN: /* in *(...|...) pattern (*+?@!) */ + if ( /*(*/ c == ')') { + *wp++ = CPAT; + state = *--statep; + } else if (c == '|') + *wp++ = SPAT; + else + goto Sbase1; + break; + } + } +Done: + Xcheck(ws, wp); + if (state != istate) + yyerror("no closing quote\n"); + + /* This done to avoid tests for SHEREDELIM wherever SBASE tested */ + if (state == SHEREDELIM) + state = SBASE; + + if ((c == '<' || c == '>') && state == SBASE) { + char *cp = Xstring(ws, wp); + if (Xlength(ws, wp) == 2 && cp[0] == CHAR && digit(cp[1])) { + wp = cp; /* throw away word */ + c2/*unit*/ = cp[1] - '0'; + } else + c2/*unit*/ = c == '>'; /* 0 for <, 1 for > */ + } + + if (wp == Xstring(ws, wp) && 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 '>': + case '<': { + register struct ioword *iop; + + iop = (struct ioword *) alloc(sizeof(*iop), ATEMP); + iop->unit = c2/*unit*/; + + c2 = getsc(); + /* <<, >>, <> are ok, >< is not */ + if (c == c2 || (c == '<' && c2 == '>')) { + iop->flag = c == c2 ? + (c == '>' ? IOCAT : IOHERE) : IORDWR; + if (iop->flag == IOHERE) + if (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; + yylval.iop = iop; + return REDIR; + } + case '\n': + gethere(); + if (cf & CONTIN) + goto Again; + return c; + + case '(': /*)*/ + if ((c2 = getsc()) == '(') /*)*/ + c = MDPAREN; + else + ungetsc(c2); + return c; + /*(*/ + case ')': + return c; + } + } + + *wp++ = EOS; /* terminate word */ + yylval.cp = Xclose(ws, wp); + if (state == SWORD || state == SDPAREN) /* 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' paded */ + 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 "<<word" text into temp file + */ + +static void +readhere(iop) + register struct ioword *iop; +{ + struct shf *volatile shf; + struct temp *h; + register int c; + char *volatile eof; + char *eofp; + int skiptabs, bn; + int i; + + eof = evalstr(iop->delim, 0); + + if (e->flags & EF_FUNC_PARSE) { + h = maketemp(APERM); + h->next = func_heredocs; + func_heredocs = h; + } else { + h = maketemp(ATEMP); + h->next = e->temps; + e->temps = h; + } + iop->name = h->name; + if (!(shf = h->shf)) + yyerror("cannot create temporary file %s - %s\n", + h->name, strerror(errno)); + + newenv(E_ERRH); + i = ksh_sigsetjmp(e->jbuf, 0); + if (i) { + quitenv(); + shf_close(shf); + unwind(i); + } + + bn = iop->flag & IOEVAL; + for (;;) { + eofp = eof; + skiptabs = iop->flag & IOSKIP; + while ((c = (bn ? getsc_bn() : getsc())) != 0) { + if (skiptabs) { + if (c == '\t') + continue; + skiptabs = 0; + } + if (c != *eofp) + break; + eofp++; + } + /* Allow EOF here so commands with out trailing newlines + * will work (eg, ksh -c '...', $(...), etc). + */ + if (*eofp == '\0' && (c == 0 || c == '\n')) + break; + ungetsc(c); + shf_write(eof, eofp - eof, shf); + while ((c = (bn ? getsc_bn() : getsc())) != '\n') { + if (c == 0) + yyerror("here document `%s' unclosed\n", eof); + shf_putc(c, shf); + } + shf_putc(c, shf); + } + shf_flush(shf); + if (shf_error(shf)) + yyerror("error saving here document `%s': %s\n", + eof, strerror(shf_errno(shf))); + /*XXX add similar checks for write errors everywhere */ + quitenv(); + shf_close(shf); +} + +void +#ifdef HAVE_PROTOTYPES +yyerror(const char *fmt, ...) +#else +yyerror(fmt, va_alist) + const char *fmt; + va_dcl +#endif +{ + va_list va; + + yynerrs++; + /* 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 */ + s->flags |= SF_ALIAS; + } else { + /* put a fake space at the end of the alias. + * This keeps the current alias in the source + * list so recursive aliases can be detected. + * The addition of a space after an alias + * never affects anything (I think). + */ + s->flags |= SF_ALIASEND; + s->start = s->str = space; + } + continue; + + case SREREAD: + if (s->start != s->u.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); +#ifdef OS2 + setmode (0, O_TEXT); +#endif /* OS2 */ + } 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 */ + } +#ifdef OS2 + setmode(0, O_BINARY); +#endif /* OS2 */ + /* 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 */ + /* 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 *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; + warningf(TRUE, "error during expansion of PS1"); + } else + prompt = str_save(substitute(ps1, 0), + saved_atemp); + quitenv(); + } + 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 */ + if (ntruncate) + shellf("%.*s", ntruncate, cp); + else { + shf_puts(cp, 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_bn(); + /* 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(); + } + } + 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_bn(); + 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; +{ + /* 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->u.ugbuf[0] = c; s->u.ugbuf[1] = '\0'; + s->start = s->str = s->u.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; + + while (1) { + c = getsc_(); + if (c != '\\') + return c; + c = getsc(); + if (c != '\n') { + ungetsc(c); + return '\\'; + } + /* ignore the \newline; get the next char... */ + } +} diff --git a/bin/pdksh/lex.h b/bin/pdksh/lex.h new file mode 100644 index 00000000000..f4517db927f --- /dev/null +++ b/bin/pdksh/lex.h @@ -0,0 +1,128 @@ +/* $OpenBSD: lex.h,v 1.1 1996/08/14 06:19:11 downsj 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 */ + char const *start; /* start of current buffer */ + union { + char ugbuf[2]; /* buffer for ungetsc() (SREREAD) */ + char **strv; /* string [] */ + struct shf *shf; /* shell file */ + struct tbl *tblp; /* alias */ + char *freeme; /* also for SREREAD */ + } u; + int line; /* line number */ + int errline; /* line the error occured 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[] seperator */ +#define SALIAS 7 /* alias expansion */ +#define SREREAD 8 /* read ahead to be re-scanned */ + +/* Source.flags values */ +#define SF_ECHO BIT(0) /* echo input to shlout */ +#define SF_ALIAS BIT(1) /* faking space at end of alias */ +#define SF_ALIASEND BIT(2) /* faking space at end of alias */ +#define SF_TTY BIT(3) /* type == SSTDIN & it is a tty */ + +/* + * states while lexing word + */ +#define SBASE 0 /* outside any lexical constructs */ +#define SWORD 1 /* implicit quoting for substitute() */ +#define SDPAREN 2 /* inside (( )), implicit quoting */ +#define SSQUOTE 3 /* inside '' */ +#define SDQUOTE 4 /* inside "" */ +#define SBRACE 5 /* inside ${} */ +#define SPAREN 6 /* inside $() */ +#define SBQUOTE 7 /* inside `` */ +#define SDDPAREN 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 +#define MDPAREN 277 /* (( )) */ +#define BANG 278 /* ! */ +#define DBRACKET 279 /* [[ .. ]] */ +#define COPROC 280 /* |& */ +#define YYERRCODE 300 + +/* flags to yylex */ +#define CONTIN BIT(0) /* skip new lines to complete command */ +#define ONEWORD BIT(1) /* single word for substitute() */ +#define ALIAS BIT(2) /* recognize alias */ +#define KEYWORD BIT(3) /* recognize keywords */ +#define LETEXPR BIT(4) /* get expression inside (( )) */ +#define VARASN BIT(5) /* check for var=word */ +#define ARRAYVAR BIT(6) /* parse x[1 & 2] as one word */ +#define ESACONLY BIT(7) /* only accept esac keyword */ +#define CMDWORD BIT(8) /* parsing simple command (alias related) */ +#define HEREDELIM BIT(9) /* parsing <<,<<- delimiter */ + +#define HERES 10 /* max << in line */ + +EXTERN Source *source; /* yyparse/yylex source */ +EXTERN YYSTYPE yylval; /* result from yylex */ +EXTERN int yynerrs; +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/bin/pdksh/mail.c b/bin/pdksh/mail.c new file mode 100644 index 00000000000..3422a5873cc --- /dev/null +++ b/bin/pdksh/mail.c @@ -0,0 +1,185 @@ +/* $OpenBSD: mail.c,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* + * Mailbox checking code by Robert J. Gibson, adapted for PD ksh by + * John R. MacMillan + */ + +#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 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; + long mailcheck; + struct tbl *vp; + struct stat stbuf; + + if (getint(global("MAILCHECK"), &mailcheck) < 0) + return; + + now = time((time_t *) 0); + if (mlastchkd == 0) + mlastchkd = now; + if (now - mlastchkd >= mailcheck) { + mlastchkd = now; + + vp = global("MAILPATH"); + if (vp && (vp->flag & ISSET)) + 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 +mbset(p) + register char *p; +{ + struct stat stbuf; + + if (mbox.mb_msg) + afree((void *)mbox.mb_msg, APERM); + mbox.mb_path = p; + 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; + + setstr((vp = local("_", FALSE)), mbp->mb_path); + + shellf("%s\n", substitute(mbp->mb_msg ? mbp->mb_msg : MBMESSAGE, 0)); + + unset(vp, 0); +} diff --git a/bin/pdksh/main.c b/bin/pdksh/main.c new file mode 100644 index 00000000000..10290b31135 --- /dev/null +++ b/bin/pdksh/main.c @@ -0,0 +1,782 @@ +/* $OpenBSD: main.c,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* + * startup, main loop, enviroments 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"; /* must be R/W */ + +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", "-ri", "PPID", NULL, + "typeset", "-i", "OPTIND=1", "MAILCHECK=600", +#ifdef KSH + "SECONDS=0", "RANDOM", "TMOUT=0", +#endif /* KSH */ + NULL, + "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", + "history=fc -l", + "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", + "newgrp=exec newgrp", +#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; + char **wp; + struct env env; + int euid; + +#ifdef MEM_DEBUG + chmem_push("+c", 1); + /*chmem_push("+cd", 1);*/ +#endif + +#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 enviroment */ + env.type = E_NONE; + ainit(&env.area); + env.savefd = NULL; + env.oenv = NULL; + env.loc = (struct block *) 0; + 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 */ + path = def_path; + + + /* Turn on nohup by default for how - will change to off + * by default once people are aware of its existance + * (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 */ + + /* import enviroment */ + 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(pwd_v, current_wd); + } + setint(global("PPID"), (long) getppid()); +#ifdef KSH + setint(global("RANDOM"), (long) time((time_t *)0)); +#endif /* KSH */ + setstr(global(version_param), ksh_version); + + /* execute initialization statements */ + for (wp = (char**) initcoms; *wp != NULL; wp++) { + shcomexec(wp); + for (; *wp != NULL; wp++) + ; + } + + euid = geteuid(); + safe_prompt = euid ? "$ " : "# "; + { + 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) || (!euid && !strchr(str_val(vp), '#'))) + setstr(vp, safe_prompt); + } + + /* Set this before parsing arguments */ + Flag(FPRIVILEGED) = getuid() != euid || 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); + 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 = "<stdin>"; + s->u.shf = shf_fdopen(0, SHF_RD | can_seek(0), + (struct shf *) 0); + if (isatty(0) && isatty(2)) { + Flag(FTALKING) = 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)) + 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; + + /* 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("/etc/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 (Flag(FTALKING)) { + hist_init(s); + alarm_init(); + } 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; + Source *volatile sold; + 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; + } + sold = source; + newenv(E_INCL); + i = ksh_sigsetjmp(e->jbuf, 0); + if (i) { + quitenv(); + source = sold; + if (s) + shf_close(s->u.shf); + 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); + quitenv(); + source = sold; + shf_close(s->u.shf); + if (old_argv) { + e->loc->argv = old_argv; + e->loc->argc = old_argc; + } + return i & 0xff; /* & 0xff to ensure value not -1 */ +} + +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; + int i; + + newenv(E_PARSE); + if (interactive) + really_exit = 0; + i = ksh_sigsetjmp(e->jbuf, 0); + if (i) { + s->start = s->str = null; + 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)... + */ + break; + } + /* fall through... */ + case LEXIT: + case LLEAVE: + case LRETURN: + quitenv(); + unwind(i); /* keep on going */ + /*NOREACHED*/ + default: + 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(); + mcheck(); + set_prompt(PS1, s); + } + + t = compile(s); + if (t != NULL && t->type == TEOF) { + if (wastty && Flag(FIGNOREEOF) && --attempts > 0) { + shellf("Use `exit' to leave ksh\n"); + s->type = SSTDIN; + } else if (wastty && !really_exit + && j_stopped_running()) + { + really_exit = 1; + s->type = SSTDIN; + } else { + /* this for POSIX, which says EXIT traps + * shall be taken in the environment + * immediately after the last command + * executed. + */ + if (toplevel) + unwind(LEXIT); + break; + } + } + + if (t && (!Flag(FNOEXEC) || (s->flags & SF_TTY))) + exstat = execute(t, 0); + + if (t != NULL && t->type != TEOF && interactive && really_exit) + really_exit = 0; + + reclaim(); + } + quitenv(); + 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: /* bottom of the stack */ + { + if (Flag(FTALKING)) + hist_finish(); + j_exit(); + remove_temps(func_heredocs); + if (i == LINTR) { + 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); + } + } + exit(exstat); + /* NOTREACHED */ + } + + 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 == NULL) /* cleanup_parents_env() was called */ + exit(exstat); /* exit child */ + if (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(); + 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 + * anywyere. + */ + + /* 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]); + } + e->oenv = (struct env *) 0; +} + +/* 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 char tmpfile[30]; + int status; + + if (strlen (tmpfile) > 0 ) { + unlink(tmpfile); + *tmpfile=0; + } +#endif /* OS2 */ + + for (; tp != NULL; tp = tp->next) + if (tp->pid == procpid) +#ifdef OS2 + { status=unlink(tp->name); + if (status < 0) + strcpy(tmpfile, tp->name); + } +#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/bin/pdksh/misc.c b/bin/pdksh/misc.c new file mode 100644 index 00000000000..dcf7cc9bf63 --- /dev/null +++ b/bin/pdksh/misc.c @@ -0,0 +1,1313 @@ +/* $OpenBSD: misc.c,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* + * Miscellaneous functions + */ + +#include "sh.h" +#include <ctype.h> /* for FILECHCONV */ +#ifdef HAVE_LIMITS_H +# include <limits.h> +#endif + +#ifndef UCHAR_MAX +# define UCHAR_MAX 0xFF +#endif + +char 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); +} + +/* 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 }, + { null, '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 */ + { null, '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 }, + { "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 */ +#endif + { "xtrace", 'x', OF_ANY }, + { NULL, 0, 0 } +}; + +/* + * translate -o option into F* constant (also used for test -o option) + */ +int +option(n) + const char *n; +{ + int i; + + for (i = 0; options[i].name; i++) + if (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; options[i].name; i++) + if (options[i].name[0]) { + 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); + } else { + /* short version ala ksh93 */ + shprintf("set"); + for (i = 0; options[i].name; i++) + if (Flag(i) && options[i].name[0]) + shprintf(" -o %s", options[i].name); + shprintf(newline); + } +} + +char * +getoptions() +{ + int i; + char m[FNFLAGS + 1]; + register char *cp = m; + + for (i = 0; options[i].name; 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 */ + setuid(getuid()); + setgid(getgid()); +#endif /* OS2 */ + } else if (f == FPOSIX && newval) { +#ifdef BRACE_EXPAND + Flag(FBRACEEXPAND) = 0 +#endif /* BRACE_EXPAND */ + ; + } +} + +/* 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; + Getopt go; + int i, optc, set, sortargs = 0, arrayset = 0; + + /* First call? Build option strings... */ + if (cmd_opts[0] == '\0') { + char *p; + + /* c is also in options[], but it needs a trailing : */ + strcpy(cmd_opts, "o:"); /* see cmd_opts[] declaration */ + p = cmd_opts + strlen(cmd_opts); + for (i = 0; options[i].name; i++) + if (options[i].c && (options[i].flags & OF_CMDLINE)) + *p++ = options[i].c; + *p = '\0'; + + strcpy(set_opts, "Ao;s"); /* see set_opts[] declaration */ + p = set_opts + strlen(set_opts); + for (i = 0; options[i].name; i++) + if (options[i].c && (options[i].flags & OF_SET)) + *p++ = options[i].c; + *p = '\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; + 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; options[i].name; i++) + if (optc == options[i].c + && (what & options[i].flags)) + { + change_flag((enum sh_flag) i, what, + set); + break; + } + if (!options[i].name) { + 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 = argv[go.optind++]; + if (!array) { + bi_errorf("-A: missing array name"); + return -1; + } + if (!*array || *skip_varname(array, FALSE)) { + bi_errorf("%s: is not an identifier", array); + return -1; + } + } else + array = (char *) 0; /* keep gcc happy */ + 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; +{ + const char *s; + register int n; + int sawdigit = 0; + + s = as; + if (*s == '-' || *s == '+') + s++; + for (n = 0; digit(*s); s++, sawdigit = 1) + n = n * 10 + (*s - '0'); + *ai = (*as == '-') ? -n : n; + if (*s || !sawdigit) + return 0; + 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 ] + * - imballenced $(...) 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; + +#ifdef KSH + /* [*+?@!](pattern|pattern|..) */ + 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 */ + 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; +#endif /* KSH */ + + 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: *+?@! */ + } + 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. Once a - or + has been seen, all other options must + * start with the same character. + */ +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 != '-' || (go->info & GI_PLUS)) + && (!(go->flags & GF_PLUSOPT) || (go->info & GI_MINUS) + || flag != '+')) + || (c = arg[1]) == '\0') + { + go->p = 0; + return EOF; + } + go->optind++; + 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: bad 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 attatched 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 attatched or unattatched + * 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 (!letnum(*p) && *p != '/') + 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) + struct shf *shf; + int n; + char *(*func) ARGS((void *, int, char *, int)); + void *arg; + int max_width; +{ + 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 (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 <sys/param.h> +#endif /* HAVE_SYS_PARAM_H */ +#ifndef MAXPATHLEN +# define MAXPATHLEN PATH +#endif /* MAXPATHLEN */ + +/* 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_GETWD + extern char *getwd ARGS((char *)); + char *b; + int len; + + 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; +#else /* HAVE_GETWD */ + char *b; + char *ret; + + /* 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; +#endif /* HAVE_GETWD */ +} diff --git a/bin/pdksh/missing.c b/bin/pdksh/missing.c new file mode 100644 index 00000000000..455403c0de1 --- /dev/null +++ b/bin/pdksh/missing.c @@ -0,0 +1,271 @@ +/* $OpenBSD: missing.c,v 1.1 1996/08/14 06:19:11 downsj 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 <sys/resource.h> +# else /* HAVE_GETRUSAGE */ +# include <sys/timeb.h> +# 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 */ diff --git a/bin/pdksh/options.h b/bin/pdksh/options.h new file mode 100644 index 00000000000..cb4a213bb37 --- /dev/null +++ b/bin/pdksh/options.h @@ -0,0 +1,89 @@ +/* $OpenBSD: options.h,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* + * Options configuration file for the PD ksh + */ + +/* Define this to the path to use if the PATH environment variable is + * not set (ie, either never set or explicitly unset with the unset + * command). A value without . in it is safest. + * THIS DEFINE IS NOT USED if confstr() and _CS_PATH are available or + * if <paths.h> defines _PATH_DEFPATH. + */ +#ifdef OS2 +# define DEFAULT_PATH "c:/usr/bin;c:/os2;/os2" /* OS/2 only */ +#else /* OS2 */ +# define DEFAULT_PATH "/bin:/usr/bin:/usr/ucb" /* Unix */ +#endif /* OS2 */ + + +/* Define KSH to get KSH features; otherwise, you get a fairly basic + * Bourne/POSIXish shell (undefining this results in EMACS, VI and + * COMPLEX_HISTORY being undefined as well, regardless of their setting + * here). + */ +#define KSH + +/* Define EMACS if you want emacs command line editing compiled in (enabled + * with "set -o emacs", or by setting the VISUAL or EDITOR variables to + * something ending in emacs). + */ +#define EMACS + +/* Define VI if you want vi command line editing compiled in (enabled with + * "set -o vi", or by setting the VISUAL or EDITOR variables to something + * ending in vi). + */ +#define VI + +/* Define JOBS if you want job control compiled in. This requires that your + * system support process groups and reliable signal handling routines (it + * will be automatically undefined if your system doesn't have them). + */ +#define JOBS + +/* Define BRACE_EXPAND if you want csh-like {} globbing compiled in and enabled + * (can be disabled with "set +o braceexpand"; also disabled by "set -o posix", + * but can be re-enabled with "set -o braceexpand"). + */ +#define BRACE_EXPAND + +/* Define COMPLEX_HISTORY if you want at&t ksh style history files (ie, file + * is updated after each command is read; concurrent ksh's read each other's + * commands, etc.). This option uses the mmap() and flock() functions - if + * these aren't available, the option is automatically undefined. If this + * option is not defined, a simplier history mechanism which reads/saves the + * history at startup/exit time, respectively, is used. COMPLEX_HISTORY is + * courtesy of Peter Collinson. + */ +#undef COMPLEX_HISTORY + +/* Define POSIXLY_CORRECT if you want POSIX behavior by default (otherwise, + * posix behavior is only turned on if the environment variable POSIXLY_CORRECT + * is present or by using "set -o posix"; it can be turned off with + * "set +o posix"). + * See the POSIX Mode section in the man page for details on what this option + * affects. + * NOTE: posix mode is not compatable with some bourne sh/at&t ksh scripts. + */ +#undef POSIXLY_CORRECT + +/* Define DEFAULT_ENV to be the name of the file (eg, "/etc/default.env") to + * include if the ENV environment variable is not set when the shell starts + * up. This can be useful when used with rsh(1) which creates a non-login + * shell (ie, profile not read) with an empty environment (ie, ENV not set). + * Setting ENV to null disables the inclusion of DEFAULT_ENV. + * NOTE: this is a non-standard feature (ie, at&t ksh has no default + * environment) - undefining this disables the use of a default ENV file. + */ +#undef DEFAULT_ENV + +/* Define SWTCH to handle SWITCH character, for use with shell layers (shl(1)). + * This has not been tested for some time. + */ +#undef SWTCH + +/* SILLY: The name says it all - compile game of life code into the emacs + * command line editing code. + */ +#undef SILLY diff --git a/bin/pdksh/path.c b/bin/pdksh/path.c new file mode 100644 index 00000000000..5242ab42cb4 --- /dev/null +++ b/bin/pdksh/path.c @@ -0,0 +1,343 @@ +/* $OpenBSD: path.c,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +#include "sh.h" +#include "ksh_stat.h" + +/* + * Contains a routine to search a : seperated list of + * paths (a la CDPATH) and make appropiate file names. + * Also contains a routine to simplify .'s and ..'s out of + * a path name. + * + * Larry Bouzane (larry@cs.mun.ca) + */ + +/* + * $Log: path.c,v $ + * Revision 1.1 1996/08/14 06:19:11 downsj + * Initial revision + * + * Revision 1.2 1994/05/19 18:32:40 michael + * Merge complete, stdio replaced, various fixes. (pre autoconf) + * + * Revision 1.1 1994/04/06 13:14:03 michael + * Initial revision + * + * Revision 4.2 1990/12/06 18:05:24 larry + * Updated test code to reflect parameter change. + * Fixed problem with /a/./.dir being simplified to /a and not /a/.dir due + * to *(cur+2) == *f test instead of the correct cur+2 == f + * + * Revision 4.1 90/10/29 14:42:19 larry + * base MUN version + * + * Revision 3.1.0.4 89/02/16 20:28:36 larry + * Forgot to set *pathlist to NULL when last changed make_path(). + * + * Revision 3.1.0.3 89/02/13 20:29:55 larry + * Fixed up cd so that it knew when a node from CDPATH was used and would + * print a message only when really necessary. + * + * Revision 3.1.0.2 89/02/13 17:51:22 larry + * Merged with Eric Gisin's version. + * + * Revision 3.1.0.1 89/02/13 17:50:58 larry + * *** empty log message *** + * + * Revision 3.1 89/02/13 17:49:28 larry + * *** empty log message *** + * + */ + +#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 : seperated 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++; +#ifdef OS2 + if (path[0] && path[1] == ':') /* skip a: */ + very_start += 2; +#endif /* OS2 */ + + /* Before After + * /foo/ /foo + * /foo/../../bar /bar + * /foo/./blah/.. /foo + * . . + * .. .. + * ./foo foo + * foo/../../../bar ../../bar + * OS2: + * a:/foo/../.. a:/ + * a:. a: + * a:.. a:.. + * a:foo/../../blah a:../blah + */ + + 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/bin/pdksh/proto.h b/bin/pdksh/proto.h new file mode 100644 index 00000000000..067f28c64d0 --- /dev/null +++ b/bin/pdksh/proto.h @@ -0,0 +1,286 @@ +/* $OpenBSD: proto.h,v 1.1 1996/08/14 06:19:11 downsj 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)); +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 search_access ARGS((const char *path, int mode)); +int pr_menu 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_seperate)); +# 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)); +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 get_coproc_fd ARGS((int mode, const char **emsgp)); +void cleanup_coproc ARGS((int reuse)); +#endif /* KSH */ +struct temp *maketemp ARGS((Area *ap)); +/* 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 */ +void mcheck ARGS((void)); +void mbset ARGS((char *p)); +void mpset ARGS((char *mptoparse)); +/* 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 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 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)); +void alarm_init ARGS((void)); +Trap * gettrap ARGS((const char *name)); +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)); +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)); +void setstr ARGS((struct tbl *vq, const char *s)); +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, int set, int 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)); +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/bin/pdksh/sh.h b/bin/pdksh/sh.h new file mode 100644 index 00000000000..b7fcde1ac93 --- /dev/null +++ b/bin/pdksh/sh.h @@ -0,0 +1,680 @@ +/* $OpenBSD: sh.h,v 1.1 1996/08/14 06:19:11 downsj 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 <stdio.h> +#include <sys/types.h> +#include <setjmp.h> +#ifdef HAVE_STDDEF_H +# include <stddef.h> +#endif + +#ifdef HAVE_STDLIB_H +# include <stdlib.h> +#else +/* just a useful subset of what stdlib.h would have */ +extern char * getenv ARGS((const char *)); +extern void * malloc ARGS((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 <unistd.h> +#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 <string.h> +#else +# include <strings.h> +# 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 <memory.h> +#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 <stdarg.h> +# define SH_VA_START(va, argn) va_start(va, argn) +#else +# include <varargs.h> +# define SH_VA_START(va, argn) va_start(va) +#endif /* HAVE_PROTOTYPES */ + +#include <errno.h> +extern int errno; + +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#else +# include <sys/file.h> +#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 <limits.h> +#endif /* HAVE_LIMITS_H */ + +#include <signal.h> +#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 <paths.h> +#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); +#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) do { \ + __setostype(0); \ + execve(p, av, ev); \ + __setostype(1); \ + } while (0) +# else /* OS_ISC && _POSIX */ +# define ksh_execve(p, av, ev) 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 */ + +/* 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) + */ +#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 + +#ifndef EXECSHELL +/* shell to exec scripts (see also $SHELL initialization in main.c) */ +# ifdef OS2 +# define EXECSHELL "c:\\os2\\cmd.exe" +# define EXECSHELL_STR "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 a:/foo yes yes no + * os2 a:foo no no no + * os2 /foo no yes no + * os2 foo no no yes + * os2 ../foo no no yes + */ +#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) == '/') +# define ISABSPATH(s) ISDIRSEP((s)[0]) +# define ISROOTEDPATH(s) ISABSPATH(s) +# define ISRELPATH(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 */ + +#define NUFILE 10 /* 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 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 Block *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 */ + + +/* + * parsing & execution environment + */ +EXTERN struct env { + short type; /* enviroment 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 enviroment */ + 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 enviroment */ +#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 */ + +/* 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_ANY (OF_CMDLINE | OF_SET | OF_SPECIAL) + +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 */ + 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 */ +#endif + FXTRACE, /* -x: execution trace */ + 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__("/"); + +/* temp/here 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 */ + char *name; +}; + +/* here documents in functions are treated specially (the get removed when + * shell exis) */ +EXTERN struct temp *func_heredocs; + +/* + * 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) */ +} 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 SIG_DFL */ +#define SS_RESTORE_IGN 3 /* restore 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 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 */ + + +/* + * TMOUT support + */ +/* values for ksh_tmout_state */ +enum tmout_enum { + TMOUT_EXECUTING = 0, /* executing commands */ + TMOUT_READING, /* waiting for input */ + TMOUT_LEAVING /* have timed out */ + }; +EXTERN unsigned int ksh_tmout; +EXTERN enum tmout_enum ksh_tmout_state I__(TMOUT_EXECUTING); + + +/* For "You have stopped jobs" message */ +EXTERN int really_exit; + + +/* + * fast character classes + */ +#define C_ALPHA 0x01 /* a-z_A-Z */ +#define C_DIGIT 0x02 /* 0-9 */ +#define C_LEX1 0x04 /* \0 \t\n|&;<>() */ +#define C_VAR1 0x08 /* *@#!$-? */ +#define C_IFSWS 0x10 /* \t \n (IFS white space) */ +#define C_SUBOP1 0x20 /* "=-+?" */ +#define C_SUBOP2 0x40 /* "#%" */ +#define C_IFS 0x80 /* $IFS */ + +extern char 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; + 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 */ + + +#ifdef KSH +/* This for co-processes */ +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 */ + void *job; /* 0 if no co-process, or co-process died */ +}; +EXTERN struct coproc coproc; +#endif /* KSH */ + +extern const char ksh_version[]; + +/* name of called builtin function (used by error functions) */ +EXTERN char *builtin_argv0; +EXTERN int 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 '}' + + +#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/bin/pdksh/shf.c b/bin/pdksh/shf.c new file mode 100644 index 00000000000..820139c1b33 --- /dev/null +++ b/bin/pdksh/shf.c @@ -0,0 +1,1271 @@ +/* $OpenBSD: shf.c,v 1.1 1996/08/14 06:19:11 downsj 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; +{ + int fd; + + fd = open(name, oflags, mode); + if (fd < 0) + return NULL; + if ((sflags & SHF_MAPHI) && fd < FDBASE) { + int nfd; + + nfd = ksh_dupbase(fd, FDBASE); + close(fd); + if (nfd < 0) + 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_fdopen(fd, sflags, (struct shf *) 0); +} + +/* 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->wbsize += 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; + } 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); + + if ((ncopy = 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. Assmumes 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 <math.h> + +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 <math.h> + * extern double frexp(); + */ + extern char *ecvt(); + + double fpnum; + int expo, decpt; + char style; + char fpbuf[FPBUF_SIZE]; +#endif /* FP */ + + if (!fmt) + return 0; + + while ((c = *fmt++)) { + if (c != '%') { + shf_putc(c, shf); + nwritten++; + continue; + } + /* + * This will accept flags/fields in any order - not + * just the order specified in printf(3), but this is + * the way _doprnt() seems to work (on bsd and sysV). + * The only resriction 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 proabably 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 + * aproximation 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/bin/pdksh/shf.h b/bin/pdksh/shf.h new file mode 100644 index 00000000000..2069d3248b8 --- /dev/null +++ b/bin/pdksh/shf.h @@ -0,0 +1,81 @@ +/* $OpenBSD: shf.h,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* + * 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)); diff --git a/bin/pdksh/sigact.c b/bin/pdksh/sigact.c new file mode 100644 index 00000000000..81dd7c2398c --- /dev/null +++ b/bin/pdksh/sigact.c @@ -0,0 +1,484 @@ +/* $OpenBSD: sigact.c,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* NAME: + * sigact.c - fake sigaction(2) + * + * SYNOPSIS: + * #include "sigact.h" + * + * int sigaction(int sig, struct sigaction *act, + * struct sigaction *oact); + * int sigaddset(sigset_t *mask, int sig); + * int sigdelset(sigset_t *mask, int sig); + * int sigemptyset(sigset_t *mask); + * int sigfillset(sigset_t *mask); + * int sigismember(sigset_t *mask, int sig); + * int sigpending(sigset_t *set); + * int sigprocmask(int how, sigset_t *set, sigset_t *oset); + * int sigsuspend(sigset_t *mask); + * + * RETSIGTYPE (*Signal(int sig, RETSIGTYPE (*disp)(int)))(int); + * + * DESCRIPTION: + * This is a fake sigaction implementation. It uses + * sigsetmask(2) et al or sigset(2) and friends if + * available, otherwise it just uses signal(2). If it + * thinks sigaction(2) really exists it compiles to "almost" + * nothing. + * + * In any case it provides a Signal() function that is + * implemented in terms of sigaction(). + * If not using signal(2) as part of the underlying + * implementation (USE_SIGNAL or USE_SIGMASK), and + * NO_SIGNAL is not defined, it also provides a signal() + * function that calls Signal(). + * + * The need for all this mucking about is the problems + * caused by mixing various signal handling mechanisms in + * the one process. This module allows for a consistent + * POSIX compliant interface to whatever is actually + * available. + * + * sigaction() allows the caller to examine and/or set the + * action to be associated with a given signal. "act" and + * "oact" are pointers to 'sigaction structs': + *.nf + * + * struct sigaction + * { + * RETSIGTYPE (*sa_handler)(); + * sigset_t sa_mask; + * int sa_flags; + * }; + *.fi + * + * RETSIGTYPE is normally 'void' in the POSIX implementation + * and for most current systems. On some older UNIX + * systems, signal handlers do not return 'void', so + * this implementation keeps 'sa_handler' inline with the + * hosts normal signal handling conventions. + * 'sa_mask' controls which signals will be blocked while + * the selected signal handler is active. It is not used + * in this implementation. + * 'sa_flags' controls various semantics such as whether + * system calls should be automagically restarted + * (SA_RESTART) etc. It is not used in this + * implementation. + * Either "act" or "oact" may be NULL in which case the + * appropriate operation is skipped. + * + * sigaddset() adds "sig" to the sigset_t pointed to by "mask". + * + * sigdelset() removes "sig" from the sigset_t pointed to + * by "mask". + * + * sigemptyset() makes the sigset_t pointed to by "mask" empty. + * + * sigfillset() makes the sigset_t pointed to by "mask" + * full ie. match all signals. + * + * sigismember() returns true if "sig" is found in "*mask". + * + * sigpending() is supposed to return "set" loaded with the + * set of signals that are blocked and pending for the + * calling process. It does nothing in this impementation. + * + * sigprocmask() is used to examine and/or change the + * signal mask for the calling process. Either "set" or + * "oset" may be NULL in which case the appropriate + * operation is skipped. "how" may be one of SIG_BLOCK, + * SIG_UNBLOCK or SIG_SETMASK. If this package is built + * with USE_SIGNAL, then this routine achieves nothing. + * + * sigsuspend() sets the signal mask to "*mask" and waits + * for a signal to be delivered after which the previous + * mask is restored. + * + * + * RETURN VALUE: + * 0==success, -1==failure + * + * BUGS: + * Since we fake most of this, don't expect fancy usage to + * work. + * + * AUTHOR: + * Simon J. Gerraty <sjg@zen.void.oz.au> + */ +/* COPYRIGHT: + * @(#)Copyright (c) 1992 Simon J. Gerraty + * + * 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. + * + * Please send copies of changes and bug-fixes to: + * sjg@zen.void.oz.au + * + */ +/* Changes to sigact.c for pdksh, Michael Rendell <michael@cs.mun.ca>: + * - sigsuspend(): pass *mask to bsd4.2 sigpause instead of mask. + * - changed SIG_HDLR to RETSIGTYPE for use with GNU autoconf + * - added and used RETSIGVAL + * - include sh.h instead of signal.h (to get *_SIGNALS macros) + * - changed if !SA_NOCLDSTOP ... to USE_FAKE_SIGACT to avoid confusion + * - set the USE_* defines using the *_SIGNALS defines from autoconf + * - sigaction(): if using BSD signals, use sigvec() (used to use + * signal()) and set the SV_INTERRUPT flag (POSIX says syscalls + * are interrupted and pdksh needs this behaviour). + * - define IS_KSH before including anything; ifdef out routines + * not used in ksh if IS_KSH is defined (same in sigact.h). + * - use ARGS() instead of __P() + * - sigaction(),sigsuspend(),Signal(),signal(): use handler_t typedef + * instead of explicit type. + */ + +/* + #include <signal.h> +*/ +#define IS_KSH +#include "sh.h" + +/* + #ifndef __P + # if defined(__STDC__) || defined(__cplusplus) + # define __P(p) p + # else + # define __P(p) () + # endif + #endif +*/ + + +/* + * some systems have a faulty sigaction() implementation! + * Allow us to bypass it. + * Or they may have installed sigact.h as signal.h which is why + * we have SA_NOCLDSTOP defined. + */ +#ifdef USE_FAKE_SIGACT /* let autoconf decide.. */ +/* #if !defined(SA_NOCLDSTOP) || defined(_SIGACT_H) || defined(USE_SIGNAL) || defined(USE_SIGSET) || defined(USE_SIGMASK) */ + +/* Let autoconf decide which to use */ +#ifdef BSD42_SIGNALS +# define USE_SIGMASK +#else +# ifdef BSD41_SIGNALS +# define USE_SIGSET +# else +# define USE_SIGNAL +# endif +#endif /* BSD42_SIGNALS */ + +/* + * if we haven't been told, + * try and guess what we should implement with. + */ +#if !defined(USE_SIGSET) && !defined(USE_SIGMASK) && !defined(USE_SIGNAL) +# if defined(sigmask) || defined(BSD) || defined(_BSD) && !defined(BSD41) +# define USE_SIGMASK +# else +# ifndef NO_SIGSET +# define USE_SIGSET +# else +# define USE_SIGNAL +# endif +# endif +#endif +/* + * if we still don't know, we're in trouble + */ +#if !defined(USE_SIGSET) && !defined(USE_SIGMASK) && !defined(USE_SIGNAL) +error must know what to implement with +#endif + +#include "sigact.h" + +/* + * in case signal() has been mapped to our Signal(). + */ +#undef signal + + +int +sigaction(sig, act, oact) + int sig; + struct sigaction *act, *oact; +{ + handler_t oldh; + + if (act) + { +#ifdef USE_SIGSET + oldh = sigset(sig, act->sa_handler); +#else +# ifdef USE_SIGMASK + struct sigvec nsv,osv; + + nsv.sv_handler = act->sa_handler; + nsv.sv_mask = 0; /* punt */ + nsv.sv_flags = SV_INTERRUPT; /* punt */ + sigvec(sig, &nsv, &osv); + oldh = osv.sv_handler; +# else /* USE_SIGMASK */ + oldh = signal(sig, act->sa_handler); +# endif /* USE_SIGMASK */ +#endif + } + else + { + if (oact) + { +#ifdef USE_SIGSET + oldh = sigset(sig, SIG_IGN); +#else + oldh = signal(sig, SIG_IGN); +#endif + if (oldh != SIG_IGN && oldh != SIG_ERR) + { +#ifdef USE_SIGSET + (void) sigset(sig, oldh); +#else + (void) signal(sig, oldh); +#endif + } + } + } + if (oact) + { + oact->sa_handler = oldh; + } + return 0; /* hey we're faking it */ +} + + +int +sigaddset(mask, sig) + sigset_t *mask; + int sig; +{ + *mask |= sigmask(sig); + return 0; +} + + +#ifndef IS_KSH +int +sigdelset(mask, sig) + sigset_t *mask; + int sig; +{ + *mask &= ~(sigmask(sig)); + return 0; +} +#endif /* IS_KSH */ + + +int +sigemptyset(mask) + sigset_t *mask; +{ + *mask = 0; + return 0; +} + + +#ifndef IS_KSH +int +sigfillset(mask) + sigset_t *mask; +{ + *mask = ~0; + return 0; +} +#endif /* IS_KSH */ + + +#ifndef IS_KSH +int +sigismember(mask, sig) + sigset_t *mask; + int sig; +{ + return ((*mask) & sigmask(sig)); +} +#endif /* IS_KSH */ + + +#ifndef IS_KSH +int +sigpending(set) + sigset_t *set; +{ + return 0; /* faking it! */ +} +#endif /* IS_KSH */ + + +int +sigprocmask(how, set, oset) + int how; + sigset_t *set, *oset; +{ +#ifdef USE_SIGSET + register int i; +#endif + static sigset_t sm; + static int once = 0; + + if (!once) + { + /* + * initally we clear sm, + * there after, it represents the last + * thing we did. + */ + once++; +#ifdef USE_SIGMASK + sm = sigblock(0); +#else + sm = 0; +#endif + } + + if (oset) + *oset = sm; + if (set) + { + switch (how) + { + case SIG_BLOCK: + sm |= *set; + break; + case SIG_UNBLOCK: + sm &= ~(*set); + break; + case SIG_SETMASK: + sm = *set; + break; + } +#ifdef USE_SIGMASK + (void) sigsetmask(sm); +#else +# ifdef USE_SIGSET + for (i = 1; i < NSIG; i++) + { + if (how == SIG_UNBLOCK) + { + if (*set & sigmask(i)) + sigrelse(i); + } + else + if (sm & sigmask(i)) + { + sighold(i); + } + } +# endif +#endif + } + return 0; +} + + +int +sigsuspend(mask) + sigset_t *mask; +{ +#ifdef USE_SIGMASK + sigpause(*mask); +#else + register int i; + +# ifdef USE_SIGSET + + for (i = 1; i < NSIG; i++) + { + if (*mask & sigmask(i)) + { + /* not the same sigpause() as above! */ + sigpause(i); + break; + } + } +# else /* signal(2) only */ + handler_t oldh; + + /* + * make sure that signals in mask will not + * be ignored. + */ + for (i = 1; i < NSIG; i++) + { + if (*mask & sigmask(i)) + { + if ((oldh = signal(i, SIG_DFL)) != SIG_ERR && + oldh != SIG_IGN && + oldh != SIG_DFL) + (void) signal(i, oldh); /* restore handler */ + } + } + pause(); /* wait for a signal */ +# endif +#endif + return 0; +} + +#endif /* USE_FAKE_SIGACT (was ! SA_NOCLDSTOP) */ + +#if !defined(RETSIGTYPE) +# define RETSIGTYPE void +# define RETSIGVAL +#endif +#if !defined(SIG_ERR) +# define SIG_ERR (RETSIGTYPE (*)())-1 +#endif + +/* + * an implementation of signal() using sigaction(). + */ + +#ifndef IS_KSH +handler_t Signal(sig, handler) + int sig; + handler_t handler; +{ + struct sigaction act, oact; + + act.sa_handler = handler; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + if (sigaction(sig, &act, &oact) < 0) + return (SIG_ERR); + return (oact.sa_handler); +} +#endif /* IS_KSH */ + +#ifndef IS_KSH +#if !defined(USE_SIGNAL) && !defined(USE_SIGMASK) && !defined(NO_SIGNAL) +/* + * ensure we avoid signal mayhem + */ + +handler_t signal(sig, handler) + int sig; + handler_t handler; +{ + return (Signal(sig, handler)); +} +#endif +#endif /* IS_KSH */ + +/* This lot (for GNU-Emacs) goes at the end of the file. */ +/* + * Local Variables: + * version-control:t + * comment-column:40 + * End: + */ diff --git a/bin/pdksh/sigact.h b/bin/pdksh/sigact.h new file mode 100644 index 00000000000..17d32f17e94 --- /dev/null +++ b/bin/pdksh/sigact.h @@ -0,0 +1,131 @@ +/* $OpenBSD: sigact.h,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* NAME: + * sigact.h - sigaction et al + * + * SYNOPSIS: + * #include "sigact.h" + * + * DESCRIPTION: + * This header is the interface to a fake sigaction(2) + * implementation. It provides a POSIX compliant interface + * to whatever signal handling mechanisms are available. + * It also provides a Signal() function that is implemented + * in terms of sigaction(). + * If not using signal(2) as part of the underlying + * implementation (USE_SIGNAL or USE_SIGMASK), and + * NO_SIGNAL is not defined, it also provides a signal() + * function that calls Signal(). + * + * SEE ALSO: + * sigact.c + */ +/* + * RCSid: + * $From: sigact.h,v 1.2 1994/05/31 13:34:34 michael Exp $ + */ +/* Changes to sigact.h for pdksh, Michael Rendell <michael@cs.mun.ca>: + * - changed SIG_HDLR to RETSIGTYPE for use with GNU autoconf + * - added RETSIGVAL + * - ifdef'd out ARGS(), volatile and const initializations + * - ifdef'd out sigset_t definition - let autoconf handle it + * - ifdef out routines not used in ksh if IS_KSH is defined + * (same in sigact.c). + */ +#ifndef _SIGACT_H +#define _SIGACT_H + +/* + * most modern systems use void for signal handlers but + * not all. + */ +#ifndef RETSIGTYPE +# define RETSIGTYPE void +# define RETSIGVAL +#endif + +#if 0 /* ARGS(), volatile and const are already set up in config*.h -mhr */ +#undef ARGS +#if defined(__STDC__) || defined(__cplusplus) +# define ARGS(p) p +#else +# define ARGS(p) () +# define volatile /* don't optimize please */ +# define const /* read only */ +#endif +#endif + +#ifndef IS_KSH +handler_t Signal ARGS((int sig, handler_t disp)); +#endif /* IS_KSH */ + +/* + * if you want to install this header as signal.h, + * modify this to pick up the original signal.h + */ +#ifndef SIGKILL +# include <signal.h> +#endif + +#ifndef SIG_ERR +# define SIG_ERR ((handler_t) -1) +#endif +#ifndef BADSIG +# define BADSIG SIG_ERR +#endif + +#ifndef SA_NOCLDSTOP +/* we assume we need the fake sigaction */ +/* sa_flags */ +#define SA_NOCLDSTOP 1 /* don't send SIGCHLD on child stop */ +#define SA_RESTART 2 /* re-start I/O */ + +/* sigprocmask flags */ +#define SIG_BLOCK 1 +#define SIG_UNBLOCK 2 +#define SIG_SETMASK 4 + +#if 0 /* autoconf will define sigset_t if it isn't available */ +/* + * this is a bit untidy + */ +#if !defined(__sys_stdtypes_h) +typedef unsigned int sigset_t; +#endif +#endif /* 0 */ + +/* + * POSIX sa_handler should return void, but since we are + * implementing in terms of something else, it may + * be appropriate to use the normal RETSIGTYPE return type + */ +struct sigaction +{ + handler_t sa_handler; + sigset_t sa_mask; + int sa_flags; +}; + + +int sigaction ARGS(( int sig, struct sigaction *act, struct sigaction *oact )); +int sigaddset ARGS(( sigset_t *mask, int sig )); +#ifndef IS_KSH +int sigdelset ARGS(( sigset_t *mask, int sig )); +#endif /* IS_KSH */ +int sigemptyset ARGS(( sigset_t *mask )); +#ifndef IS_KSH +int sigfillset ARGS(( sigset_t *mask )); +int sigismember ARGS(( sigset_t *mask, int sig )); +int sigpending ARGS(( sigset_t *set )); +#endif /* IS_KSH */ +int sigprocmask ARGS(( int how, sigset_t *set, sigset_t *oset )); +int sigsuspend ARGS(( sigset_t *mask )); + +#ifndef sigmask +# define sigmask(s) (1<<((s)-1)) /* convert SIGnum to mask */ +#endif +#if !defined(NSIG) && defined(_NSIG) +# define NSIG _NSIG +#endif +#endif /* ! SA_NOCLDSTOP */ +#endif /* _SIGACT_H */ diff --git a/bin/pdksh/siglist.in b/bin/pdksh/siglist.in new file mode 100644 index 00000000000..de6f1dd849f --- /dev/null +++ b/bin/pdksh/siglist.in @@ -0,0 +1,56 @@ +# $OpenBSD: siglist.in,v 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/bin/pdksh/siglist.sh b/bin/pdksh/siglist.sh new file mode 100644 index 00000000000..420aaf6dd05 --- /dev/null +++ b/bin/pdksh/siglist.sh @@ -0,0 +1,40 @@ +#!/bin/sh +# $OpenBSD: siglist.sh,v 1.1 1996/08/14 06:19:11 downsj 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; } + { + 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/bin/pdksh/syn.c b/bin/pdksh/syn.c new file mode 100644 index 00000000000..bd5db3740dd --- /dev/null +++ b/bin/pdksh/syn.c @@ -0,0 +1,927 @@ +/* $OpenBSD: syn.c,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ + +/* + * shell parser (C version) + */ + +#include "sh.h" +#include "c_test.h" + +struct multiline_state { + int on; /* set in multiline commands (\n becomes ;) */ + int start_token; /* token multiline is for (eg, FOR, {, etc.) */ + int start_line; /* line multiline command started 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((void)); +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 multiline_push ARGS((struct multiline_state *save, int tok)); +static void multiline_pop ARGS((struct multiline_state *saved)); +static int assign_command ARGS((char *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 multiline_state multiline; /* \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; + yynerrs = 0; + + outtree = c_list(); + 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() +{ + register struct op *t, *p, *tl = NULL; + register int c; + + t = andor(); + if (t != NULL) { + while ((c = token(0)) == ';' || c == '&' || c == COPROC || + (c == '\n' && (multiline.on || source->type == SALIAS))) + { + if (c == '&' || c == COPROC) { + int type = c == '&' ? TASYNC : TCOPROC; + if (tl) + tl->right = block(type, tl->right, + NOBLOCK, NOWORDS); + else + t = block(type, t, NOBLOCK, NOWORDS); + } + if ((p = andor()) == NULL) + return (t); + if (tl == NULL) + t = tl = block(TLIST, t, p, NOWORDS); + else + tl = tl->right = block(TLIST, tl->right, p, NOWORDS); + } + 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 multiline_state old_multiline; + + multiline_push(&old_multiline, smark); + t = c_list(); + musthave(emark, KEYWORD|ALIAS); + multiline_pop(&old_multiline); + 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 multiline_state old_multiline; + + iops = (struct ioword **) alloc(sizeofN(struct ioword *, NUFILE+1), + ATEMP); + XPinit(args, 16); + XPinit(vars, 16); + + if (multiline.on) + cf = CONTIN; + 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); + while (1) { + cf = (t->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->evalflags = DOVACHECK; + if ((XPsize(args) == 0 || Flag(FKEYWORD)) + && is_wdvarassign(yylval.cp)) + XPput(vars, yylval.cp); + else + XPput(args, yylval.cp); + break; + + case '(': + /* Check for "> foo (echo hi)", which at&t ksh + * allows (not POSIX, but not disallowed) + */ + afree(t, ATEMP); + if (XPsize(args) == 0 && XPsize(vars) != 0) { + ACCEPT; + goto Subshell; + } + /* Must be a function */ + if (iopn != 0 || XPsize(args) != 1 + || XPsize(vars) != 0) + syntaxerr((char *) 0); + ACCEPT; + /*(*/ + musthave(')', 0); + t = function_body(XPptrv(args)[0], FALSE); + goto Leave; + + default: + goto Leave; + } + } + Leave: + break; + + Subshell: + case '(': + t = nested(TPAREN, '(', ')'); + break; + + case '{': /*}*/ + t = nested(TBRACE, '{', '}'); + break; + + case MDPAREN: + { + static const char let_cmd[] = { CHAR, 'l', CHAR, 'e', + CHAR, 't', EOS }; + syniocf &= ~(KEYWORD|ALIAS); + t = newtp(TCOM); + ACCEPT; + XPput(args, wdcopy(let_cmd, ATEMP)); + musthave(LWORD,LETEXPR); + XPput(args, yylval.cp); + break; + } + +#ifdef KSH + case DBRACKET: /* [[ .. ]] */ + syniocf &= ~(KEYWORD|ALIAS); + 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); + multiline_push(&old_multiline, c); + t->vars = wordlist(); + t->left = dogroup(); + multiline_pop(&old_multiline); + break; + + case WHILE: + case UNTIL: + multiline_push(&old_multiline, c); + t = newtp((c == WHILE) ? TWHILE: TUNTIL); + t->left = c_list(); + t->right = dogroup(); + multiline_pop(&old_multiline); + break; + + case CASE: + t = newtp(TCASE); + musthave(LWORD, 0); + t->str = yylval.cp; + multiline_push(&old_multiline, c); + t->left = caselist(); + multiline_pop(&old_multiline); + break; + + case IF: + multiline_push(&old_multiline, c); + t = newtp(TIF); + t->left = c_list(); + t->right = thenpart(); + musthave(FI, KEYWORD|ALIAS); + multiline_pop(&old_multiline); + 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(); + 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(); + 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()) == NULL) + syntaxerr((char *) 0); + return (t); + + case ELIF: + t = newtp(TELIF); + t->left = c_list(); + 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(); + 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() { .. } */ +{ + XString xs; + char *xp, *p; + struct op *t; + int old_func_parse; + + Xinit(xs, xp, 16, ATEMP); + for (p = name; ; ) { + if ((*p == EOS && Xlength(xs, xp) == 0) + || (*p != EOS && *p != CHAR && *p != QCHAR + && *p != OQUOTE && *p != CQUOTE)) + { + p = snptreef((char *) 0, 32, "%S", name); + yyerror("%s: invalid function name\n", p); + } + Xcheck(xs, xp); + if (*p == EOS) { + Xput(xs, xp, '\0'); + break; + } else if (*p == CHAR || *p == QCHAR) { + Xput(xs, xp, p[1]); + p += 2; + } else + p++; /* OQUOTE/CQUOTE */ + } + t = newtp(TFUNCT); + t->str = Xclose(xs, xp); + + /* 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) { + /* create empty command so foo(): will work */ + t->left = newtp(TCOM); + t->args = (char **) alloc(sizeof(char *), ATEMP); + t->args[0] = (char *) 0; + t->vars = (char **) alloc(sizeof(char *), ATEMP); + t->vars[0] = (char *) 0; + } + if (!old_func_parse) + e->flags &= ~EF_FUNC_PARSE; + + return t; +} + +static char ** +wordlist() +{ + register int c; + XPtrV args; + + XPinit(args, 16); + 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 }, + { "((", MDPAREN, FALSE }, +#ifdef KSH + { "|&", 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 (multiline.on && multiline.start_token) { + multiline.on = FALSE; /* avoid infinate loops */ + c = multiline.start_token; + source->errline = multiline.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 +multiline_push(save, tok) + struct multiline_state *save; + int tok; +{ + *save = multiline; + multiline.on = TRUE; + multiline.start_token = tok; + multiline.start_line = source->line; +} + +static void +multiline_pop(saved) + struct multiline_state *saved; +{ + multiline = *saved; +} + +static struct op * +newtp(type) + int type; +{ + register struct op *t; + + t = (struct op *) alloc(sizeof(*t), ATEMP); + t->type = type; + t->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; +{ + yynerrs = 0; + multiline.on = s->type == SSTRING; + multiline.start_token = 0; + multiline.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); +} + + +#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/bin/pdksh/table.c b/bin/pdksh/table.c new file mode 100644 index 00000000000..03558fa137a --- /dev/null +++ b/bin/pdksh/table.c @@ -0,0 +1,239 @@ +/* $OpenBSD: table.c,v 1.1 1996/08/14 06:19:11 downsj 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 { + 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->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/bin/pdksh/table.h b/bin/pdksh/table.h new file mode 100644 index 00000000000..1dfc08942fa --- /dev/null +++ b/bin/pdksh/table.h @@ -0,0 +1,165 @@ +/* $OpenBSD: table.h,v 1.1 1996/08/14 06:19:12 downsj 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 */ + INT32 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 */ + int field; /* field with for -L/-R/-Z */ + 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 */ +/* 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 SPEC_BI BIT(11) /* a POSIX special builtin */ +#define REG_BI BIT(12) /* a POSIX regular builtin */ + +/* 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; + struct table vars; /* local variables */ + struct table funs; /* local functions */ +#if 1 + char * error; /* error handler */ + char * exit; /* exit handler */ +#else + Trap error, exit; +#endif + struct block *next; /* enclosing block */ +}; + +/* + * 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 + +/* values for set_prompt() */ +#define PS1 0 /* command */ +#define PS2 1 /* command continuation */ + +EXTERN const char *path; /* PATH value */ +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 */ diff --git a/bin/pdksh/trap.c b/bin/pdksh/trap.c new file mode 100644 index 00000000000..e4ecdd39efa --- /dev/null +++ b/bin/pdksh/trap.c @@ -0,0 +1,419 @@ +/* $OpenBSD: trap.c,v 1.1 1996/08/14 06:19:12 downsj 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 RETSIGTYPE alarm_catcher ARGS((int sig)); + +static struct sigaction Sigact_ign, Sigact_trap, Sigact_alarm; + +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][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; + Sigact_alarm = Sigact_ign; + Sigact_alarm.sa_handler = alarm_catcher; + + 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 temproary 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); +} + +void +alarm_init() +{ + sigtraps[SIGALRM].flags |= TF_SHELL_USES; + setsig(&sigtraps[SIGALRM], alarm_catcher, + SS_RESTORE_ORIG|SS_FORCE); +} + +static RETSIGTYPE +alarm_catcher(sig) + int sig; +{ + trapsig(sig); +#ifdef KSH + if (ksh_tmout_state == TMOUT_READING) { + int left = alarm(0); + + if (left == 0) { + ksh_tmout_state = TMOUT_LEAVING; + intrsig = 1; + } else + alarm(left); + } +#endif /* KSH */ +#ifdef V7_SIGNALS + sigaction(sig, &Sigact_alarm, (struct sigaction *) 0); +#endif /* V7_SIGNALS */ + return RETSIGVAL; +} + +Trap * +gettrap(name) + const char *name; +{ + 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 && strcasecmp(p->name, name) == 0) + return p; + return NULL; +} + +/* + * trap signal handler + */ +RETSIGTYPE +trapsig(i) + int i; +{ + Trap *p = &sigtraps[i]; + + trap = p->set = 1; + if (p->flags & TF_DFL_INTR) + intrsig = 1; + if ((p->flags & TF_FATAL) && !p->trap) { + fatal_trap = 1; + intrsig = 1; + } +#ifdef V7_SIGNALS + if (sigtraps[i].cursig == trapsig) /* this for SIGCHLD,SIGALRM */ + sigaction(i, &Sigact_trap, (struct sigaction *) 0); +#endif /* V7_SIGNALS */ + 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 + * occured 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; + 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; + + 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; + } + if ((p->flags & TF_ORIG_IGN) && (flags & SS_USER) + && !(flags & SS_FORCE) && !Flag(FTALKING)) + return 0; + if (!(flags & SS_USER)) { + if (!(flags & SS_FORCE) && (p->flags & TF_ORIG_IGN)) + return 0; + } + + setexecsig(p, flags & SS_RESTORE_MASK); + + 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/bin/pdksh/tree.c b/bin/pdksh/tree.c new file mode 100644 index 00000000000..3ceb7756cf7 --- /dev/null +++ b/bin/pdksh/tree.c @@ -0,0 +1,656 @@ +/* $OpenBSD: tree.c,v 1.1 1996/08/14 06:19:12 downsj 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: + t = t->left; + goto Chain; + 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, "function %s %T", t->str, t->left); + break; + case TTIME: + fptreef(shf, indent, "time %T", t->left); + break; + default: + fptreef(shf, indent, "<botch>"); + break; + } + if ((ioact = t->ioact) != NULL) { + int need_nl = 0; + + while (*ioact != NULL) + pioact(shf, indent, *ioact++); + /* Print here documents after everything else... */ + for (ioact = t->ioact; *ioact != NULL; ) { + struct ioword *iop = *ioact++; + + /* name is 0 when tracing (set -x) */ + if ((iop->flag & IOTYPE) == IOHERE && iop->name) { + struct shf *rshf; + char buf[1024]; + int n; + + tputc('\n', shf); + if ((rshf = shf_open(iop->name, O_RDONLY, 0, 0))) { + while ((n = shf_read(buf, sizeof(buf), rshf)) + > 0) + shf_write(buf, n, shf); + shf_close(rshf); + } else + errorf("can't open %s - %s", + iop->name, strerror(errno)); + 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; + + 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); + break; + case EXPRSUB: + tputc('$', shf); + tputc('(', shf); + tputc('(', shf); + while (*wp != 0) + tputC(*wp++, shf); + tputc(')', shf); + tputc(')', shf); + break; + case OQUOTE: + quoted = 1; + tputc('"', shf); + break; + case CQUOTE: + quoted = 0; + tputc('"', shf); + break; + case OSUBST: + tputc('$', shf); + tputc('{', shf); + while ((c = *wp++) != 0) + tputC(c, shf); + break; + case CSUBST: + 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->evalflags = t->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((int)(tw - t->vars) * 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((int)(tw - t->args) * 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); + + 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: + 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 */ + } +} + +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((int)(ior - iow) * 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); + } + 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); + afree((void*)p, ap); + } +} diff --git a/bin/pdksh/tree.h b/bin/pdksh/tree.h new file mode 100644 index 00000000000..29be89ccc4a --- /dev/null +++ b/bin/pdksh/tree.h @@ -0,0 +1,134 @@ +/* $OpenBSD: tree.h,v 1.1 1996/08/14 06:19:12 downsj 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 */ + short evalflags; /* eval() flags for arg expansion */ + 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 + */ +}; + +/* 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 ${ substitution */ +#define CSUBST 8 /* closing } of above */ +#define OPAT 9 /* open pattern: *(, @(, etc. */ +#define SPAT 10 /* seperate pattern: | */ +#define CPAT 11 /* close pattern: ) */ + +/* + * IO redirection + */ +struct ioword { + int unit; /* unit affected */ + int flag; /* action (below) */ + char *name; /* file name */ + char *delim; /* delimiter for <<,<<- */ +}; + +/* 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 */ + +/* + * 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/bin/pdksh/tty.c b/bin/pdksh/tty.c new file mode 100644 index 00000000000..5ee19fbba1d --- /dev/null +++ b/bin/pdksh/tty.c @@ -0,0 +1,160 @@ +/* $OpenBSD: tty.c,v 1.1 1996/08/14 06:19:12 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) { +/* X11R5 xterm on mips doesn't set controlling tty properly - temporary hack */ +# if !defined(__mips) || !(defined(_SYSTYPE_BSD43) || defined(__SYSTYPE_BSD43)) + 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/bin/pdksh/tty.h b/bin/pdksh/tty.h new file mode 100644 index 00000000000..3982fa9752d --- /dev/null +++ b/bin/pdksh/tty.h @@ -0,0 +1,87 @@ +/* $OpenBSD: tty.h,v 1.1 1996/08/14 06:19:12 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 + +#ifdef HAVE_TERMIOS_H +# include <termios.h> +typedef struct termios TTY_state; +#else +# ifdef HAVE_TERMIO_H +# include <termio.h> +# 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 <sgtty.h> +# define TIOCSETN TIOCSETP +# else +# include <sys/ioctl.h> +# 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/bin/pdksh/var.c b/bin/pdksh/var.c new file mode 100644 index 00000000000..8a4922b4903 --- /dev/null +++ b/bin/pdksh/var.c @@ -0,0 +1,1130 @@ +/* $OpenBSD: var.c,v 1.1 1996/08/14 06:19:12 downsj Exp $ */ + +#include "sh.h" +#include "ksh_time.h" +#include "ksh_limval.h" +#include "ksh_stat.h" +#include <ctype.h> + +/* + * 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 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); + ainit(&l->area); + 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); + 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 }, + { "MAIL", V_MAIL }, + { "MAILCHECK", V_MAILCHECK }, + { "MAILPATH", V_MAILPATH }, + { "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 + { "RANDOM", V_RANDOM }, + { "SECONDS", V_SECONDS }, + { "TMOUT", V_TMOUT }, +#endif /* KSH */ + { (char *) 0, 0 } + }; + int i; + struct tbl *tp; + + tinit(&specials, APERM, 32); /* must be 2^n (currently 16 speciasl) */ + 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, FALSE); + 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|RDONLY); + 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(vp, l->argv[c]); + return vp; + } + 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->field = vq->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 */ +void +setstr(vq, s) + register struct tbl *vq; + const char *s; +{ + 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: assigning to self"); + 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) /* <sjg> don't lie */ + vq->flag |= ALLOC; + } + } else /* integer dest */ + /* XXX is this correct? */ + v_evaluate(vq, s, FALSE); + vq->flag |= ISSET; + if ((vq->flag&SPECIAL)) + setspec(vq); +} + +/* 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(vq, str_val(vp)); + } 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) /* redundent 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->field) /* default field width */ + vp->field = olen; + nlen = vp->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->field) { + s += slen - vp->field; + slen = vp->field; + } + shf_snprintf(p, nlen + 1, + ((vp->flag & ZEROFIL) && digit(*s)) ? + "%0*s%.*s" : "%*s%.*s", + vp->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->field, vp->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; + int 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 envirnment: 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) : 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) { + /* 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->field = field; + if (fake_assign) { + setstr(t, s); + if (free_me) + afree((void *) free_me, t->areap); + } + } + } + + if (val != NULL) { + if (vp->flag&INTEGER) { + /* do not zero base before assignment */ + setstr(vp, val); + /* Done after assignment to override default */ + if (base > 0) + vp->type = base; + } else + setstr(vp, val); + } + + /* 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; + } + vp->flag &= SPECIAL; /* Should ``unspecial'' some vars */ + if (vp->flag & SPECIAL) + unsetspec(vp); +} + +/* 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) { + /* 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; + setstr(vp, val); + } + XPput(env, vp->val.s); + } + XPput(env, NULL); + return (char **) XPclose(env); +} + +/* + * 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->type : V_NONE; +} + +#ifdef KSH +static time_t seconds; /* time SECONDS last set */ +#endif /* KSH */ + +static void +getspec(vp) + register struct tbl *vp; +{ + switch (special(vp->name)) { +#ifdef KSH + case V_SECONDS: + vp->flag &= ~SPECIAL; + 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 */ + } +} + +static void +setspec(vp) + register struct tbl *vp; +{ + char *s; + + switch (special(vp->name)) { + case V_PATH: + path = str_val(vp); + flushcom(1); /* clear tracked aliases */ + break; + case V_IFS: + setctypes(s = str_val(vp), C_IFS); + ifs0 = *s; + break; + case V_OPTIND: + getopts_reset((int) intval(vp)); + break; + case V_MAIL: + mbset(str_val(vp)); + break; + case V_MAILPATH: + mpset(str_val(vp)); + break; + case V_MAILCHECK: + /* mail_check_set(intval(vp)); */ + 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_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 */ + } +} + +static void +unsetspec(vp) + register struct tbl *vp; +{ + switch (special(vp->name)) { + case V_PATH: + path = def_path; + flushcom(1); /* clear tracked aliases */ + break; + case V_IFS: + setctypes(" \t\n", C_IFS); + ifs0 = ' '; + break; + case V_MAIL: + mbset((char *) 0); + break; + case V_MAILPATH: + mpset((char *) 0); + break; + case V_TMPDIR: + /* should not become unspecial */ + if (tmpdir) { + afree(tmpdir, APERM); + tmpdir = (char *) 0; + } + break; +#ifdef KSH + case V_TMOUT: + /* at&t ksh doesn't do this. TMOUT becomes unspecial so + * future assignments don't have effect. Could be + * useful (eg, after "TMOUT=60; unset TMOUT", user + * can't get rid of the timeout...). Should be handled + * by generic unset code... + */ + ksh_tmout = 0; + break; +#endif /* KSH */ + /* todo: generic action for specials (at&t says variables + * loose their special meaning when unset but global() checks + * the name of new vars to see if they are special) + * loose meaning: _, ERRNO, LINENO, MAILCHECK, + * OPTARG, OPTIND, RANDOM, SECONDS, TMOUT. + * unknown: MAIL, MAILPATH, HISTSIZE, HISTFILE, + * no effect: IFS, COLUMNS, PATH, TMPDIR, + * VISUAL, EDITOR, + * 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->field = vp->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); + for (i = 0; vals[i]; i++) { + vq = arraysearch(vp, i); + setstr(vq, vals[i]); + } +} diff --git a/bin/pdksh/version.c b/bin/pdksh/version.c new file mode 100644 index 00000000000..5c71a81d14b --- /dev/null +++ b/bin/pdksh/version.c @@ -0,0 +1,10 @@ +/* $OpenBSD: version.c,v 1.1 1996/08/14 06:19:12 downsj Exp $ */ + +/* + * value of $KSH_VERSION (or $SH_VERSION) + */ + +#include "sh.h" + +const char ksh_version [] = + "@(#)PD KSH v5.2.7 96/06/04"; diff --git a/bin/pdksh/vi.c b/bin/pdksh/vi.c new file mode 100644 index 00000000000..c4b2aeeee33 --- /dev/null +++ b/bin/pdksh/vi.c @@ -0,0 +1,2208 @@ +/* $OpenBSD: vi.c,v 1.1 1996/08/14 06:19:12 downsj Exp $ */ + +/* + * vi command editing + * written by John Rochester (initially for nsh) + * bludgeoned to fit pdksh by Larry Bouzane, Jeff Sparkes & Eric Gisin + * + */ +#include "config.h" +#ifdef VI + +#include "sh.h" +#include <ctype.h> +#include "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 do_comment ARGS((void)); +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)); + +#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, 0, 0, 0, 0, 0, + /* 04 <space> ! " # $ % & ' */ + M_, 0, 0, C_, M_, M_, 0, 0, + /* 05 ( ) * + , - . / */ + 0, 0, C_, C_, M_, C_, 0, C_|S_, + /* 06 0 1 2 3 4 5 6 7 */ + M_, 0, 0, 0, 0, 0, 0, 0, + /* 07 8 9 : ; < = > ? */ + 0, 0, 0, M_, 0, C_, 0, C_|S_, + /* 010 @ A B C D E F G */ + C_|X_, C_, M_, C_, C_, M_, M_|X_, C_|U_|Z_, + /* 011 H I J K L M N O */ + 0, C_, 0, 0, 0, 0, C_|U_, 0, + /* 012 P Q R S T U V W */ + C_, 0, C_, C_, M_|X_, C_, 0, M_, + /* 013 X Y Z [ \ ] ^ _ */ + C_, C_|U_, 0, 0, C_|Z_, 0, M_, C_|Z_, + /* 014 ` a b c d e f g */ + 0, C_, M_, E_, E_, M_, M_|X_, C_|Z_, + /* 015 h i j k l m n o */ + M_, C_, C_|U_, C_|U_, M_, 0, C_|U_, 0, + /* 016 p q r s t u v w */ + C_, 0, X_, C_, M_|X_, C_|U_, C_|U_|Z_,M_, + /* 017 x y z { | } ~ ^? */ + C_, E_|U_, 0, 0, M_|Z_, 0, C_, 0 +}; + +#define MAXVICMD 3 +#define SRCHLEN 40 + +#define INSERT 1 +#define REPLACE 2 + +#define VNORMAL 0 /* command, insert or replace mode */ +#define VARG1 1 /* digit prefix (first, eg, 5l) */ +#define VEXTCMD 2 /* cmd + movement (eg, cl) */ +#define VARG2 3 /* digit prefix (second, eg, 2c3l) */ +#define VXCH 4 /* f, F, t, T, @ */ +#define VFAIL 5 /* bad command */ +#define VCMD 6 /* single char command (eg, X) */ +#define VREDO 7 /* . */ +#define VLIT 8 /* ^V */ +#define VSEARCH 9 /* /, ? */ +#define VVERSION 10 /* <ESC> ^V */ + +static char undocbuf[CMDLEN]; + +static struct edstate *save_edstate 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) strcpy(srchpat, locpat); + } + 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) != 0) { + 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 - don't redraw the screen */ + return 1; + } + 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 '#': + return do_comment(); + + 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; + /* FALLTHROUGH */ + + case Ctrl('['): /* some annoying at&t ksh's */ + 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; + strcpy(new->cbuf, old->cbuf); + 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; +} + +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++ = es->cbuf[cur] ^ '@'; + col++; + } + } else { + *twb1++ = es->cbuf[cur]; + 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(x_cols - 2, 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; +} + +/* Handle the commenting/uncommenting of a line */ +static int +do_comment() +{ + int i, j; + + if (es->linelen == 0) + return 1; /* somewhat arbitrary - it's what at&t ksh does */ + + /* Already commented? */ + if (es->cbuf[0] == '#') { + int saw_nl = 0; + + for (j = 0, i = 1; i < es->linelen; i++) { + if (!saw_nl || es->cbuf[i] != '#') + es->cbuf[j++] = es->cbuf[i]; + saw_nl = es->cbuf[i] == '\n'; + } + es->linelen = j; + es->cursor = 0; + return 0; + } else { + int n = 1; + + /* See if there's room for the #'s - 1 per \n */ + for (i = 0; i < es->linelen; i++) + if (es->cbuf[i] == '\n') + n++; + if (es->linelen + n >= es->cbufsize) + return -1; + /* Now add them... */ + for (i = es->linelen, j = es->linelen + n; --i >= 0; ) { + if (es->cbuf[i] == '\n') + es->cbuf[--j] = '#'; + es->cbuf[--j] = es->cbuf[i]; + } + es->cbuf[0] = '#'; + es->linelen += n; + es->cursor = 0; + return 1; + } +} + +/* 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 (putbuf(words[i], (int) strlen(words[i]), 0) != 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; + if (putbuf(match, match_len, 0) != 0) + rval = -1; + else if (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 wierdness */ +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 */ |