lobase

Linux port of OpenBSDs userland.
Log | Files | Refs | README

commit 80c20201bd002d621170beff142450dd5cce0243
parent 11a2f5ec63d119c70ad1f583cfa34efd0431e20c
Author: Duncaen <mail@duncano.de>
Date:   Wed,  8 Mar 2017 22:42:42 +0100

bin/ksh: import

Diffstat:
bin/Makefile | 2+-
bin/ksh/CONTRIBUTORS | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/LEGAL | 12++++++++++++
bin/ksh/Makefile | 18++++++++++++++++++
bin/ksh/NOTES | 296+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/PROJECTS | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/README | 22++++++++++++++++++++++
bin/ksh/alloc.c | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/c_ksh.c | 1409+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/c_sh.c | 889+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/c_test.c | 564+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/c_test.h | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/c_ulimit.c | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/edit.c | 833+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/edit.h | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/emacs.c | 2176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/eval.c | 1340+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/exec.c | 1444+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/expand.h | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/expr.c | 596+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/history.c | 981+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/io.c | 444+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/jobs.c | 1668+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/ksh.1 | 5563+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/lex.c | 1675+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/lex.h | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/mail.c | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/main.c | 838+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/misc.c | 1153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/path.c | 266+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/sh.1 | 2220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/sh.h | 608+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/shf.c | 998+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/shf.h | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/syn.c | 897+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/table.c | 249+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/table.h | 191+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/trap.c | 423+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/tree.c | 700+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/tree.h | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/tty.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/tty.h | 21+++++++++++++++++++++
bin/ksh/var.c | 1209+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
bin/ksh/version.c | 10++++++++++
bin/ksh/vi.c | 2132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
include/compat.h | 3+++
include/stdlib.h | 3+++
47 files changed, 33250 insertions(+), 1 deletion(-)

diff --git a/bin/Makefile b/bin/Makefile @@ -1,4 +1,4 @@ TOPDIR?=.. -SUBDIR= cat chmod cp date dd df domainname echo ed expr hostname kill ln ls \ +SUBDIR= cat chmod cp date dd df domainname echo ed expr hostname kill ksh ln ls\ md5 mkdir mv pax pwd rm rmdir sleep stty sync test include ${.TOPDIR}/mk/bsd.subdir.mk diff --git a/bin/ksh/CONTRIBUTORS b/bin/ksh/CONTRIBUTORS @@ -0,0 +1,129 @@ +$OpenBSD: CONTRIBUTORS,v 1.10 2006/02/06 16:47:07 jmc Exp $ + +This is a partial history of this shell gleened from old change logs and +readmes (most of which are still in the misc directory) and the source +code. Hopefully it is correct and no contributors have been left out +(file a bug report if you spot a problem :-)). + +Release history: + * Eric Gisin (egisin@math.uwaterloo.ca), created pdksh, using + Charles Forsyth's public domain V7 shell as a base; also used parts + of the BRL shell (written by Doug A Gwyn, Doug Kingston, Ron Natalie, + Arnold Robbins, Lou Salkind, and others?, circa '87; the parts used in + pdksh included getopts, test builtin, ulimit, tty setting/getting, emacs + editing, and job control; the test builtin was based on code by Erik + Baalbergen). + '87..'89 ? + Released versions: .. 3.2 + * John R MacMillan (@yonge.csri.toronto.edu:chance!john@sq.sq.com) + takes over as maintainer + dates? + Released versions: 3.3 (?) + * Simon J. Gerraty (sjg@zen.void.oz.au) takes over as maintainer + Nov '91..July '94 ? + Released versions: 4.0 .. 4.9 + * Michael Rendell (michael@cs.mun.ca) takes over as maintainer + July, 1994 + Released versions: 5.0 .. 5.2 + +Major contributions: + * John R MacMillan (@yonge.csri.toronto.edu:chance!john@sq.sq.com), ?: + cleaned up configuration, many bug fixes (see misc/Changes.jrm). + * Simon Gerraty, (sjg@zen.void.oz.au), Nov '91..?: much improved emacs mode + ala at&t ksh, 386bsd port, sigaction routines for non-POSIX systems + (see misc/ChangeLog.sjg and misc/ReadME.sjg). + * Peter Collinson (pc@hillside.co.uk), July '92: added select, at&t ksh + style history file, original csh-style {} globbing, BSD/386 port, + misc bug fixes. + * Larry Bouzane (larry@compusult.nf.ca), Mar '89..'93: re-wrote job control, + added async job notification, added CDPATH and other cd fixes, misc bug + fixes. + * John Rochester (jr@cs.mun.ca), '87: wrote vi command line editor; various + bug fixes/enhancements. + * Jeff Sparkes (jsparkes@bnr.ca), Mar '89..Mar '90: added arrays, + merged John Rochester's vi code into pdksh, misc bug fixes. + * Michael Haardt (u31b3hs@POOL.Informatik.RWTH-Aachen.DE), Sept '94: + organized man page, filled in many of its copious blank spots; added + KSH ifdefs. + * Dale DePriest (daled@cadence.com): ported to OS/2 (initially based on + port of pdksh4.9 to OS/2 by Kai Rommel (rommel@ars.muc.de)); maintains + OS/2 port; misc bug fixes. + +Other contributors: + * Piercarlo Grandi (pcg@aber.ac.uk), Dec '93: fixes for linux port + * Neil Smithline (Neil.Smithline@eng.sun.com), Aug '92: emacs-style + filename completion. + * Mike Jetzer [mlj] (jetzer@studsys.mscs.mu.edu), ?;Nov '94: fixes for vi + mode (see misc/Changes.mlj), added v to vi, fixes for history; fixed + command redoing in vi; fixes to vi globbing. + * Robert J Gibson: mailbox checking code that was adapted for pdksh by + John R. MacMillan. + * ? (guy@demon.co.uk), ?: promptlen() function. + * J.T. Conklin (jtc@cygnus.com): POSIXized test builtin; miscellaneous + fixes/enhancements. + * Sean Hogan (sean@neweast.ca): fixes for ICS 3.0 Unix, found and helped + fix numerous problems. + * Gordan Larson (hoh@approve.se): fix to compile sans VI, ksh.1 typo. + * Thomas Gellekum (thomas@ghpc8.ihf.rwth-aachen.de): fixes for Makefile + typos, fixed CLK_TCK for FreeBSD, man page fixes. + * Ed Ferguson (Ed.Ferguson@dseg.ti.com): fix to compile sans VI. + * Brian Campbell (brianc@qnx.com): fixes to compile under QNX and + to compile with dmake. + * (guy@netapp.com), Oct '94: patch to use gmacs flag. + * Andrew Moore (alm@netcom.com): reported many bugs, fixes. + * William Bader (wbader@CSEE.Lehigh.Edu): fix to compile on SCO Unix + (struct winsize). + * Mike Long (mike.long@analog.com): makefile fix - use $manext, not 1. + * Art Mills (aem@hpbs9162.bio.hp.com): bug fix for vi file completion in + command mode. + * Tory Bollinger (tboll@authstin.ibm.com): allow ~ in vi mode to take + a count. + * Frank Edwards (<crash@azhrei.EEC.COM>): added macros to vi (@char). + * Fritz Heinrichmeyer (<Fritz.Heinrichmeyer@FernUni-Hagen.de>): fixes + to allow compile under Linux 1.4.3. + * Gabor Zahemszky (<zgabor@CoDe.hu>): SVR3_PGRP vs SYSV_PGRP, many + bug reports and man page fixes. + * Dave Kinchlea (<kinch@julian.uwo.ca>): DEFAULT_ENV patches. + * Paul Borman (<prb@bsdi.com>): j_exit: send HUP, then CONT; HUP fg process. + * DaviD W. Sanderson (<dws@ssec.wisc.edu>): patches to allow { .. } instead + of in .. esac in case statements. + * ? (<ra@rhi.hi.is>): partial patches to handle SIGWINCH for command line + editing. + * Jason Tyler (<jason@nc.bhpese.oz.au>): fixes for bugs in fc. + * Stefan Dalibor (<Stefan.Dalibor@informatik.uni-erlangen.de>): fix for + COLUMNS never being set in x_init(). + * Arnon Kanfi (<arnon@gilly.datatools.com>): fix for prompt. + * Marc Olzheim (<marcolz@stack.nl>): patches to ifdef KSH the mail check + code and aliases; enum patches for old K&R compilers; handle missing dup2. + * Lars Hecking (<lhecking@nmrc.ucc.ie>): fixes so shell compiles as sh + again. + * Bill Kish (<kish@browncow.com>): added prompt delimiter hack for + hidden characters (eg, escape codes). + * Andrew S. Townley (<atownley@informix.com>): fixes for NeXT machines: + get a controlling if one needed, use correct profile. + * Eric J. Chet (<ejc@bazzle.com>): fix for core dump in . (quitenv() called + too soon). + * Greg A. Woods <woods@most.weird.com>: fix to make ^[_ in emacs work + as in at&t ksh. + * George Robbins <grr@shandakor.tharsis.com>: fix for sh mode to + keep exec'd file descriptors open. + * George White <gwhite@bodnext.bio.dfo.ca>: fix here-doc problem under OS/2 + (memory allocated incorrectly). + * David E. Wexelblat <dwex@DataFocus.com>: fix to avoid memory overrun + in aresize(); fix to not print un-named options. + * Clifford Wolf (<clifford@clifford.at>): fix memory overrun in aresize(); + fixed sys_siglist[] problem. + * Theo de Raadt (<deraadt@cvs.openbsd.org>): allow ". /dev/null". + * Eric Youngdale (<ericy@datafocus.com>): flag field incorrectly changed + in exec.c(flushcom). + * Todd. C Miller (Todd C. Miller <Todd.Miller@courtesan.com>): fix + for coredump in jobs. + * Kevin Schoedel <schoedel@kw.igs.net>: fix for word location in file + completion. + * Martin Lucina <mato@kotelna.sk>: fix for argument parsing in exit command, + fix for KSH_CHECK_H_TYPE. + * Mark Funkenhauser <mark@interix.com>: added $LINENO support. + * Corinna Vinschen <Corinna@Vinschen.de> and Steven Hein <ssh@sgi.com>: + port to cyngin environment on win95/winnt. + * Martin Dalecki <dalecki@cs.net.pl>: changes for 8 bit emacs mode. + * Dave Hillman <daveh@gte.net>: patch for bug in test -nt. diff --git a/bin/ksh/LEGAL b/bin/ksh/LEGAL @@ -0,0 +1,12 @@ +$OpenBSD: LEGAL,v 1.2 2003/07/17 20:59:43 deraadt Exp $ + +pdksh is provided AS IS, with NO WARRANTY, either expressed or implied. + +The vast majority of the code that makes pdksh is in the public domain. +The exceptions are: + sigact.c and sigact.h + [REMOVED] + aclocal.m4 + [REMOVED] + +That's it. Short and simple. diff --git a/bin/ksh/Makefile b/bin/ksh/Makefile @@ -0,0 +1,18 @@ +# $OpenBSD: Makefile,v 1.33 2016/03/30 06:38:40 jmc Exp $ + +.TOPDIR?=../.. + +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 path.c shf.c syn.c table.c trap.c tree.c tty.c var.c \ + version.c vi.c + +DEFS= -Wall +CFLAGS+=${DEFS} -I. -I${.CURDIR} -I${.CURDIR}/../../lib/libopenbsd/gen +MAN= ksh.1 sh.1 + +LINKS= ${BINDIR}/ksh ${BINDIR}/rksh +LINKS+= ${BINDIR}/ksh ${BINDIR}/sh + +include ${.TOPDIR}/mk/bsd.prog.mk diff --git a/bin/ksh/NOTES b/bin/ksh/NOTES @@ -0,0 +1,296 @@ +$OpenBSD: NOTES,v 1.14 2016/01/29 11:50:40 tb Exp $ + +General features of at&t ksh88 that are not (yet) in pdksh: + - exported aliases and functions (not in ksh93). + - set -t. + - signals/traps not cleared during functions. + - trap DEBUG, local ERR and EXIT traps in functions. + - ERRNO parameter. + - doesn't have posix file globbing (eg, [[:alpha:]], etc.). + - use of an `agent' to execute unreadable/setuid/setgid shell scripts + (don't ask). + - read/select aren't hooked in to the command line editor + - the last command of a pipeline is not run in the parent shell + +Known bugs (see also BUG-REPORTS and PROJECTS files): + Variable parsing, Expansion: + - some specials behave differently when unset (eg, IFS behaves like + " \t\n") others lose their special meaning. IFS/PATH taken care of, + still need to sort out some others (eg, TMOUT). + Parsing,Lexing: + - line numbers in errors are wrong for nested constructs. Need to + keep track of the line a command started on (can use for LINENO + parameter as well). + - a $(..) expression nested inside double quotes inside another $(..) + isn't parsed correctly (eg, $(echo "foo$(echo ")")") ) + Commands,Execution: + - setting special parameters that have side effects when + changed/restored (ie, HISTFILE, OPTIND, RANDOM) in front + of a command (eg, HISTFILE=/foo/bar echo hi) effects the parent + shell. Note that setting other (not so special) parameters + does not effect the parent shell. + - `echo hi | exec cat -n' causes at&t to exit, `exec echo hi | cat -n' + does not. pdksh exits for neither. Don't think POSIX requires + an exit, but not sure. + - `echo foo | read bar; echo $bar' prints foo in at&t ksh, nothing + in pdksh (ie, the read is done in a separate process in pdksh). + Misc: + +Known problems not caused by ksh: + - after stoping a job, emacs/vi is not re-entered. Hitting return + prints the prompt and everything is fine again. Problem (often + involving a pager like less) is related to order of process + scheduling (shell runs before `stop'ed (sub) processes have had a chance + to clean up the screen/terminal). + +Known differences between pdksh & at&t ksh (that may change) + - vi: + - `^U': at&t: kills only what has been inserted, pdksh: kills to + start of line + - at&t ksh login shells say "Warning: you have running jobs" if you + try to exit when there are running jobs. An immediate second attempt + to exit will kill the jobs and exit. pdksh does not print a warning, + nor does it kill running jobs when it exits (it does warn/kill for + stopped jobs). + - TMOUT: at&t prints warning, then waits another 60 seconds. If on screwed + up serial line, the output could cause more input, so pdksh just + prints a message and exits. (Also, in at&t ksh, setting TMOUT has no + effect after the sequence "TMOUT=60; unset TMOUT", which could be + useful - pdksh may do this in the future). + - in pdksh, if the last command of a pipeline is a shell builtin, it is + not executed in the parent shell, so "echo a b | read foo bar" does not + set foo and bar in the parent shell (at&t ksh will). + This may get fixed in the future, but it may take a while. + - in pdksh, set +o lists the options that are currently set, in at&t ksh + it is the same as set -o. + - in pdksh emacs mode, ^T does what gnu emacs does, not what at&t ksh + does. + - in ksh93, `. name' calls a function (defined with function) with POSIX + semantics (instead of ksh semantics). in pdksh, . does not call + functions. + - test: "test -f foo bar blah" is the same as "test -f foo" (the extra + arguments, of which there must be at least 2, are ignored) - pdksh + generates an error message (unexpected operator/operand "bar") as it + should. Sometimes used to test file globs (e.g., if test -f *.o; ...). + - if the command 'sleep 5 && /bin/echo blah' is run interactively and + is the sleep is stopped (^Z), the echo is run immediately in pdksh. + In at&t ksh, the whole thing is stopped. + - LINENO: + - in ksh88 variable is always 1 (can't be changed) in interac mode; + in pdksh it changes. + - Value of LINENO after it has been set by the script in one file + is bizarre when used in another file. + +Known differences between pdksh & at&t ksh (that are not likely to change) + - at&t ksh seems to catch or ignore SIGALRM - pdksh dies upon receipt + (unless it's traped of course) + - typeset: + - at&t ksh overloads -u/-l options: for integers, means unsigned/long, + for strings means uppercase/lowercase; pdksh just has the + upper/lower case (which can be useful for integers when base > 10). + unsigned/long really should have their own options. + - at&t ksh can't have justified integer variables + (eg, typeset -iR5 j=10), pdksh can. + - in pdksh, number arguments for -L/-R/-Z/-i must follow the option + character, at&t allows it at the end of the option group (eg, + at&t ksh likes "typeset -iu5 j", pdksh wants "typeset -i5 -u j" + or "typeset -ui5 j"). Also, pdksh allows "typeset -i 5 j" (same + as "typeset -i5 j"), at&t ksh does not allow this. + - typeset -R: pdksh strips trailing space type characters (ie, + uses isspace()), at&t ksh only skips blanks. + - at&t ksh allows attributes of read-only variables to be changed, + pdksh allows only the export attribute to be set. + - (some) at&t ksh allows set -A of readonly variables, pdksh does not. + - at&t ksh allows command assignments of readonly variables (eg, YY=2 cat), + pdksh does not. + - at&t ksh does not exit scripts when an implicit assignment to an integer + variable fails due to an expression error: eg, + echo 2+ > /tmp/x + unset x; typeset -i x + read x < /tmp/x + echo still here + prints an error and then prints "still here", similarly for + unset x; typeset -i x + set +A x 1 2+ 3 + echo still here + and + unset x y; typeset -i x y; set +A y 10 20 30 + set +A x 1 1+y[2+] 3 + echo still here + pdksh exits a script in all the above cases. (note that both shells + exit for: + unset x; typeset -i x + for x in 1 2+ 3; do echo x=$x; done + echo still here + ). + - at&t ksh seems to allow function calls inside expressions + (eg, typeset -i x='y(2)') but they do not seem to be regular functions + nor math functions (eg, pow, exp) - anyone known anything about this? + - `set -o nounset; unset foo; echo ${#foo}`: at&t ksh prints 0; pdksh + generates error. Same for ${#foo[*]} and ${#foo[@]}. + - . file: at&t ksh parses the whole file before executing anything, + pdksh executes as it parses. This means aliases defined in the file + will affect how pdksh parses the file, but won't affect how at&t ksh + parses the file. Also means pdksh will not parse statements occurring + after a (executed) return statement. + - a return in $ENV in at&t ksh will cause the shell to exit, while in + pdksh it will stop executing the script (this is consistent with + what a return in .profile does in both shells). + - at&t ksh does file globbing for `echo "${foo:-"*"}"`, pdksh does not + (POSIX would seem to indicate pdksh is right). + - at&t ksh thinks ${a:##foo} is ok, pdksh doesn't. + - at&t does tilde expansion on here-document delimiters, pdksh does + not. eg. + $ cat << ~michael + ~michael + $ + works for pdksh, not for at&t ksh (POSIX seems to agree with pdksh). + - in at&t ksh, tracked aliases have the export flag implicitly set + and tracked aliases and normal aliases live in the same name space + (eg, "alias" will list both tracked and normal aliases). + in pdksh, -t does not imply -x (since -x doesn't do anything yet), and + tracked/normal aliases live in separate name spaces. + in at&t ksh, alias accepts + options (eg, +x, +t) - pdksh does not. + in pdksh, alias has a -d option to allow examination/changing of + cached ~ entries, also unalias has -d and -t options (unalias -d + is useful if the ~ cache gets out of date - not sure how at&t deals + with this problem (it does cache ~ entries)). + - at&t ksh will stop a recursive function after about 60 calls; pdksh + will not since the limit is arbitrary and can't be controlled + by the user (hit ^C if you get in trouble). + - the wait command (with and without arguments) in at&t ksh will wait for + stopped jobs when job control is enabled. pdksh doesn't. + - at&t ksh automatically sets the bgnice option for interactive shells; + pdksh does not. + - in at&t ksh, "eval `false`; echo $?" prints 1, pdksh prints 0 (which + is what POSIX says it should). Same goes for "wait `false`; echo $?". + (same goes for "set `false`; echo $?" if posix option is set - some + scripts that use the old getopt depend on this, so be careful about + setting the posix option). + - in at&t ksh, print -uX and read -uX are interrperted as -u with no + argument (defaults to 1 and 0 respectively) and -X (which may or + may not be a valid flag). In pdksh, -uX is interpreted as file + descriptor X. + - in at&t ksh, some signals (HUP, INT, QUIT) cause the read to exit, others + (ie, everything else) do not. When it does cause exiting, anything read + to that point is used (usually an empty line) and read returns with 0 + status. pdksh currently does similar things, but for TERM as well and + the exit status is 128+<signal-number> - in future, pdksh's read will + do this for all signals that are normally fatal as required by POSIX. + (POSIX does not require the setting of variables to null so applications + shouldn't rely on this). + - in pdksh, ! substitution done before variable substitution; in at&t ksh + it is done after substitution (and therefore may do ! substitutions on + the result of variable substitutions). POSIX doesn't say which is to be + done. + - pwd: in at&t ksh, it ignores arguments; in pdksh, it complains when given + arguments. + - the at&t ksh does not do command substition on PS1, pdksh does. + - ksh93 allows ". foo" to run the function foo if there is no file + called foo (go figure). + - field splitting (IFS): ksh88/ksh93 strip leading non-white space IFS + chars, pdksh (and POSIX, I think) leave them intact. e.g. + $ IFS="$IFS:"; read x; echo "<$x>" + :: + prints "<>" in at&t ksh, "<::>" in pdksh. + - command completion: at&t ksh will do completion on a blank line (matching + all commands), pdksh does not (as this isn't very useful - use * if + you really want the list). + - co-processes: if ksh93, the write portion of the co-process output is + closed when the most recently started co-process exits. pdksh closes + it when all the co-processes using it have exited. + - pdksh accepts empty command lists for while and for statements, while + at&t ksh (and sh) don't. Eg., pdksh likes + while false ; do done + but ksh88 doesn't like it. + - pdksh bumps RANDOM in parent after a fork, at&t ksh bumps it in both + parent and child: + RANDOM=1 + echo child: `echo $RANDOM` + echo parent: $RANDOM + will produce "child: 16838 parent: 5758" in pdksh, while at&t ksh + will produce "child: 5758 parent: 5758". + +Oddities in ksh (pd & at&t): + - array references inside (())/$(()) are strange: + $(( x[2] )) does the expected, $(( $x[2] )) doesn't. + - `typeset -R3 X='x '; echo "($X)"` produces ( x) - trailing + spaces are stripped. + - typeset -R turns off Z flag. + - both shells have the following mis-feature: + $ x='function xx { + cat -n <<- EOF + here we are in xx + EOF + }' + $ (eval "$x"; (sleep 2; xx) & echo bye) + [1] 1234 + bye + $ xx: /tmp/sh1234.1: cannot open + - bizarre special handling of alias/export/readonly/typeset arguments + $ touch a=a; typeset a=[ab]; echo "$a" + a=[ab] + $ x=typeset; $x a=[ab]; echo "$a" + a=a + $ + - both ignore SIGTSTP,SIGTTIN,SIGTTOU in exec'd processes when talking + and not monitoring (at&t ksh kind of does this). Doesn't really make + sense. + (Note that ksh.att -ic 'set +m; check-sigs' shows TSTP et al aren't + ignored, while ksh.att -ic 'set +m^J check-sigs' does... very strange) + - when tracing (set -x), and a command's stderr is redirected, the trace + output is also redirected. so "set -x; echo foo 2> /tmp/O > /dev/null" + will create /tmp/foo with the lines "+ > /dev/null" and "+ echo foo". + - undocumented at&t ksh feature: FPATH is searched after PATH if no + executable is found, even if typeset -uf wasn't used. + +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/ksh/PROJECTS b/bin/ksh/PROJECTS @@ -0,0 +1,111 @@ +$OpenBSD: PROJECTS,v 1.8 2015/09/14 09:42:33 nicm Exp $ + +Things to be done in pdksh (see also the NOTES file): + + * builtin utilities: + pdksh has most if not all POSIX/at&t ksh builtins, but they need to + be checked that they conform to POSIX/at&t manual. Part of the + process is changing the builtins to use the ksh_getopt() routine. + + The following builtins, which are defined by POSIX, haven't been + examined: + eval + + The first pass has been done on the following commands: + . : alias bg break cd continue echo exec exit export false fc fg + getopts jobs kill pwd read readonly return set shift time trap true + umask unalias unset wait + + The second pass (ie, believed to be completely POSIX) has been done on + the following commands: + test + + (ulimit also needs to be examined to check that it fits the posix style) + + * test suite + Ideally, as the builtin utilities are being POSIXized, short tests + should be written to be used in regression testing. The tests + directory contains some tests, but many more need to be written. + + * internationalization + Need to handle with the LANG and LC_* environment variables. This + involves changes to ensure <ctype.h> macros are being used (currently + uses its own macros in many places), figuring out how to deal with + bases (for integer arithmetic, eg, 12#1A), and (the nasty one) doing + string look ups for error messages, etc.. It probably isn't worth + translating strings to other languages yet as the code is likely + to change a lot in the near future, but it would be good to have the + code set up so string tables can be used. + + * trap code + * add the DEBUG trap. + * fix up signal handling code. In particular, fatal vs tty signals, + have signal routine to call to check for pending/fatal traps, etc. + + * parsing + * the time keyword needs to be hacked to accept options (!) since + POSIX says it shall accept the -p option and must skip a -- argument + (end of options). Yuck. + + * lexing + the lexing may need a re-write since it currently doesn't parse $( .. ), + $(( .. )), (( ... )) properly. + * need to ignore contents of quoted strings (and escaped chars?) + inside $( .. ) and $(( .. )) when counting parentheses. + * need to put bounds check on states[] array (if it still exists after + the re-write) + + * variables + * The "struct tbl" that is currently used for variables needs work since + more information (eg, array stuff, fields) are needed for variables + but not for the other things that use "struct tbl". + * Arrays need to be implemented differently: currently does a linear + search of a linked list to find element i; the linked list is not + freed when a variable is unset. + + * functions + finish the differences between function x and x(): trap EXIT, traps + in general, treatment of OPTIND/OPTARG, + + * history + There are two versions of the history code, COMPLEX_HISTORY and + EASY_HISTORY, which need to be merged. COMPLEX does at&t style history + where the history file is written after each command and checked when + ever looking through the history (in case another shell has added + something). EASY simply reads the history file at startup and writes + it before exiting. + * re-write the COMPLEX_HISTORY code so mmap() not needed (currently + can't be used on machines without mmap()). + * Add multiline knowledge to COMPLEX_HISTORY (see EASY_HISTORY + stuff). + * change COMPLEX_HISTORY code so concurrent history files are + controlled by an option (set -o history-concurrent?). Delete + the EASY_HISTORY code. + * bring history code up to POSIX standards (see POSIX description + of fc, etc.). + + * documentation + Some sort of tutorial with examples would be good. Texinfo is probably + the best medium for this. Also, the man page could be converted to + texinfo (if the tutorial and man page are put in the same texinfo + page, they should be somewhat distinct - i.e., the tutorial should + be a separate thread - but there should be cross references between the + two). + + * miscellaneous + * POSIX specifies what happens when various kinds of errors occur + in special built-ins commands vs regular commands (builtin or + otherwise) (see POSIX.2:3.8.1). Some of this has been taken + care of, but more needs doing. + + * remove static limits created by fixed sized arrays + (eg, ident[], heres[], PATH, buffer size in emacs/vi code) + + * merge the emacs and vi code (should reduce the size of the shell and + make maintenance easier); handle SIGWINCH while editing a line. + [John Rochester is working on the merge] + + * add POSIX globbing (eg, [[:alnum:]]), see POSIX.2:2.8.3.2. + + * teach shf_vfprintf() about long long's (%lld); also make %p use + long longs if appropriate. diff --git a/bin/ksh/README b/bin/ksh/README @@ -0,0 +1,22 @@ +$OpenBSD: README,v 1.15 2015/12/05 19:40:45 mmcc Exp $ + +Last updated Jul '99 for pdksh-5.2.14. + +PD-ksh is a mostly complete AT&T ksh look-alike (see NOTES file for a list +of things not supported). Work is mostly finished to make it fully +compatible with both POSIX and AT&T ksh (when the two don't conflict). + +PDksh was 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. + +Files of interest: + 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. + LEGAL A file detailing legal issues concerning pdksh. + + +BTW, THE MOST FREQUENTLY REPORTED BUG IS + echo hi | read a; echo $a # Does not print hi +I'm aware of this and there is no need to report it. diff --git a/bin/ksh/alloc.c b/bin/ksh/alloc.c @@ -0,0 +1,136 @@ +/* $OpenBSD: alloc.c,v 1.15 2016/06/01 10:29:20 espie Exp $ */ + +/* Public domain, like most of the rest of ksh */ + +/* + * area-based allocation built on malloc/free + */ + +#include <stdint.h> +#include <stdlib.h> + +#include "sh.h" + +struct link { + struct link *prev; + struct link *next; +}; + +Area * +ainit(Area *ap) +{ + ap->freelist = NULL; + return ap; +} + +void +afreeall(Area *ap) +{ + struct link *l, *l2; + + for (l = ap->freelist; l != NULL; l = l2) { + l2 = l->next; + free(l); + } + ap->freelist = NULL; +} + +#define L2P(l) ( (void *)(((char *)(l)) + sizeof(struct link)) ) +#define P2L(p) ( (struct link *)(((char *)(p)) - sizeof(struct link)) ) + +void * +alloc(size_t size, Area *ap) +{ + struct link *l; + + /* ensure that we don't overflow by allocating space for link */ + if (size > SIZE_MAX - sizeof(struct link)) + internal_errorf(1, "unable to allocate memory"); + + l = malloc(sizeof(struct link) + size); + if (l == NULL) + internal_errorf(1, "unable to allocate memory"); + l->next = ap->freelist; + l->prev = NULL; + if (ap->freelist) + ap->freelist->prev = l; + ap->freelist = l; + + return L2P(l); +} + +/* + * Copied from calloc(). + * + * This is sqrt(SIZE_MAX+1), as s1*s2 <= SIZE_MAX + * if both s1 < MUL_NO_OVERFLOW and s2 < MUL_NO_OVERFLOW + */ +#define MUL_NO_OVERFLOW (1UL << (sizeof(size_t) * 4)) + +void * +areallocarray(void *ptr, size_t nmemb, size_t size, Area *ap) +{ + /* condition logic cloned from calloc() */ + if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && + nmemb > 0 && SIZE_MAX / nmemb < size) { + internal_errorf(1, "unable to allocate memory"); + } + + return aresize(ptr, nmemb * size, ap); +} + +void * +aresize(void *ptr, size_t size, Area *ap) +{ + struct link *l, *l2, *lprev, *lnext; + + if (ptr == NULL) + return alloc(size, ap); + + /* ensure that we don't overflow by allocating space for link */ + if (size > SIZE_MAX - sizeof(struct link)) + internal_errorf(1, "unable to allocate memory"); + + l = P2L(ptr); + lprev = l->prev; + lnext = l->next; + + l2 = realloc(l, sizeof(struct link) + size); + if (l2 == NULL) + internal_errorf(1, "unable to allocate memory"); + if (lprev) + lprev->next = l2; + else + ap->freelist = l2; + if (lnext) + lnext->prev = l2; + + return L2P(l2); +} + +void +afree(void *ptr, Area *ap) +{ + struct link *l, *l2; + + if (!ptr) + return; + + l = P2L(ptr); + + for (l2 = ap->freelist; l2 != NULL; l2 = l2->next) { + if (l == l2) + break; + } + if (l2 == NULL) + internal_errorf(1, "afree: %p not present in area %p", ptr, ap); + + if (l->prev) + l->prev->next = l->next; + else + ap->freelist = l->next; + if (l->next) + l->next->prev = l->prev; + + free(l); +} diff --git a/bin/ksh/c_ksh.c b/bin/ksh/c_ksh.c @@ -0,0 +1,1409 @@ +/* $OpenBSD: c_ksh.c,v 1.50 2016/03/21 13:35:00 tb Exp $ */ + +/* + * built-in Korn commands: c_* + */ + +#include <sys/stat.h> + +#include <ctype.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include "sh.h" + +int +c_cd(char **wp) +{ + int optc; + int physical = Flag(FPHYSICAL); + int cdnode; /* was a node from cdpath added in? */ + int printpath = 0; /* print where we cd'd? */ + int rval; + struct tbl *pwd_s, *oldpwd_s; + XString xs; + char *xp; + char *dir, *try, *pwd; + int phys_path; + char *cdpath; + char *fdir = NULL; + + while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1) + switch (optc) { + case 'L': + physical = 0; + break; + case 'P': + physical = 1; + break; + case '?': + return 1; + } + wp += builtin_opt.optind; + + if (Flag(FRESTRICTED)) { + bi_errorf("restricted shell - can't cd"); + return 1; + } + + pwd_s = global("PWD"); + oldpwd_s = global("OLDPWD"); + + if (!wp[0]) { + /* No arguments - go home */ + if ((dir = str_val(global("HOME"))) == null) { + bi_errorf("no home directory (HOME not set)"); + return 1; + } + } else if (!wp[1]) { + /* One argument: - or dir */ + dir = wp[0]; + if (strcmp(dir, "-") == 0) { + dir = str_val(oldpwd_s); + if (dir == null) { + bi_errorf("no OLDPWD"); + return 1; + } + printpath++; + } + } else if (!wp[2]) { + /* Two arguments - substitute arg1 in PWD for arg2 */ + int ilen, olen, nlen, elen; + char *cp; + + if (!current_wd[0]) { + bi_errorf("don't know current directory"); + return 1; + } + /* substitute arg1 for arg2 in current path. + * if the first substitution fails because the cd fails + * we could try to find another substitution. For now + * we don't + */ + if ((cp = strstr(current_wd, wp[0])) == NULL) { + bi_errorf("bad substitution"); + return 1; + } + ilen = cp - current_wd; + olen = strlen(wp[0]); + nlen = strlen(wp[1]); + elen = strlen(current_wd + ilen + olen) + 1; + fdir = dir = alloc(ilen + nlen + elen, ATEMP); + memcpy(dir, current_wd, ilen); + memcpy(dir + ilen, wp[1], nlen); + memcpy(dir + ilen + nlen, current_wd + ilen + olen, elen); + printpath++; + } else { + bi_errorf("too many arguments"); + return 1; + } + + Xinit(xs, xp, PATH, ATEMP); + /* xp will have a bogus value after make_path() - set it to 0 + * so that if it's used, it will cause a dump + */ + xp = NULL; + + cdpath = str_val(global("CDPATH")); + do { + cdnode = make_path(current_wd, dir, &cdpath, &xs, &phys_path); + if (physical) + rval = chdir(try = Xstring(xs, xp) + phys_path); + else { + simplify_path(Xstring(xs, xp)); + rval = chdir(try = Xstring(xs, xp)); + } + } while (rval < 0 && cdpath != NULL); + + if (rval < 0) { + if (cdnode) + bi_errorf("%s: bad directory", dir); + else + bi_errorf("%s - %s", try, strerror(errno)); + afree(fdir, ATEMP); + return 1; + } + + /* Clear out tracked aliases with relative paths */ + flushcom(0); + + /* Set OLDPWD (note: unsetting OLDPWD does not disable this + * setting in at&t ksh) + */ + if (current_wd[0]) + /* Ignore failure (happens if readonly or integer) */ + setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR); + + if (Xstring(xs, xp)[0] != '/') { + pwd = NULL; + } else + if (!physical || !(pwd = get_phys_path(Xstring(xs, xp)))) + pwd = Xstring(xs, xp); + + /* Set PWD */ + if (pwd) { + char *ptmp = pwd; + set_current_wd(ptmp); + /* Ignore failure (happens if readonly or integer) */ + setstr(pwd_s, ptmp, KSH_RETURN_ERROR); + } else { + set_current_wd(null); + pwd = Xstring(xs, xp); + /* XXX unset $PWD? */ + } + if (printpath || cdnode) + shprintf("%s\n", pwd); + + afree(fdir, ATEMP); + + return 0; +} + +int +c_pwd(char **wp) +{ + int optc; + int physical = Flag(FPHYSICAL); + char *p, *freep = NULL; + + while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != -1) + switch (optc) { + case 'L': + physical = 0; + break; + case 'P': + physical = 1; + break; + case '?': + return 1; + } + wp += builtin_opt.optind; + + if (wp[0]) { + bi_errorf("too many arguments"); + return 1; + } + p = current_wd[0] ? (physical ? get_phys_path(current_wd) : current_wd) : + NULL; + if (p && access(p, R_OK) < 0) + p = NULL; + if (!p) { + freep = p = ksh_get_wd(NULL, 0); + if (!p) { + bi_errorf("can't get current directory - %s", + strerror(errno)); + return 1; + } + } + shprintf("%s\n", p); + afree(freep, ATEMP); + return 0; +} + +int +c_print(char **wp) +{ +#define PO_NL BIT(0) /* print newline */ +#define PO_EXPAND BIT(1) /* expand backslash sequences */ +#define PO_PMINUSMINUS BIT(2) /* print a -- argument */ +#define PO_HIST BIT(3) /* print to history instead of stdout */ +#define PO_COPROC BIT(4) /* printing to coprocess: block SIGPIPE */ + int fd = 1; + int flags = PO_EXPAND|PO_NL; + char *s; + const char *emsg; + XString xs; + char *xp; + + if (wp[0][0] == 'e') { /* echo command */ + int nflags = flags; + + /* A compromise between sysV and BSD echo commands: + * escape sequences are enabled by default, and + * -n, -e and -E are recognized if they appear + * in arguments with no illegal options (ie, echo -nq + * will print -nq). + * Different from sysV echo since options are recognized, + * different from BSD echo since escape sequences are enabled + * by default. + */ + wp += 1; + if (Flag(FPOSIX)) { + if (*wp && strcmp(*wp, "-n") == 0) { + flags &= ~PO_NL; + wp++; + } + } else { + while ((s = *wp) && *s == '-' && s[1]) { + while (*++s) + if (*s == 'n') + nflags &= ~PO_NL; + else if (*s == 'e') + nflags |= PO_EXPAND; + else if (*s == 'E') + nflags &= ~PO_EXPAND; + else + /* bad option: don't use + * nflags, print argument + */ + break; + if (*s) + break; + wp++; + flags = nflags; + } + } + } else { + int optc; + const char *options = "Rnprsu,"; + while ((optc = ksh_getopt(wp, &builtin_opt, options)) != -1) + switch (optc) { + case 'R': /* fake BSD echo command */ + flags |= PO_PMINUSMINUS; + flags &= ~PO_EXPAND; + options = "ne"; + break; + case 'e': + flags |= PO_EXPAND; + break; + case 'n': + flags &= ~PO_NL; + break; + case 'p': + if ((fd = coproc_getfd(W_OK, &emsg)) < 0) { + bi_errorf("-p: %s", emsg); + return 1; + } + break; + case 'r': + flags &= ~PO_EXPAND; + break; + case 's': + flags |= PO_HIST; + break; + case 'u': + if (!*(s = builtin_opt.optarg)) + fd = 0; + else if ((fd = check_fd(s, W_OK, &emsg)) < 0) { + bi_errorf("-u: %s: %s", s, emsg); + return 1; + } + break; + case '?': + return 1; + } + if (!(builtin_opt.info & GI_MINUSMINUS)) { + /* treat a lone - like -- */ + if (wp[builtin_opt.optind] && + strcmp(wp[builtin_opt.optind], "-") == 0) + builtin_opt.optind++; + } else if (flags & PO_PMINUSMINUS) + builtin_opt.optind--; + wp += builtin_opt.optind; + } + + Xinit(xs, xp, 128, ATEMP); + + while (*wp != NULL) { + int c; + s = *wp; + while ((c = *s++) != '\0') { + Xcheck(xs, xp); + if ((flags & PO_EXPAND) && c == '\\') { + int i; + + switch ((c = *s++)) { + /* Oddly enough, \007 seems more portable than + * \a (due to HP-UX cc, Ultrix cc, old pcc's, + * etc.). + */ + case 'a': c = '\007'; break; + case 'b': c = '\b'; break; + case 'c': flags &= ~PO_NL; + continue; /* AT&T brain damage */ + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = 0x0B; break; + case '0': + /* Look for an octal number: can have + * three digits (not counting the + * leading 0). Truly burnt. + */ + c = 0; + for (i = 0; i < 3; i++) { + if (*s >= '0' && *s <= '7') + c = c*8 + *s++ - '0'; + else + break; + } + break; + case '\0': s--; c = '\\'; break; + case '\\': break; + default: + Xput(xs, xp, '\\'); + } + } + Xput(xs, xp, c); + } + if (*++wp != NULL) + Xput(xs, xp, ' '); + } + if (flags & PO_NL) + Xput(xs, xp, '\n'); + + if (flags & PO_HIST) { + Xput(xs, xp, '\0'); + source->line++; + histsave(source->line, Xstring(xs, xp), 1); + Xfree(xs, xp); + } else { + int n, len = Xlength(xs, xp); + int opipe = 0; + + /* Ensure we aren't killed by a SIGPIPE while writing to + * a coprocess. at&t ksh doesn't seem to do this (seems + * to just check that the co-process is alive, which is + * not enough). + */ + if (coproc.write >= 0 && coproc.write == fd) { + flags |= PO_COPROC; + opipe = block_pipe(); + } + for (s = Xstring(xs, xp); len > 0; ) { + n = write(fd, s, len); + if (n < 0) { + if (flags & PO_COPROC) + restore_pipe(opipe); + if (errno == EINTR) { + /* allow user to ^C out */ + intrcheck(); + if (flags & PO_COPROC) + opipe = block_pipe(); + continue; + } + /* This doesn't really make sense - could + * break scripts (print -p generates + * error message). + *if (errno == EPIPE) + * coproc_write_close(fd); + */ + return 1; + } + s += n; + len -= n; + } + if (flags & PO_COPROC) + restore_pipe(opipe); + } + + return 0; +} + +int +c_whence(char **wp) +{ + struct tbl *tp; + char *id; + int pflag = 0, vflag = 0, Vflag = 0; + int ret = 0; + int optc; + int iam_whence = wp[0][0] == 'w'; + int fcflags; + const char *options = iam_whence ? "pv" : "pvV"; + + while ((optc = ksh_getopt(wp, &builtin_opt, options)) != -1) + switch (optc) { + case 'p': + pflag = 1; + break; + case 'v': + vflag = 1; + break; + case 'V': + Vflag = 1; + break; + case '?': + return 1; + } + wp += builtin_opt.optind; + + + fcflags = FC_BI | FC_PATH | FC_FUNC; + if (!iam_whence) { + /* Note that -p on its own is dealt with in comexec() */ + if (pflag) + fcflags |= FC_DEFPATH; + /* Convert command options to whence options. Note that + * command -pV and command -pv use a different path search + * than whence -v or whence -pv. This should be considered + * a feature. + */ + vflag = Vflag; + } else if (pflag) + fcflags &= ~(FC_BI | FC_FUNC); + + while ((vflag || ret == 0) && (id = *wp++) != NULL) { + tp = NULL; + if (!iam_whence || !pflag) + tp = ktsearch(&keywords, id, hash(id)); + if (!tp && (!iam_whence || !pflag)) { + tp = ktsearch(&aliases, id, hash(id)); + if (tp && !(tp->flag & ISSET)) + tp = NULL; + } + if (!tp) + tp = findcom(id, fcflags); + if (vflag || (tp->type != CALIAS && tp->type != CEXEC && + tp->type != CTALIAS)) + shprintf("%s", id); + switch (tp->type) { + case CKEYWD: + if (vflag) + shprintf(" is a reserved word"); + break; + case CALIAS: + if (vflag) + shprintf(" is an %salias for ", + (tp->flag & EXPORT) ? "exported " : ""); + 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" : ""); + 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 " : ""); + } + 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("\n"); + } + return ret; +} + +/* Deal with command -vV - command -p dealt with in comexec() */ +int +c_command(char **wp) +{ + /* Let c_whence do the work. Note that c_command() must be + * a distinct function from c_whence() (tested in comexec()). + */ + return c_whence(wp); +} + +/* typeset, export, and readonly */ +int +c_typeset(char **wp) +{ + struct block *l; + struct tbl *vp, **p; + int fset = 0, fclr = 0, thing = 0, func = 0, local = 0, pflag = 0; + const char *options = "L#R#UZ#fi#lprtux"; /* see comment below */ + char *fieldstr, *basestr; + int field, base, optc, flag; + + 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 = NULL; + builtin_opt.flags |= GF_PLUSOPT; + /* at&t ksh seems to have 0-9 as options, which are multiplied + * to get a number that is used with -L, -R, -Z or -i (eg, -1R2 + * sets right justify in a field of 12). This allows options + * to be grouped in an order (eg, -Lu12), but disallows -i8 -L3 and + * does not allow the number to be specified as a separate argument + * Here, the number must follow the RLZi option, but is optional + * (see the # kludge in ksh_getopt()). + */ + while ((optc = ksh_getopt(wp, &builtin_opt, options)) != -1) { + flag = 0; + switch (optc) { + case 'L': + flag = LJUST; + fieldstr = builtin_opt.optarg; + break; + case 'R': + flag = RJUST; + fieldstr = builtin_opt.optarg; + break; + case 'U': + /* at&t ksh uses u, but this conflicts with + * upper/lower case. If this option is changed, + * need to change the -U below as well + */ + flag = INT_U; + break; + case 'Z': + flag = ZEROFIL; + fieldstr = builtin_opt.optarg; + break; + case 'f': + func = 1; + break; + case 'i': + flag = INTEGER; + basestr = builtin_opt.optarg; + break; + case 'l': + flag = LCASEV; + break; + case 'p': + /* posix export/readonly -p flag. + * typeset -p is the same as typeset (in pdksh); + * here for compatibility with ksh93. + */ + pflag = 1; + break; + case 'r': + flag = RDONLY; + break; + case 't': + flag = TRACE; + break; + case 'u': + flag = UCASEV_AL; /* upper case / autoload */ + break; + case 'x': + flag = EXPORT; + break; + case '?': + return 1; + } + if (builtin_opt.info & GI_PLUS) { + fclr |= flag; + fset &= ~flag; + thing = '+'; + } else { + fset |= flag; + fclr &= ~flag; + thing = '-'; + } + } + + field = 0; + if (fieldstr && !bi_getn(fieldstr, &field)) + return 1; + base = 0; + if (basestr && !bi_getn(basestr, &base)) + return 1; + + if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind] && + (wp[builtin_opt.optind][0] == '-' || + wp[builtin_opt.optind][0] == '+') && + wp[builtin_opt.optind][1] == '\0') { + thing = wp[builtin_opt.optind][0]; + builtin_opt.optind++; + } + + if (func && ((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT))) { + bi_errorf("only -t, -u and -x options may be used with -f"); + return 1; + } + if (wp[builtin_opt.optind]) { + /* Take care of exclusions. + * At this point, flags in fset are cleared in fclr and vise + * versa. This property should be preserved. + */ + if (fset & LCASEV) /* LCASEV has priority over UCASEV_AL */ + fset &= ~UCASEV_AL; + if (fset & LJUST) /* LJUST has priority over RJUST */ + fset &= ~RJUST; + if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { /* -Z implies -ZR */ + fset |= RJUST; + fclr &= ~RJUST; + } + /* Setting these attributes clears the others, unless they + * are also set in this command + */ + if (fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | LCASEV | + INTEGER | INT_U | INT_L)) + fclr |= ~fset & (LJUST | RJUST | ZEROFIL | UCASEV_AL | + LCASEV | INTEGER | INT_U | INT_L); + } + + /* set variables and attributes */ + if (wp[builtin_opt.optind]) { + int i; + int rval = 0; + struct tbl *f; + + if (local && !func) + fset |= LOCAL; + for (i = builtin_opt.optind; wp[i]; i++) { + if (func) { + f = findfunc(wp[i], hash(wp[i]), + (fset&UCASEV_AL) ? true : false); + if (!f) { + /* at&t ksh does ++rval: bogus */ + rval = 1; + continue; + } + if (fset | fclr) { + f->flag |= fset; + f->flag &= ~fclr; + } else + fptreef(shl_stdout, 0, + f->flag & FKSH ? + "function %s %T\n" : + "%s() %T\n", wp[i], f->val.t); + } else if (!typeset(wp[i], fset, fclr, field, base)) { + bi_errorf("%s: not identifier", wp[i]); + return 1; + } + } + return rval; + } + + /* list variables and attributes */ + flag = fset | fclr; /* no difference at this point.. */ + if (func) { + for (l = genv->loc; l; l = l->next) { + for (p = ktsort(&l->funs); (vp = *p++); ) { + if (flag && (vp->flag & flag) == 0) + continue; + if (thing == '-') + fptreef(shl_stdout, 0, vp->flag & FKSH ? + "function %s %T\n" : "%s() %T\n", + vp->name, vp->val.t); + else + shprintf("%s\n", vp->name); + } + } + } else { + for (l = genv->loc; l; l = l->next) { + for (p = ktsort(&l->vars); (vp = *p++); ) { + struct tbl *tvp; + int any_set = 0; + /* + * See if the parameter is set (for arrays, if any + * element is set). + */ + for (tvp = vp; tvp; tvp = tvp->u.array) + if (tvp->flag & ISSET) { + any_set = 1; + break; + } + + /* + * Check attributes - note that all array elements + * have (should have?) the same attributes, so checking + * the first is sufficient. + * + * Report an unset param only if the user has + * explicitly given it some attribute (like export); + * otherwise, after "echo $FOO", we would report FOO... + */ + if (!any_set && !(vp->flag & USERATTRIB)) + continue; + if (flag && (vp->flag & flag) == 0) + continue; + for (; vp; vp = vp->u.array) { + /* Ignore array elements that aren't + * set unless there are no set elements, + * in which case the first is reported on */ + if ((vp->flag&ARRAY) && any_set && + !(vp->flag & ISSET)) + continue; + /* no arguments */ + if (thing == 0 && flag == 0) { + /* at&t ksh prints things + * like export, integer, + * leftadj, zerofill, etc., + * but POSIX says must + * be suitable for re-entry... + */ + shprintf("typeset "); + if ((vp->flag&INTEGER)) + shprintf("-i "); + if ((vp->flag&EXPORT)) + shprintf("-x "); + if ((vp->flag&RDONLY)) + shprintf("-r "); + if ((vp->flag&TRACE)) + shprintf("-t "); + if ((vp->flag&LJUST)) + shprintf("-L%d ", vp->u2.field); + if ((vp->flag&RJUST)) + shprintf("-R%d ", vp->u2.field); + if ((vp->flag&ZEROFIL)) + shprintf("-Z "); + if ((vp->flag&LCASEV)) + shprintf("-l "); + if ((vp->flag&UCASEV_AL)) + shprintf("-u "); + if ((vp->flag&INT_U)) + shprintf("-U "); + shprintf("%s\n", vp->name); + if (vp->flag&ARRAY) + break; + } else { + if (pflag) + shprintf("%s ", + (flag & EXPORT) ? + "export" : "readonly"); + if ((vp->flag&ARRAY) && any_set) + shprintf("%s[%d]", + vp->name, vp->index); + else + shprintf("%s", vp->name); + if (thing == '-' && (vp->flag&ISSET)) { + char *s = str_val(vp); + + shprintf("="); + /* at&t ksh can't have + * justified integers.. */ + if ((vp->flag & + (INTEGER|LJUST|RJUST)) == + INTEGER) + shprintf("%s", s); + else + print_value_quoted(s); + } + shprintf("\n"); + } + /* Only report first `element' of an array with + * no set elements. + */ + if (!any_set) + break; + } + } + } + } + return 0; +} + +int +c_alias(char **wp) +{ + struct table *t = &aliases; + int rv = 0, rflag = 0, tflag, Uflag = 0, pflag = 0, prefix = 0; + int xflag = 0; + int optc; + + builtin_opt.flags |= GF_PLUSOPT; + while ((optc = ksh_getopt(wp, &builtin_opt, "dprtUx")) != -1) { + prefix = builtin_opt.info & GI_PLUS ? '+' : '-'; + switch (optc) { + case 'd': + t = &homedirs; + break; + case 'p': + pflag = 1; + break; + case 'r': + rflag = 1; + break; + case 't': + t = &taliases; + break; + case 'U': + /* + * kludge for tracked alias initialization + * (don't do a path search, just make an entry) + */ + Uflag = 1; + break; + case 'x': + xflag = EXPORT; + break; + case '?': + return 1; + } + } + wp += builtin_opt.optind; + + if (!(builtin_opt.info & GI_MINUSMINUS) && *wp && + (wp[0][0] == '-' || wp[0][0] == '+') && wp[0][1] == '\0') { + prefix = wp[0][0]; + wp++; + } + + tflag = t == &taliases; + + /* "hash -r" means reset all the tracked aliases.. */ + if (rflag) { + static const char *const args[] = { + "unalias", "-ta", NULL + }; + + if (!tflag || *wp) { + shprintf("alias: -r flag can only be used with -t" + " and without arguments\n"); + return 1; + } + ksh_getopt_reset(&builtin_opt, GF_ERROR); + return c_unalias((char **) args); + } + + if (*wp == NULL) { + struct tbl *ap, **p; + + for (p = ktsort(t); (ap = *p++) != NULL; ) + if ((ap->flag & (ISSET|xflag)) == (ISSET|xflag)) { + if (pflag) + shf_puts("alias ", shl_stdout); + shf_puts(ap->name, shl_stdout); + if (prefix != '+') { + shf_putc('=', shl_stdout); + print_value_quoted(ap->val.s); + } + shprintf("\n"); + } + } + + for (; *wp != NULL; wp++) { + char *alias = *wp; + char *val = strchr(alias, '='); + char *newval; + struct tbl *ap; + int h; + + if (val) + alias = str_nsave(alias, val++ - alias, ATEMP); + h = hash(alias); + if (val == NULL && !tflag && !xflag) { + ap = ktsearch(t, alias, h); + if (ap != NULL && (ap->flag&ISSET)) { + if (pflag) + shf_puts("alias ", shl_stdout); + shf_puts(ap->name, shl_stdout); + if (prefix != '+') { + shf_putc('=', shl_stdout); + print_value_quoted(ap->val.s); + } + shprintf("\n"); + } else { + shprintf("%s alias not found\n", alias); + rv = 1; + } + continue; + } + ap = ktenter(t, alias, h); + ap->type = tflag ? CTALIAS : CALIAS; + /* Are we setting the value or just some flags? */ + if ((val && !tflag) || (!val && tflag && !Uflag)) { + if (ap->flag&ALLOC) { + ap->flag &= ~(ALLOC|ISSET); + afree(ap->val.s, APERM); + } + /* ignore values for -t (at&t ksh does this) */ + newval = tflag ? search(alias, path, X_OK, NULL) : + val; + if (newval) { + ap->val.s = str_save(newval, APERM); + ap->flag |= ALLOC|ISSET; + } else + ap->flag &= ~ISSET; + } + ap->flag |= DEFINED; + if (prefix == '+') + ap->flag &= ~xflag; + else + ap->flag |= xflag; + if (val) + afree(alias, ATEMP); + } + + return rv; +} + +int +c_unalias(char **wp) +{ + struct table *t = &aliases; + struct tbl *ap; + int rv = 0, all = 0; + int optc; + + while ((optc = ksh_getopt(wp, &builtin_opt, "adt")) != -1) + switch (optc) { + case 'a': + all = 1; + break; + case 'd': + t = &homedirs; + break; + case 't': + t = &taliases; + break; + case '?': + return 1; + } + wp += builtin_opt.optind; + + for (; *wp != NULL; wp++) { + ap = ktsearch(t, *wp, hash(*wp)); + if (ap == NULL) { + rv = 1; /* POSIX */ + continue; + } + if (ap->flag&ALLOC) { + ap->flag &= ~(ALLOC|ISSET); + afree(ap->val.s, APERM); + } + ap->flag &= ~(DEFINED|ISSET|EXPORT); + } + + if (all) { + struct tstate ts; + + for (ktwalk(&ts, t); (ap = ktnext(&ts)); ) { + if (ap->flag&ALLOC) { + ap->flag &= ~(ALLOC|ISSET); + afree(ap->val.s, APERM); + } + ap->flag &= ~(DEFINED|ISSET|EXPORT); + } + } + + return rv; +} + +int +c_let(char **wp) +{ + int rv = 1; + long val; + + if (wp[1] == NULL) /* at&t ksh does this */ + bi_errorf("no arguments"); + else + for (wp++; *wp; wp++) + if (!evaluate(*wp, &val, KSH_RETURN_ERROR, true)) { + rv = 2; /* distinguish error from zero result */ + break; + } else + rv = val == 0; + return rv; +} + +int +c_jobs(char **wp) +{ + int optc; + int flag = 0; + int nflag = 0; + int rv = 0; + + while ((optc = ksh_getopt(wp, &builtin_opt, "lpnz")) != -1) + switch (optc) { + case 'l': + flag = 1; + break; + case 'p': + flag = 2; + break; + case 'n': + nflag = 1; + break; + case 'z': /* debugging: print zombies */ + nflag = -1; + break; + case '?': + return 1; + } + wp += builtin_opt.optind; + if (!*wp) { + if (j_jobs(NULL, flag, nflag)) + rv = 1; + } else { + for (; *wp; wp++) + if (j_jobs(*wp, flag, nflag)) + rv = 1; + } + return rv; +} + +#ifdef JOBS +int +c_fgbg(char **wp) +{ + int bg = strcmp(*wp, "bg") == 0; + int rv = 0; + + if (!Flag(FMONITOR)) { + bi_errorf("job control not enabled"); + return 1; + } + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return 1; + wp += builtin_opt.optind; + if (*wp) + for (; *wp; wp++) + rv = j_resume(*wp, bg); + else + rv = j_resume("%%", bg); + /* POSIX says fg shall return 0 (unless an error occurs). + * at&t ksh returns the exit value of the job... + */ + return (bg || Flag(FPOSIX)) ? 0 : rv; +} +#endif + +struct kill_info { + int num_width; + int name_width; +}; +static char *kill_fmt_entry(void *arg, int i, char *buf, int buflen); + +/* format a single kill item */ +static char * +kill_fmt_entry(void *arg, int i, char *buf, int buflen) +{ + struct kill_info *ki = (struct kill_info *) arg; + + i++; + if (sigtraps[i].name) + shf_snprintf(buf, buflen, "%*d %*s %s", + ki->num_width, i, + ki->name_width, sigtraps[i].name, + sigtraps[i].mess); + else + shf_snprintf(buf, buflen, "%*d %*d %s", + ki->num_width, i, + ki->name_width, sigtraps[i].signal, + sigtraps[i].mess); + return buf; +} + + +int +c_kill(char **wp) +{ + Trap *t = NULL; + char *p; + int lflag = 0; + int i, n, rv, sig; + + /* assume old style options if -digits or -UPPERCASE */ + if ((p = wp[1]) && *p == '-' && + (digit(p[1]) || isupper((unsigned char)p[1]))) { + if (!(t = gettrap(p + 1, true))) { + bi_errorf("bad signal `%s'", p + 1); + return 1; + } + i = (wp[2] && strcmp(wp[2], "--") == 0) ? 3 : 2; + } else { + int optc; + + while ((optc = ksh_getopt(wp, &builtin_opt, "ls:")) != -1) + switch (optc) { + case 'l': + lflag = 1; + break; + case 's': + if (!(t = gettrap(builtin_opt.optarg, true))) { + bi_errorf("bad signal `%s'", + builtin_opt.optarg); + return 1; + } + break; + case '?': + return 1; + } + i = builtin_opt.optind; + } + if ((lflag && t) || (!wp[i] && !lflag)) { + shf_fprintf(shl_out, + "usage: kill [-s signame | -signum | -signame] { job | pid | pgrp } ...\n" + " kill -l [exit_status ...]\n"); + bi_errorf(NULL); + return 1; + } + + if (lflag) { + if (wp[i]) { + for (; wp[i]; i++) { + if (!bi_getn(wp[i], &n)) + return 1; + if (n > 128 && n < 128 + NSIG) + n -= 128; + if (n > 0 && n < NSIG && sigtraps[n].name) + shprintf("%s\n", sigtraps[n].name); + else + shprintf("%d\n", n); + } + } else if (Flag(FPOSIX)) { + p = null; + for (i = 1; i < NSIG; i++, p = " ") + if (sigtraps[i].name) + shprintf("%s%s", p, sigtraps[i].name); + shprintf("\n"); + } else { + int mess_width = 0, w, i; + struct kill_info ki = { + .num_width = 1, + .name_width = 0, + }; + + for (i = NSIG; i >= 10; i /= 10) + ki.num_width++; + + for (i = 0; i < NSIG; i++) { + w = sigtraps[i].name ? strlen(sigtraps[i].name) : + ki.num_width; + if (w > ki.name_width) + ki.name_width = w; + w = strlen(sigtraps[i].mess); + if (w > mess_width) + mess_width = w; + } + + print_columns(shl_stdout, NSIG - 1, + kill_fmt_entry, (void *) &ki, + ki.num_width + ki.name_width + mess_width + 3, 1); + } + return 0; + } + rv = 0; + sig = t ? t->signal : SIGTERM; + for (; (p = wp[i]); i++) { + if (*p == '%') { + if (j_kill(p, sig)) + rv = 1; + } else if (!getn(p, &n)) { + bi_errorf("%s: arguments must be jobs or process IDs", + p); + rv = 1; + } else { + /* use killpg if < -1 since -1 does special things for + * some non-killpg-endowed kills + */ + if ((n < -1 ? killpg(-n, sig) : kill(n, sig)) < 0) { + bi_errorf("%s: %s", p, strerror(errno)); + rv = 1; + } + } + } + return rv; +} + +void +getopts_reset(int val) +{ + if (val >= 1) { + ksh_getopt_reset(&user_opt, + GF_NONAME | (Flag(FPOSIX) ? 0 : GF_PLUSOPT)); + user_opt.optind = user_opt.uoptind = val; + } +} + +int +c_getopts(char **wp) +{ + int argc; + const char *options; + const char *var; + int optc; + int ret; + char buf[3]; + struct tbl *vq, *voptarg; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return 1; + wp += builtin_opt.optind; + + options = *wp++; + if (!options) { + bi_errorf("missing options argument"); + return 1; + } + + var = *wp++; + if (!var) { + bi_errorf("missing name argument"); + return 1; + } + if (!*var || *skip_varname(var, true)) { + bi_errorf("%s: is not an identifier", var); + return 1; + } + + if (genv->loc->next == NULL) { + internal_errorf(0, "c_getopts: no argv"); + return 1; + } + /* Which arguments are we parsing... */ + if (*wp == NULL) + wp = genv->loc->next->argv; + else + *--wp = genv->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 = NULL; + optc = ksh_getopt(wp, &user_opt, options); + + if (optc >= 0 && optc != '?' && (user_opt.info & GI_PLUS)) { + buf[0] = '+'; + buf[1] = optc; + buf[2] = '\0'; + } else { + /* POSIX says var is set to ? at end-of-options, at&t ksh + * sets it to null - we go with POSIX... + */ + buf[0] = optc < 0 ? '?' : optc; + buf[1] = '\0'; + } + + /* at&t ksh does not change OPTIND if it was an unknown option. + * Scripts counting on this are prone to break... (ie, don't count + * on this staying). + */ + if (optc != '?') { + user_opt.uoptind = user_opt.optind; + } + + voptarg = global("OPTARG"); + voptarg->flag &= ~RDONLY; /* at&t ksh clears ro and int */ + /* Paranoia: ensure no bizarre results. */ + if (voptarg->flag & INTEGER) + typeset("OPTARG", 0, INTEGER, 0, 0); + if (user_opt.optarg == NULL) + unset(voptarg, 0); + else + /* This can't fail (have cleared readonly/integer) */ + setstr(voptarg, user_opt.optarg, KSH_RETURN_ERROR); + + ret = 0; + + vq = global(var); + /* Error message already printed (integer, readonly) */ + if (!setstr(vq, buf, KSH_RETURN_ERROR)) + ret = 1; + if (Flag(FEXPORT)) + typeset(var, EXPORT, 0, 0, 0); + + return optc < 0 ? 1 : ret; +} + +#ifdef EMACS +int +c_bind(char **wp) +{ + int optc, rv = 0, macro = 0, list = 0; + char *cp; + + while ((optc = ksh_getopt(wp, &builtin_opt, "lm")) != -1) + switch (optc) { + case 'l': + list = 1; + break; + case 'm': + macro = 1; + break; + case '?': + return 1; + } + wp += builtin_opt.optind; + + if (*wp == NULL) /* list all */ + rv = x_bind(NULL, 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/ksh/c_sh.c b/bin/ksh/c_sh.c @@ -0,0 +1,889 @@ +/* $OpenBSD: c_sh.c,v 1.59 2016/03/04 15:11:06 deraadt Exp $ */ + +/* + * built-in Bourne commands + */ + +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "sh.h" + +static void p_time(struct shf *, int, struct timeval *, int, char *, char *); + +/* :, false and true */ +int +c_label(char **wp) +{ + return wp[0][0] == 'f' ? 1 : 0; +} + +int +c_shift(char **wp) +{ + struct block *l = genv->loc; + int n; + long val; + char *arg; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return 1; + arg = wp[builtin_opt.optind]; + + if (arg) { + evaluate(arg, &val, KSH_UNWIND_ERROR, false); + n = val; + } else + n = 1; + if (n < 0) { + bi_errorf("%s: bad number", arg); + return (1); + } + if (l->argc < n) { + bi_errorf("nothing to shift"); + return (1); + } + l->argv[n] = l->argv[0]; + l->argv += n; + l->argc -= n; + return 0; +} + +int +c_umask(char **wp) +{ + int i; + char *cp; + int symbolic = 0; + mode_t old_umask; + int optc; + + while ((optc = ksh_getopt(wp, &builtin_opt, "S")) != -1) + switch (optc) { + case 'S': + symbolic = 1; + break; + case '?': + return 1; + } + cp = wp[builtin_opt.optind]; + if (cp == NULL) { + old_umask = umask(0); + umask(old_umask); + if (symbolic) { + char buf[18]; + int j; + + old_umask = ~old_umask; + cp = buf; + for (i = 0; i < 3; i++) { + *cp++ = "ugo"[i]; + *cp++ = '='; + for (j = 0; j < 3; j++) + if (old_umask & (1 << (8 - (3*i + j)))) + *cp++ = "rwx"[j]; + *cp++ = ','; + } + cp[-1] = '\0'; + shprintf("%s\n", buf); + } else + shprintf("%#3.3o\n", old_umask); + } else { + mode_t new_umask; + + if (digit(*cp)) { + for (new_umask = 0; *cp >= '0' && *cp <= '7'; cp++) + new_umask = new_umask * 8 + (*cp - '0'); + if (*cp) { + bi_errorf("bad number"); + return 1; + } + } else { + /* symbolic format */ + int positions, new_val; + char op; + + old_umask = umask(0); + umask(old_umask); /* in case of error */ + old_umask = ~old_umask; + new_umask = old_umask; + positions = 0; + while (*cp) { + while (*cp && strchr("augo", *cp)) + switch (*cp++) { + case 'a': + positions |= 0111; + break; + case 'u': + positions |= 0100; + break; + case 'g': + positions |= 0010; + break; + case 'o': + positions |= 0001; + break; + } + if (!positions) + positions = 0111; /* default is a */ + if (!strchr("=+-", op = *cp)) + break; + cp++; + new_val = 0; + while (*cp && strchr("rwxugoXs", *cp)) + switch (*cp++) { + case 'r': new_val |= 04; break; + case 'w': new_val |= 02; break; + case 'x': new_val |= 01; break; + case 'u': new_val |= old_umask >> 6; + break; + case 'g': new_val |= old_umask >> 3; + break; + case 'o': new_val |= old_umask >> 0; + break; + case 'X': if (old_umask & 0111) + new_val |= 01; + break; + case 's': /* ignored */ + break; + } + new_val = (new_val & 07) * positions; + switch (op) { + case '-': + new_umask &= ~new_val; + break; + case '=': + new_umask = new_val | + (new_umask & ~(positions * 07)); + break; + case '+': + new_umask |= new_val; + } + if (*cp == ',') { + positions = 0; + cp++; + } else if (!strchr("=+-", *cp)) + break; + } + if (*cp) { + bi_errorf("bad mask"); + return 1; + } + new_umask = ~new_umask; + } + umask(new_umask); + } + return 0; +} + +int +c_dot(char **wp) +{ + char *file, *cp; + char **argv; + int argc; + int i; + int err; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return 1; + + if ((cp = wp[builtin_opt.optind]) == NULL) + return 0; + file = search(cp, path, R_OK, &err); + if (file == NULL) { + bi_errorf("%s: %s", cp, err ? strerror(err) : "not found"); + return 1; + } + + /* Set positional parameters? */ + if (wp[builtin_opt.optind + 1]) { + argv = wp + builtin_opt.optind; + argv[0] = genv->loc->argv[0]; /* preserve $0 */ + for (argc = 0; argv[argc + 1]; argc++) + ; + } else { + argc = 0; + argv = NULL; + } + i = include(file, argc, argv, 0); + if (i < 0) { /* should not happen */ + bi_errorf("%s: %s", cp, strerror(errno)); + return 1; + } + return i; +} + +int +c_wait(char **wp) +{ + int rv = 0; + int sig; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return 1; + wp += builtin_opt.optind; + if (*wp == NULL) { + while (waitfor(NULL, &sig) >= 0) + ; + rv = sig; + } else { + for (; *wp; wp++) + rv = waitfor(*wp, &sig); + if (rv < 0) + rv = sig ? sig : 127; /* magic exit code: bad job-id */ + } + return rv; +} + +int +c_read(char **wp) +{ + int c = 0; + int expand = 1, history = 0; + int expanding; + int ecode = 0; + char *cp; + int fd = 0; + struct shf *shf; + int optc; + const char *emsg; + XString cs, xs; + struct tbl *vp; + char *xp = NULL; + + while ((optc = ksh_getopt(wp, &builtin_opt, "prsu,")) != -1) + switch (optc) { + case 'p': + if ((fd = coproc_getfd(R_OK, &emsg)) < 0) { + bi_errorf("-p: %s", emsg); + return 1; + } + break; + case 'r': + expand = 0; + break; + case 's': + history = 1; + break; + case 'u': + if (!*(cp = builtin_opt.optarg)) + fd = 0; + else if ((fd = check_fd(cp, R_OK, &emsg)) < 0) { + bi_errorf("-u: %s: %s", cp, emsg); + return 1; + } + break; + case '?': + return 1; + } + wp += builtin_opt.optind; + + if (*wp == NULL) + *--wp = "REPLY"; + + /* Since we can't necessarily seek backwards on non-regular files, + * don't buffer them so we can't read too much. + */ + shf = shf_reopen(fd, SHF_RD | SHF_INTERRUPT | can_seek(fd), shl_spare); + + if ((cp = strchr(*wp, '?')) != NULL) { + *cp = 0; + if (isatty(fd)) { + /* at&t ksh says it prints prompt on fd if it's open + * for writing and is a tty, but it doesn't do it + * (it also doesn't check the interactive flag, + * as is indicated in the Kornshell book). + */ + shellf("%s", cp+1); + } + } + + /* If we are reading from the co-process for the first time, + * make sure the other side of the pipe is closed first. This allows + * the detection of eof. + * + * This is not compatible with at&t ksh... the fd is kept so another + * coproc can be started with same output, however, this means eof + * can't be detected... This is why it is closed here. + * If this call is removed, remove the eof check below, too. + * coproc_readw_close(fd); + */ + + if (history) + Xinit(xs, xp, 128, ATEMP); + expanding = 0; + Xinit(cs, cp, 128, ATEMP); + for (; *wp != NULL; wp++) { + for (cp = Xstring(cs, cp); ; ) { + if (c == '\n' || c == EOF) + break; + while (1) { + c = shf_getc(shf); + if (c == '\0') + continue; + if (c == EOF && shf_error(shf) && + shf->errno_ == EINTR) { + /* Was the offending signal one that + * would normally kill a process? + * If so, pretend the read was killed. + */ + ecode = fatal_trap_check(); + + /* non fatal (eg, CHLD), carry on */ + if (!ecode) { + shf_clearerr(shf); + continue; + } + } + break; + } + if (history) { + Xcheck(xs, xp); + Xput(xs, xp, c); + } + Xcheck(cs, cp); + if (expanding) { + expanding = 0; + if (c == '\n') { + c = 0; + if (Flag(FTALKING_I) && isatty(fd)) { + /* set prompt in case this is + * called from .profile or $ENV + */ + set_prompt(PS2, NULL); + pprompt(prompt, 0); + } + } else if (c != EOF) + Xput(cs, cp, c); + continue; + } + if (expand && c == '\\') { + expanding = 1; + continue; + } + if (c == '\n' || c == EOF) + break; + if (ctype(c, C_IFS)) { + if (Xlength(cs, cp) == 0 && ctype(c, C_IFSWS)) + continue; + if (wp[1]) + break; + } + Xput(cs, cp, c); + } + /* strip trailing IFS white space from last variable */ + if (!wp[1]) + while (Xlength(cs, cp) && ctype(cp[-1], C_IFS) && + ctype(cp[-1], C_IFSWS)) + cp--; + Xput(cs, cp, '\0'); + vp = global(*wp); + /* Must be done before setting export. */ + if (vp->flag & RDONLY) { + shf_flush(shf); + bi_errorf("%s is read only", *wp); + return 1; + } + if (Flag(FEXPORT)) + typeset(*wp, EXPORT, 0, 0, 0); + if (!setstr(vp, Xstring(cs, cp), KSH_RETURN_ERROR)) { + shf_flush(shf); + return 1; + } + } + + shf_flush(shf); + if (history) { + Xput(xs, xp, '\0'); + source->line++; + histsave(source->line, Xstring(xs, xp), 1); + Xfree(xs, xp); + } + /* if this is the co-process fd, close the file descriptor + * (can get eof if and only if all processes are have died, ie, + * coproc.njobs is 0 and the pipe is closed). + */ + if (c == EOF && !ecode) + coproc_read_close(fd); + + return ecode ? ecode : c == EOF; +} + +int +c_eval(char **wp) +{ + struct source *s; + int rv; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return 1; + s = pushs(SWORDS, ATEMP); + s->u.strv = wp + builtin_opt.optind; + if (!Flag(FPOSIX)) { + /* + * Handle case where the command is empty due to failed + * command substitution, eg, eval "$(false)". + * In this case, shell() will not set/change exstat (because + * compiled tree is empty), so will use this value. + * subst_exstat is cleared in execute(), so should be 0 if + * there were no substitutions. + * + * A strict reading of POSIX says we don't do this (though + * it is traditionally done). [from 1003.2-1992] + * 3.9.1: Simple Commands + * ... If there is a command name, execution shall + * continue as described in 3.9.1.1. If there + * is no command name, but the command contained a command + * substitution, the command shall complete with the exit + * status of the last command substitution + * 3.9.1.1: Command Search and Execution + * ...(1)...(a) If the command name matches the name of + * a special built-in utility, that special built-in + * utility shall be invoked. + * 3.14.5: Eval + * ... If there are no arguments, or only null arguments, + * eval shall return an exit status of zero. + */ + exstat = subst_exstat; + } + + rv = shell(s, false); + afree(s, ATEMP); + return (rv); +} + +int +c_trap(char **wp) +{ + int i; + char *s; + Trap *p; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return 1; + wp += builtin_opt.optind; + + if (*wp == NULL) { + for (p = sigtraps, i = NSIG+1; --i >= 0; p++) { + if (p->trap != NULL) { + shprintf("trap -- "); + print_value_quoted(p->trap); + shprintf(" %s\n", p->name); + } + } + return 0; + } + + /* + * Use case sensitive lookup for first arg so the + * command 'exit' isn't confused with the pseudo-signal + * 'EXIT'. + */ + s = (gettrap(*wp, false) == NULL) ? *wp++ : NULL; /* get command */ + if (s != NULL && s[0] == '-' && s[1] == '\0') + s = NULL; + + /* set/clear traps */ + while (*wp != NULL) { + p = gettrap(*wp++, true); + if (p == NULL) { + bi_errorf("bad signal %s", wp[-1]); + return 1; + } + settrap(p, s); + } + return 0; +} + +int +c_exitreturn(char **wp) +{ + int how = LEXIT; + int n; + char *arg; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return 1; + arg = wp[builtin_opt.optind]; + + if (arg) { + if (!getn(arg, &n)) { + exstat = 1; + warningf(true, "%s: bad number", arg); + } else + exstat = n; + } + if (wp[0][0] == 'r') { /* return */ + struct env *ep; + + /* need to tell if this is exit or return so trap exit will + * work right (POSIX) + */ + for (ep = genv; ep; ep = ep->oenv) + if (STOP_RETURN(ep->type)) { + how = LRETURN; + break; + } + } + + if (how == LEXIT && !really_exit && j_stopped_running()) { + really_exit = 1; + how = LSHELL; + } + + quitenv(NULL); /* get rid of any i/o redirections */ + unwind(how); + /* NOTREACHED */ + return 0; +} + +int +c_brkcont(char **wp) +{ + int n, quit; + struct env *ep, *last_ep = NULL; + char *arg; + + if (ksh_getopt(wp, &builtin_opt, null) == '?') + return 1; + arg = wp[builtin_opt.optind]; + + if (!arg) + n = 1; + else if (!bi_getn(arg, &n)) + return 1; + quit = n; + if (quit <= 0) { + /* at&t ksh does this for non-interactive shells only - weird */ + bi_errorf("%s: bad value", arg); + return 1; + } + + /* Stop at E_NONE, E_PARSE, E_FUNC, or E_INCL */ + for (ep = genv; ep && !STOP_BRKCONT(ep->type); ep = ep->oenv) + if (ep->type == E_LOOP) { + if (--quit == 0) + break; + ep->flags |= EF_BRKCONT_PASS; + last_ep = ep; + } + + if (quit) { + /* at&t ksh doesn't print a message - just does what it + * can. We print a message 'cause it helps in debugging + * scripts, but don't generate an error (ie, keep going). + */ + if (n == quit) { + warningf(true, "%s: cannot %s", wp[0], wp[0]); + return 0; + } + /* POSIX says if n is too big, the last enclosing loop + * shall be used. Doesn't say to print an error but we + * do anyway 'cause the user messed up. + */ + if (last_ep) + last_ep->flags &= ~EF_BRKCONT_PASS; + warningf(true, "%s: can only %s %d level(s)", + wp[0], wp[0], n - quit); + } + + unwind(*wp[0] == 'b' ? LBREAK : LCONTIN); + /* NOTREACHED */ +} + +int +c_set(char **wp) +{ + int argi, setargs; + struct block *l = genv->loc; + char **owp = wp; + + if (wp[1] == NULL) { + static const char *const args [] = { "set", "-", NULL }; + return c_typeset((char **) args); + } + + argi = parse_args(wp, OF_SET, &setargs); + if (argi < 0) + return 1; + /* set $# and $* */ + if (setargs) { + owp = wp += argi - 1; + wp[0] = l->argv[0]; /* save $0 */ + while (*++wp != NULL) + *wp = str_save(*wp, &l->area); + l->argc = wp - owp - 1; + l->argv = areallocarray(NULL, l->argc+2, sizeof(char *), &l->area); + for (wp = l->argv; (*wp++ = *owp++) != NULL; ) + ; + } + /* POSIX says set exit status is 0, but old scripts that use + * getopt(1), use the construct: set -- `getopt ab:c "$@"` + * which assumes the exit value set will be that of the `` + * (subst_exstat is cleared in execute() so that it will be 0 + * if there are no command substitutions). + */ + return Flag(FPOSIX) ? 0 : subst_exstat; +} + +int +c_unset(char **wp) +{ + char *id; + int optc, unset_var = 1; + + while ((optc = ksh_getopt(wp, &builtin_opt, "fv")) != -1) + switch (optc) { + case 'f': + unset_var = 0; + break; + case 'v': + unset_var = 1; + break; + case '?': + return 1; + } + wp += builtin_opt.optind; + for (; (id = *wp) != NULL; wp++) + if (unset_var) { /* unset variable */ + struct tbl *vp = global(id); + + if ((vp->flag&RDONLY)) { + bi_errorf("%s is read only", vp->name); + return 1; + } + unset(vp, strchr(id, '[') ? 1 : 0); + } else { /* unset function */ + define(id, NULL); + } + return 0; +} + +static void +p_time(struct shf *shf, int posix, struct timeval *tv, int width, char *prefix, + char *suffix) +{ + if (posix) + shf_fprintf(shf, "%s%*lld.%02ld%s", prefix ? prefix : "", + width, (long long)tv->tv_sec, tv->tv_usec / 10000, suffix); + else + shf_fprintf(shf, "%s%*lldm%02lld.%02lds%s", prefix ? prefix : "", + width, (long long)tv->tv_sec / 60, + (long long)tv->tv_sec % 60, + tv->tv_usec / 10000, suffix); +} + +int +c_times(char **wp) +{ + struct rusage usage; + + (void) getrusage(RUSAGE_SELF, &usage); + p_time(shl_stdout, 0, &usage.ru_utime, 0, NULL, " "); + p_time(shl_stdout, 0, &usage.ru_stime, 0, NULL, "\n"); + + (void) getrusage(RUSAGE_CHILDREN, &usage); + p_time(shl_stdout, 0, &usage.ru_utime, 0, NULL, " "); + p_time(shl_stdout, 0, &usage.ru_stime, 0, NULL, "\n"); + + return 0; +} + +/* + * time pipeline (really a statement, not a built-in command) + */ +int +timex(struct op *t, int f, volatile int *xerrok) +{ +#define TF_NOARGS BIT(0) +#define TF_NOREAL BIT(1) /* don't report real time */ +#define TF_POSIX BIT(2) /* report in posix format */ + int rv = 0; + struct rusage ru0, ru1, cru0, cru1; + struct timeval usrtime, systime, tv0, tv1; + int tf = 0; + extern struct timeval j_usrtime, j_systime; /* computed by j_wait */ + + gettimeofday(&tv0, NULL); + getrusage(RUSAGE_SELF, &ru0); + getrusage(RUSAGE_CHILDREN, &cru0); + if (t->left) { + /* + * Two ways of getting cpu usage of a command: just use t0 + * and t1 (which will get cpu usage from other jobs that + * finish while we are executing t->left), or get the + * cpu usage of t->left. at&t ksh does the former, while + * pdksh tries to do the later (the j_usrtime hack doesn't + * really work as it only counts the last job). + */ + timerclear(&j_usrtime); + timerclear(&j_systime); + rv = execute(t->left, f | XTIME, xerrok); + if (t->left->type == TCOM) + tf |= t->left->str[0]; + gettimeofday(&tv1, NULL); + getrusage(RUSAGE_SELF, &ru1); + getrusage(RUSAGE_CHILDREN, &cru1); + } else + tf = TF_NOARGS; + + if (tf & TF_NOARGS) { /* ksh93 - report shell times (shell+kids) */ + tf |= TF_NOREAL; + timeradd(&ru0.ru_utime, &cru0.ru_utime, &usrtime); + timeradd(&ru0.ru_stime, &cru0.ru_stime, &systime); + } else { + timersub(&ru1.ru_utime, &ru0.ru_utime, &usrtime); + timeradd(&usrtime, &j_usrtime, &usrtime); + timersub(&ru1.ru_stime, &ru0.ru_stime, &systime); + timeradd(&systime, &j_systime, &systime); + } + + if (!(tf & TF_NOREAL)) { + timersub(&tv1, &tv0, &tv1); + if (tf & TF_POSIX) + p_time(shl_out, 1, &tv1, 5, "real ", "\n"); + else + p_time(shl_out, 0, &tv1, 5, NULL, " real "); + } + if (tf & TF_POSIX) + p_time(shl_out, 1, &usrtime, 5, "user ", "\n"); + else + p_time(shl_out, 0, &usrtime, 5, NULL, " user "); + if (tf & TF_POSIX) + p_time(shl_out, 1, &systime, 5, "sys ", "\n"); + else + p_time(shl_out, 0, &systime, 5, NULL, " system\n"); + shf_flush(shl_out); + + return rv; +} + +void +timex_hook(struct op *t, char **volatile *app) +{ + char **wp = *app; + int optc; + int i, j; + Getopt opt; + + ksh_getopt_reset(&opt, 0); + opt.optind = 0; /* start at the start */ + while ((optc = ksh_getopt(wp, &opt, ":p")) != -1) + switch (optc) { + case 'p': + t->str[0] |= TF_POSIX; + break; + case '?': + errorf("time: -%s unknown option", opt.optarg); + case ':': + errorf("time: -%s requires an argument", + opt.optarg); + } + /* Copy command words down over options. */ + if (opt.optind != 0) { + for (i = 0; i < opt.optind; i++) + afree(wp[i], ATEMP); + for (i = 0, j = opt.optind; (wp[i] = wp[j]); i++, j++) + ; + } + if (!wp[0]) + t->str[0] |= TF_NOARGS; + *app = wp; +} + +/* exec with no args - args case is taken care of in comexec() */ +int +c_exec(char **wp) +{ + int i; + + /* make sure redirects stay in place */ + if (genv->savefd != NULL) { + for (i = 0; i < NUFILE; i++) { + if (genv->savefd[i] > 0) + close(genv->savefd[i]); + /* + * For ksh keep anything > 2 private, + * for sh, let them be (POSIX says what + * happens is unspecified and the bourne shell + * keeps them open). + */ + if (!Flag(FSH) && i > 2 && genv->savefd[i]) + fcntl(i, F_SETFD, FD_CLOEXEC); + } + genv->savefd = NULL; + } + return 0; +} + +static int +c_suspend(char **wp) +{ + if (wp[1] != NULL) { + bi_errorf("too many arguments"); + return 1; + } + if (Flag(FLOGIN)) { + /* Can't suspend an orphaned process group. */ + pid_t parent = getppid(); + if (getpgid(parent) == getpgid(0) || + getsid(parent) != getsid(0)) { + bi_errorf("can't suspend a login shell"); + return 1; + } + } + j_suspend(); + return 0; +} + +/* dummy function, special case in comexec() */ +int +c_builtin(char **wp) +{ + return 0; +} + +extern int c_test(char **wp); /* in c_test.c */ +extern int c_ulimit(char **wp); /* in c_ulimit.c */ + +/* A leading = means assignments before command are kept; + * a leading * means a POSIX special builtin; + * a leading + means a POSIX regular builtin + * (* and + should not be combined). + */ +const struct builtin shbuiltins [] = { + {"*=.", c_dot}, + {"*=:", c_label}, + {"[", c_test}, + {"*=break", c_brkcont}, + {"=builtin", c_builtin}, + {"*=continue", c_brkcont}, + {"*=eval", c_eval}, + {"*=exec", c_exec}, + {"*=exit", c_exitreturn}, + {"+false", c_label}, + {"*=return", c_exitreturn}, + {"*=set", c_set}, + {"*=shift", c_shift}, + {"*=times", c_times}, + {"*=trap", c_trap}, + {"+=wait", c_wait}, + {"+read", c_read}, + {"test", c_test}, + {"+true", c_label}, + {"ulimit", c_ulimit}, + {"+umask", c_umask}, + {"*=unset", c_unset}, + {"suspend", c_suspend}, + {NULL, NULL} +}; diff --git a/bin/ksh/c_test.c b/bin/ksh/c_test.c @@ -0,0 +1,564 @@ +/* $OpenBSD: c_test.c,v 1.23 2015/12/14 13:59:42 tb 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 <sys/stat.h> + +#include <string.h> +#include <unistd.h> + +#include "sh.h" +#include "c_test.h" + +/* test(1) accepts the following grammar: + oexpr ::= aexpr | aexpr "-o" oexpr ; + aexpr ::= nexpr | nexpr "-a" aexpr ; + nexpr ::= primary | "!" nexpr ; + primary ::= unary-operator operand + | operand binary-operator operand + | operand + | "(" oexpr ")" + ; + + unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"| + "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"| + "-L"|"-h"|"-S"|"-H"; + + binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| + "-nt"|"-ot"|"-ef"| + "<"|">" # rules used for [[ .. ]] expressions + ; + operand ::= <any thing> +*/ + +#define T_ERR_EXIT 2 /* POSIX says > 1 for errors */ + +struct t_op { + char op_text[4]; + Test_op op_num; +}; +static const struct t_op u_ops [] = { + {"-a", TO_FILAXST }, + {"-b", TO_FILBDEV }, + {"-c", TO_FILCDEV }, + {"-d", TO_FILID }, + {"-e", TO_FILEXST }, + {"-f", TO_FILREG }, + {"-G", TO_FILGID }, + {"-g", TO_FILSETG }, + {"-h", TO_FILSYM }, + {"-H", TO_FILCDF }, + {"-k", TO_FILSTCK }, + {"-L", TO_FILSYM }, + {"-n", TO_STNZE }, + {"-O", TO_FILUID }, + {"-o", TO_OPTION }, + {"-p", TO_FILFIFO }, + {"-r", TO_FILRD }, + {"-s", TO_FILGZ }, + {"-S", TO_FILSOCK }, + {"-t", TO_FILTT }, + {"-u", TO_FILSETU }, + {"-w", TO_FILWR }, + {"-x", TO_FILEX }, + {"-z", TO_STZER }, + {"", TO_NONOP } +}; +static const struct t_op b_ops [] = { + {"=", TO_STEQL }, + {"==", TO_STEQL }, + {"!=", TO_STNEQ }, + {"<", TO_STLT }, + {">", TO_STGT }, + {"-eq", TO_INTEQ }, + {"-ne", TO_INTNE }, + {"-gt", TO_INTGT }, + {"-ge", TO_INTGE }, + {"-lt", TO_INTLT }, + {"-le", TO_INTLE }, + {"-ef", TO_FILEQ }, + {"-nt", TO_FILNT }, + {"-ot", TO_FILOT }, + {"", TO_NONOP } +}; + +static int test_stat(const char *, struct stat *); +static int test_eaccess(const char *, int); +static int test_oexpr(Test_env *, int); +static int test_aexpr(Test_env *, int); +static int test_nexpr(Test_env *, int); +static int test_primary(Test_env *, int); +static int ptest_isa(Test_env *, Test_meta); +static const char *ptest_getopnd(Test_env *, Test_op, int); +static int ptest_eval(Test_env *, Test_op, const char *, + const char *, int); +static void ptest_error(Test_env *, int, const char *); + +int +c_test(char **wp) +{ + int argc; + int res; + Test_env te; + + te.flags = 0; + te.isa = ptest_isa; + te.getopnd = ptest_getopnd; + te.eval = ptest_eval; + te.error = ptest_error; + + for (argc = 0; wp[argc]; argc++) + ; + + if (strcmp(wp[0], "[") == 0) { + if (strcmp(wp[--argc], "]") != 0) { + bi_errorf("missing ]"); + return T_ERR_EXIT; + } + } + + te.pos.wp = wp + 1; + te.wp_end = wp + argc; + + /* + * Handle the special cases from POSIX.2, section 4.62.4. + * Implementation of all the rules isn't necessary since + * our parser does the right thing for the omitted steps. + */ + if (argc <= 5) { + char **owp = wp; + int invert = 0; + Test_op op; + const char *opnd1, *opnd2; + + while (--argc >= 0) { + if ((*te.isa)(&te, TM_END)) + return !0; + if (argc == 3) { + opnd1 = (*te.getopnd)(&te, TO_NONOP, 1); + if ((op = (Test_op) (*te.isa)(&te, TM_BINOP))) { + opnd2 = (*te.getopnd)(&te, op, 1); + res = (*te.eval)(&te, op, opnd1, + opnd2, 1); + if (te.flags & TEF_ERROR) + return T_ERR_EXIT; + if (invert & 1) + res = !res; + return !res; + } + /* back up to opnd1 */ + te.pos.wp--; + } + if (argc == 1) { + opnd1 = (*te.getopnd)(&te, TO_NONOP, 1); + /* Historically, -t by itself test if fd 1 + * is a file descriptor, but POSIX says its + * a string test... + */ + if (!Flag(FPOSIX) && strcmp(opnd1, "-t") == 0) + break; + res = (*te.eval)(&te, TO_STNZE, opnd1, + NULL, 1); + if (invert & 1) + res = !res; + return !res; + } + if ((*te.isa)(&te, TM_NOT)) { + invert++; + } else + break; + } + te.pos.wp = owp + 1; + } + + return test_parse(&te); +} + +/* + * Generic test routines. + */ + +Test_op +test_isop(Test_env *te, Test_meta meta, const char *s) +{ + char sc1; + const struct t_op *otab; + + otab = meta == TM_UNOP ? u_ops : b_ops; + if (*s) { + sc1 = s[1]; + for (; otab->op_text[0]; otab++) + if (sc1 == otab->op_text[1] && + strcmp(s, otab->op_text) == 0 && + ((te->flags & TEF_DBRACKET) || + (otab->op_num != TO_STLT && otab->op_num != TO_STGT))) + return otab->op_num; + } + return TO_NONOP; +} + +int +test_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2, + int do_eval) +{ + int res; + int not; + struct stat b1, b2; + + if (!do_eval) + return 0; + + switch ((int) op) { + /* + * Unary Operators + */ + case TO_STNZE: /* -n */ + return *opnd1 != '\0'; + case TO_STZER: /* -z */ + return *opnd1 == '\0'; + case TO_OPTION: /* -o */ + if ((not = *opnd1 == '!')) + opnd1++; + if ((res = option(opnd1)) < 0) + res = 0; + else { + res = Flag(res); + if (not) + res = !res; + } + return res; + case TO_FILRD: /* -r */ + return test_eaccess(opnd1, R_OK) == 0; + case TO_FILWR: /* -w */ + return test_eaccess(opnd1, W_OK) == 0; + case TO_FILEX: /* -x */ + return test_eaccess(opnd1, X_OK) == 0; + case TO_FILAXST: /* -a */ + return test_stat(opnd1, &b1) == 0; + case TO_FILEXST: /* -e */ + /* at&t ksh does not appear to do the /dev/fd/ thing for + * this (unless the os itself handles it) + */ + return stat(opnd1, &b1) == 0; + case TO_FILREG: /* -r */ + return test_stat(opnd1, &b1) == 0 && S_ISREG(b1.st_mode); + case TO_FILID: /* -d */ + return test_stat(opnd1, &b1) == 0 && S_ISDIR(b1.st_mode); + case TO_FILCDEV: /* -c */ + return test_stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode); + case TO_FILBDEV: /* -b */ + return test_stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode); + case TO_FILFIFO: /* -p */ + return test_stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode); + case TO_FILSYM: /* -h -L */ + return lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode); + case TO_FILSOCK: /* -S */ + return test_stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode); + case TO_FILCDF:/* -H HP context dependent files (directories) */ + return 0; + case TO_FILSETU: /* -u */ + return test_stat(opnd1, &b1) == 0 && + (b1.st_mode & S_ISUID) == S_ISUID; + case TO_FILSETG: /* -g */ + return test_stat(opnd1, &b1) == 0 && + (b1.st_mode & S_ISGID) == S_ISGID; + case TO_FILSTCK: /* -k */ + return test_stat(opnd1, &b1) == 0 && + (b1.st_mode & S_ISVTX) == S_ISVTX; + case TO_FILGZ: /* -s */ + return test_stat(opnd1, &b1) == 0 && b1.st_size > 0L; + case TO_FILTT: /* -t */ + if (opnd1 && !bi_getn(opnd1, &res)) { + te->flags |= TEF_ERROR; + res = 0; + } else { + /* generate error if in FPOSIX mode? */ + res = isatty(opnd1 ? res : 0); + } + return res; + case TO_FILUID: /* -O */ + return test_stat(opnd1, &b1) == 0 && b1.st_uid == ksheuid; + case TO_FILGID: /* -G */ + return test_stat(opnd1, &b1) == 0 && b1.st_gid == getegid(); + /* + * Binary Operators + */ + case TO_STEQL: /* = */ + if (te->flags & TEF_DBRACKET) + return gmatch(opnd1, opnd2, false); + return strcmp(opnd1, opnd2) == 0; + case TO_STNEQ: /* != */ + if (te->flags & TEF_DBRACKET) + return !gmatch(opnd1, opnd2, false); + return strcmp(opnd1, opnd2) != 0; + case TO_STLT: /* < */ + return strcmp(opnd1, opnd2) < 0; + case TO_STGT: /* > */ + return strcmp(opnd1, opnd2) > 0; + case TO_INTEQ: /* -eq */ + case TO_INTNE: /* -ne */ + case TO_INTGE: /* -ge */ + case TO_INTGT: /* -gt */ + case TO_INTLE: /* -le */ + case TO_INTLT: /* -lt */ + { + long v1, v2; + + if (!evaluate(opnd1, &v1, KSH_RETURN_ERROR, false) || + !evaluate(opnd2, &v2, KSH_RETURN_ERROR, false)) { + /* error already printed.. */ + te->flags |= TEF_ERROR; + return 1; + } + switch ((int) op) { + case TO_INTEQ: + return v1 == v2; + case TO_INTNE: + return v1 != v2; + case TO_INTGE: + return v1 >= v2; + case TO_INTGT: + return v1 > v2; + case TO_INTLE: + return v1 <= v2; + case TO_INTLT: + return v1 < v2; + } + } + case TO_FILNT: /* -nt */ + { + int s2; + /* ksh88/ksh93 succeed if file2 can't be stated + * (subtly different from `does not exist'). + */ + return stat(opnd1, &b1) == 0 && + (((s2 = stat(opnd2, &b2)) == 0 && + b1.st_mtime > b2.st_mtime) || s2 < 0); + } + case TO_FILOT: /* -ot */ + { + int s1; + /* ksh88/ksh93 succeed if file1 can't be stated + * (subtly different from `does not exist'). + */ + return stat(opnd2, &b2) == 0 && + (((s1 = stat(opnd1, &b1)) == 0 && + b1.st_mtime < b2.st_mtime) || s1 < 0); + } + case TO_FILEQ: /* -ef */ + return stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 && + b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino; + } + (*te->error)(te, 0, "internal error: unknown op"); + return 1; +} + +/* Nasty kludge to handle Korn's bizarre /dev/fd hack */ +static int +test_stat(const char *path, struct stat *statb) +{ + return stat(path, statb); +} + +/* Routine to handle Korn's /dev/fd hack, and to deal with X_OK on + * non-directories when running as root. + */ +static int +test_eaccess(const char *path, int mode) +{ + int res; + + res = access(path, mode); + /* + * On most (all?) unixes, access() says everything is executable for + * root - avoid this on files by using stat(). + */ + if (res == 0 && ksheuid == 0 && (mode & X_OK)) { + struct stat statb; + + if (stat(path, &statb) < 0) + res = -1; + else if (S_ISDIR(statb.st_mode)) + res = 0; + else + res = (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) ? + 0 : -1; + } + + return res; +} + +int +test_parse(Test_env *te) +{ + int res; + + res = test_oexpr(te, 1); + + if (!(te->flags & TEF_ERROR) && !(*te->isa)(te, TM_END)) + (*te->error)(te, 0, "unexpected operator/operand"); + + return (te->flags & TEF_ERROR) ? T_ERR_EXIT : !res; +} + +static int +test_oexpr(Test_env *te, int do_eval) +{ + int res; + + res = test_aexpr(te, do_eval); + if (res) + do_eval = 0; + if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_OR)) + return test_oexpr(te, do_eval) || res; + return res; +} + +static int +test_aexpr(Test_env *te, int do_eval) +{ + int res; + + res = test_nexpr(te, do_eval); + if (!res) + do_eval = 0; + if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_AND)) + return test_aexpr(te, do_eval) && res; + return res; +} + +static int +test_nexpr(Test_env *te, int do_eval) +{ + if (!(te->flags & TEF_ERROR) && (*te->isa)(te, TM_NOT)) + return !test_nexpr(te, do_eval); + return test_primary(te, do_eval); +} + +static int +test_primary(Test_env *te, int do_eval) +{ + const char *opnd1, *opnd2; + int res; + Test_op op; + + if (te->flags & TEF_ERROR) + return 0; + if ((*te->isa)(te, TM_OPAREN)) { + res = test_oexpr(te, do_eval); + if (te->flags & TEF_ERROR) + return 0; + if (!(*te->isa)(te, TM_CPAREN)) { + (*te->error)(te, 0, "missing closing paren"); + return 0; + } + return res; + } + /* + * Binary should have precedence over unary in this case + * so that something like test \( -f = -f \) is accepted + */ + if ((te->flags & TEF_DBRACKET) || (&te->pos.wp[1] < te->wp_end && + !test_isop(te, TM_BINOP, te->pos.wp[1]))) { + if ((op = (Test_op) (*te->isa)(te, TM_UNOP))) { + /* unary expression */ + opnd1 = (*te->getopnd)(te, op, do_eval); + if (!opnd1) { + (*te->error)(te, -1, "missing argument"); + return 0; + } + + return (*te->eval)(te, op, opnd1, NULL, + 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, NULL, do_eval); +} + +/* + * Plain test (test and [ .. ]) specific routines. + */ + +/* Test if the current token is a whatever. Accepts the current token if + * it is. Returns 0 if it is not, non-zero if it is (in the case of + * TM_UNOP and TM_BINOP, the returned value is a Test_op). + */ +static int +ptest_isa(Test_env *te, Test_meta meta) +{ + /* Order important - indexed by Test_meta values */ + static const char *const tokens[] = { + "-o", "-a", "!", "(", ")" + }; + int ret; + + if (te->pos.wp >= te->wp_end) + return meta == TM_END; + + if (meta == TM_UNOP || meta == TM_BINOP) + ret = (int) test_isop(te, meta, *te->pos.wp); + else if (meta == TM_END) + ret = 0; + else + ret = strcmp(*te->pos.wp, tokens[(int) meta]) == 0; + + /* Accept the token? */ + if (ret) + te->pos.wp++; + + return ret; +} + +static const char * +ptest_getopnd(Test_env *te, Test_op op, int do_eval) +{ + if (te->pos.wp >= te->wp_end) + return op == TO_FILTT ? "1" : NULL; + return *te->pos.wp++; +} + +static int +ptest_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2, + int do_eval) +{ + return test_eval(te, op, opnd1, opnd2, do_eval); +} + +static void +ptest_error(Test_env *te, int offset, const char *msg) +{ + const char *op = te->pos.wp + offset >= te->wp_end ? + NULL : 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/ksh/c_test.h b/bin/ksh/c_test.h @@ -0,0 +1,53 @@ +/* $OpenBSD: c_test.h,v 1.4 2004/12/20 11:34:26 otto Exp $ */ + +/* Various types of operations. Keeping things grouped nicely + * (unary,binary) makes switch() statements more efficient. + */ +enum Test_op { + TO_NONOP = 0, /* non-operator */ + /* unary operators */ + TO_STNZE, TO_STZER, TO_OPTION, + TO_FILAXST, + TO_FILEXST, + TO_FILREG, TO_FILBDEV, TO_FILCDEV, TO_FILSYM, TO_FILFIFO, TO_FILSOCK, + TO_FILCDF, TO_FILID, TO_FILGID, TO_FILSETG, TO_FILSTCK, TO_FILUID, + TO_FILRD, TO_FILGZ, TO_FILTT, TO_FILSETU, TO_FILWR, TO_FILEX, + /* binary operators */ + TO_STEQL, TO_STNEQ, TO_STLT, TO_STGT, TO_INTEQ, TO_INTNE, TO_INTGT, + TO_INTGE, TO_INTLT, TO_INTLE, TO_FILEQ, TO_FILNT, TO_FILOT +}; +typedef enum Test_op Test_op; + +/* Used by Test_env.isa() (order important - used to index *_tokens[] arrays) */ +enum Test_meta { + TM_OR, /* -o or || */ + TM_AND, /* -a or && */ + TM_NOT, /* ! */ + TM_OPAREN, /* ( */ + TM_CPAREN, /* ) */ + TM_UNOP, /* unary operator */ + TM_BINOP, /* binary operator */ + TM_END /* end of input */ +}; +typedef enum Test_meta Test_meta; + +#define TEF_ERROR BIT(0) /* set if we've hit an error */ +#define TEF_DBRACKET BIT(1) /* set if [[ .. ]] test */ + +typedef struct test_env Test_env; +struct test_env { + int flags; /* TEF_* */ + union { + char **wp; /* used by ptest_* */ + XPtrV *av; /* used by dbtestp_* */ + } pos; + char **wp_end; /* used by ptest_* */ + int (*isa)(Test_env *, Test_meta); + const char *(*getopnd) (Test_env *, Test_op, int); + int (*eval)(Test_env *, Test_op, const char *, const char *, int); + void (*error)(Test_env *, int, const char *); +}; + +Test_op test_isop(Test_env *, Test_meta, const char *); +int test_eval(Test_env *, Test_op, const char *, const char *, int); +int test_parse(Test_env *); diff --git a/bin/ksh/c_ulimit.c b/bin/ksh/c_ulimit.c @@ -0,0 +1,206 @@ +/* $OpenBSD: c_ulimit.c,v 1.24 2015/12/14 13:59:42 tb Exp $ */ + +/* + ulimit -- handle "ulimit" builtin + + Reworked to use getrusage() and ulimit() at once (as needed on + some schizophrenic systems, eg, HP-UX 9.01), made argument parsing + conform to at&t ksh, added autoconf support. Michael Rendell, May, '94 + + Eric Gisin, September 1988 + Adapted to PD KornShell. Removed AT&T code. + + last edit: 06-Jun-1987 D A Gwyn + + This started out as the BRL UNIX System V system call emulation + for 4.nBSD, and was later extended by Doug Kingston to handle + the extended 4.nBSD resource limits. It now includes the code + that was originally under case SYSULIMIT in source file "xec.c". +*/ + +#include <sys/resource.h> + +#include <ctype.h> +#include <errno.h> +#include <string.h> + +#include "sh.h" + +#define SOFT 0x1 +#define HARD 0x2 + +struct limits { + const char *name; + int resource; /* resource to get/set */ + int factor; /* multiply by to get rlim_{cur,max} values */ + char option; /* option character (-d, -f, ...) */ +}; + +static void print_ulimit(const struct limits *, int); +static int set_ulimit(const struct limits *, const char *, int); + +int +c_ulimit(char **wp) +{ + static const struct limits limits[] = { + /* Do not use options -H, -S or -a or change the order. */ + { "time(cpu-seconds)", RLIMIT_CPU, 1, 't' }, + { "file(blocks)", RLIMIT_FSIZE, 512, 'f' }, + { "coredump(blocks)", RLIMIT_CORE, 512, 'c' }, + { "data(kbytes)", RLIMIT_DATA, 1024, 'd' }, + { "stack(kbytes)", RLIMIT_STACK, 1024, 's' }, + { "lockedmem(kbytes)", RLIMIT_MEMLOCK, 1024, 'l' }, + { "memory(kbytes)", RLIMIT_RSS, 1024, 'm' }, + { "nofiles(descriptors)", RLIMIT_NOFILE, 1, 'n' }, + { "processes", RLIMIT_NPROC, 1, 'p' }, +#ifdef RLIMIT_VMEM + { "vmemory(kbytes)", RLIMIT_VMEM, 1024, 'v' }, +#endif /* RLIMIT_VMEM */ + { NULL } + }; + static char options[4 + NELEM(limits) * 2]; + int how = SOFT | HARD; + const struct limits *l; + int optc, all = 0; + + if (!options[0]) { + /* build options string on first call - yuck */ + char *p = options; + + *p++ = 'H'; *p++ = 'S'; *p++ = 'a'; + for (l = limits; l->name; l++) { + *p++ = l->option; + *p++ = '#'; + } + *p = '\0'; + } + /* First check for -a, -H and -S. */ + while ((optc = ksh_getopt(wp, &builtin_opt, options)) != -1) + switch (optc) { + case 'H': + how = HARD; + break; + case 'S': + how = SOFT; + break; + case 'a': + all = 1; + break; + case '?': + return 1; + default: + break; + } + + if (wp[builtin_opt.optind] != NULL) { + bi_errorf("usage: ulimit [-acdfHlmnpSst] [value]"); + return 1; + } + + /* Then parse and act on the actual limits, one at a time */ + ksh_getopt_reset(&builtin_opt, GF_ERROR); + while ((optc = ksh_getopt(wp, &builtin_opt, options)) != -1) + switch (optc) { + case 'a': + case 'H': + case 'S': + break; + case '?': + return 1; + default: + for (l = limits; l->name && l->option != optc; l++) + ; + if (!l->name) { + internal_errorf(0, "ulimit: %c", optc); + return 1; + } + if (builtin_opt.optarg) { + if (set_ulimit(l, builtin_opt.optarg, how)) + return 1; + } else + print_ulimit(l, how); + break; + } + + wp += builtin_opt.optind; + + if (all) { + for (l = limits; l->name; l++) { + shprintf("%-20s ", l->name); + print_ulimit(l, how); + } + } else if (builtin_opt.optind == 1) { + /* No limit specified, use file size */ + l = &limits[1]; + if (wp[0] != NULL) { + if (set_ulimit(l, wp[0], how)) + return 1; + wp++; + } else { + print_ulimit(l, how); + } + } + + return 0; +} + +static int +set_ulimit(const struct limits *l, const char *v, int how) +{ + rlim_t val = 0; + struct rlimit limit; + + if (strcmp(v, "unlimited") == 0) + val = RLIM_INFINITY; + else { + long rval; + + if (!evaluate(v, &rval, KSH_RETURN_ERROR, false)) + return 1; + /* + * Avoid problems caused by typos that evaluate misses due + * to evaluating unset parameters to 0... + * If this causes problems, will have to add parameter to + * evaluate() to control if unset params are 0 or an error. + */ + if (!rval && !digit(v[0])) { + bi_errorf("invalid limit: %s", v); + return 1; + } + val = (rlim_t)rval * l->factor; + } + + getrlimit(l->resource, &limit); + if (how & SOFT) + limit.rlim_cur = val; + if (how & HARD) + limit.rlim_max = val; + if (setrlimit(l->resource, &limit) < 0) { + if (errno == EPERM) + bi_errorf("-%c exceeds allowable limit", l->option); + else + bi_errorf("bad -%c limit: %s", l->option, + strerror(errno)); + return 1; + } + return 0; +} + +static void +print_ulimit(const struct limits *l, int how) +{ + rlim_t val = 0; + struct rlimit limit; + + getrlimit(l->resource, &limit); + if (how & SOFT) + val = limit.rlim_cur; + else if (how & HARD) + val = limit.rlim_max; + if (val == RLIM_INFINITY) + shprintf("unlimited\n"); + else { + val /= l->factor; + shprintf("%ld\n", (long) val); + } +} diff --git a/bin/ksh/edit.c b/bin/ksh/edit.c @@ -0,0 +1,833 @@ +/* $OpenBSD: edit.c,v 1.53 2016/03/17 23:33:23 mmcc Exp $ */ + +/* + * Command line editing - common code + * + */ + +#include "config.h" +#ifdef EDIT + +#include <sys/ioctl.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <errno.h> +#include <libgen.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "sh.h" +#include "edit.h" +#include "tty.h" + +X_chars edchars; + +static void x_sigwinch(int); +volatile sig_atomic_t got_sigwinch; +static void check_sigwinch(void); + +static int x_file_glob(int, const char *, int, char ***); +static int x_command_glob(int, const char *, int, char ***); +static int x_locate_word(const char *, int, int, int *, int *); + + +/* Called from main */ +void +x_init(void) +{ + /* set to -2 to force initial binding */ + edchars.erase = edchars.kill = edchars.intr = edchars.quit = + edchars.eof = -2; + /* default value for deficient systems */ + edchars.werase = 027; /* ^W */ + + if (setsig(&sigtraps[SIGWINCH], x_sigwinch, SS_RESTORE_ORIG|SS_SHTRAP)) + sigtraps[SIGWINCH].flags |= TF_SHELL_USES; + got_sigwinch = 1; /* force initial check */ + check_sigwinch(); + +#ifdef EMACS + x_init_emacs(); +#endif /* EMACS */ +} + +static void +x_sigwinch(int sig) +{ + got_sigwinch = 1; +} + +static void +check_sigwinch(void) +{ + if (got_sigwinch) { + struct winsize ws; + + got_sigwinch = 0; + if (procpid == kshpid && ioctl(tty_fd, TIOCGWINSZ, &ws) >= 0) { + struct tbl *vp; + + /* Do NOT export COLUMNS/LINES. Many applications + * check COLUMNS/LINES before checking ws.ws_col/row, + * so if the app is started with C/L in the environ + * and the window is then resized, the app won't + * see the change cause the environ doesn't change. + */ + if (ws.ws_col) { + x_cols = ws.ws_col < MIN_COLS ? MIN_COLS : + ws.ws_col; + + if ((vp = typeset("COLUMNS", 0, 0, 0, 0))) + setint(vp, (long) ws.ws_col); + } + if (ws.ws_row && (vp = typeset("LINES", 0, 0, 0, 0))) + setint(vp, (long) ws.ws_row); + } + } +} + +/* + * read an edited command line + */ +int +x_read(char *buf, size_t len) +{ + int i; + + x_mode(true); +#ifdef EMACS + if (Flag(FEMACS) || Flag(FGMACS)) + i = x_emacs(buf, len); + else +#endif +#ifdef VI + if (Flag(FVI)) + i = x_vi(buf, len); + else +#endif + i = -1; /* internal error */ + x_mode(false); + check_sigwinch(); + return i; +} + +/* tty I/O */ + +int +x_getc(void) +{ + char c; + int n; + + while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR) + if (trap) { + x_mode(false); + runtraps(0); + x_mode(true); + } + if (n != 1) + return -1; + return (int) (unsigned char) c; +} + +void +x_flush(void) +{ + shf_flush(shl_out); +} + +void +x_putc(int c) +{ + shf_putc(c, shl_out); +} + +void +x_puts(const char *s) +{ + while (*s != 0) + shf_putc(*s++, shl_out); +} + +bool +x_mode(bool onoff) +{ + static bool x_cur_mode; + bool prev; + + if (x_cur_mode == onoff) + return x_cur_mode; + prev = x_cur_mode; + x_cur_mode = onoff; + + if (onoff) { + struct termios cb; + X_chars oldchars; + + oldchars = edchars; + cb = tty_state; + + edchars.erase = cb.c_cc[VERASE]; + edchars.kill = cb.c_cc[VKILL]; + edchars.intr = cb.c_cc[VINTR]; + edchars.quit = cb.c_cc[VQUIT]; + edchars.eof = cb.c_cc[VEOF]; + edchars.werase = cb.c_cc[VWERASE]; + cb.c_iflag &= ~(INLCR|ICRNL); + cb.c_lflag &= ~(ISIG|ICANON|ECHO); + /* osf/1 processes lnext when ~icanon */ + cb.c_cc[VLNEXT] = _POSIX_VDISABLE; + /* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */ + cb.c_cc[VDISCARD] = _POSIX_VDISABLE; + cb.c_cc[VTIME] = 0; + cb.c_cc[VMIN] = 1; + + tcsetattr(tty_fd, TCSADRAIN, &cb); + + /* Convert unset values to internal `unset' value */ + if (edchars.erase == _POSIX_VDISABLE) + edchars.erase = -1; + if (edchars.kill == _POSIX_VDISABLE) + edchars.kill = -1; + if (edchars.intr == _POSIX_VDISABLE) + edchars.intr = -1; + if (edchars.quit == _POSIX_VDISABLE) + edchars.quit = -1; + if (edchars.eof == _POSIX_VDISABLE) + edchars.eof = -1; + if (edchars.werase == _POSIX_VDISABLE) + edchars.werase = -1; + if (memcmp(&edchars, &oldchars, sizeof(edchars)) != 0) { +#ifdef EMACS + x_emacs_keys(&edchars); +#endif + } + } else { + tcsetattr(tty_fd, TCSADRAIN, &tty_state); + } + + return prev; +} + +void +set_editmode(const char *ed) +{ + static const enum sh_flag edit_flags[] = { +#ifdef EMACS + FEMACS, FGMACS, +#endif +#ifdef VI + FVI, +#endif + }; + char *rcp; + int i; + + if ((rcp = strrchr(ed, '/'))) + ed = ++rcp; + for (i = 0; i < NELEM(edit_flags); i++) + if (strstr(ed, options[(int) edit_flags[i]].name)) { + change_flag(edit_flags[i], OF_SPECIAL, 1); + return; + } +} + +/* ------------------------------------------------------------------------- */ +/* Misc common code for vi/emacs */ + +/* Handle the commenting/uncommenting of a line. + * Returns: + * 1 if a carriage return is indicated (comment added) + * 0 if no return (comment removed) + * -1 if there is an error (not enough room for comment chars) + * If successful, *lenp contains the new length. Note: cursor should be + * moved to the start of the line after (un)commenting. + */ +int +x_do_comment(char *buf, int bsize, int *lenp) +{ + int i, j; + int len = *lenp; + + if (len == 0) + return 1; /* somewhat arbitrary - it's what at&t ksh does */ + + /* Already commented? */ + if (buf[0] == '#') { + int saw_nl = 0; + + for (j = 0, i = 1; i < len; i++) { + if (!saw_nl || buf[i] != '#') + buf[j++] = buf[i]; + saw_nl = buf[i] == '\n'; + } + *lenp = j; + return 0; + } else { + int n = 1; + + /* See if there's room for the #'s - 1 per \n */ + for (i = 0; i < len; i++) + if (buf[i] == '\n') + n++; + if (len + n >= bsize) + return -1; + /* Now add them... */ + for (i = len, j = len + n; --i >= 0; ) { + if (buf[i] == '\n') + buf[--j] = '#'; + buf[--j] = buf[i]; + } + buf[0] = '#'; + *lenp += n; + return 1; + } +} + +/* ------------------------------------------------------------------------- */ +/* Common file/command completion code for vi/emacs */ + + +static char *add_glob(const char *str, int slen); +static void glob_table(const char *pat, XPtrV *wp, struct table *tp); +static void glob_path(int flags, const char *pat, XPtrV *wp, + const char *path); + +void +x_print_expansions(int nwords, char *const *words, int is_command) +{ + int use_copy = 0; + int prefix_len; + XPtrV l; + + /* Check if all matches are in the same directory (in this + * case, we want to omit the directory name) + */ + if (!is_command && + (prefix_len = x_longest_prefix(nwords, words)) > 0) { + int i; + + /* Special case for 1 match (prefix is whole word) */ + if (nwords == 1) + prefix_len = x_basename(words[0], NULL); + /* Any (non-trailing) slashes in non-common word suffixes? */ + for (i = 0; i < nwords; i++) + if (x_basename(words[i] + prefix_len, NULL) > + prefix_len) + break; + /* All in same directory? */ + if (i == nwords) { + while (prefix_len > 0 && words[0][prefix_len - 1] != '/') + prefix_len--; + use_copy = 1; + XPinit(l, nwords + 1); + for (i = 0; i < nwords; i++) + XPput(l, words[i] + prefix_len); + XPput(l, NULL); + } + } + + /* + * Enumerate expansions + */ + x_putc('\r'); + x_putc('\n'); + pr_list(use_copy ? (char **) XPptrv(l) : words); + + if (use_copy) + XPfree(l); /* not x_free_words() */ +} + +/* + * Do file globbing: + * - appends * to (copy of) str if no globbing chars found + * - does expansion, checks for no match, etc. + * - sets *wordsp to array of matching strings + * - returns number of matching strings + */ +static int +x_file_glob(int flags, const char *str, int slen, char ***wordsp) +{ + char *toglob; + char **words; + int nwords; + XPtrV w; + struct source *s, *sold; + + if (slen < 0) + return 0; + + toglob = add_glob(str, slen); + + /* + * Convert "foo*" (toglob) to an array of strings (words) + */ + sold = source; + s = pushs(SWSTR, ATEMP); + s->start = s->str = toglob; + source = s; + if (yylex(ONEWORD|UNESCAPE) != LWORD) { + source = sold; + internal_errorf(0, "fileglob: substitute error"); + return 0; + } + source = sold; + XPinit(w, 32); + expand(yylval.cp, &w, DOGLOB|DOTILDE|DOMARKDIRS); + XPput(w, NULL); + words = (char **) XPclose(w); + + for (nwords = 0; words[nwords]; nwords++) + ; + if (nwords == 1) { + struct stat statb; + + /* Check if file exists, also, check for empty + * result - happens if we tried to glob something + * which evaluated to an empty string (e.g., + * "$FOO" when there is no FOO, etc). + */ + if ((lstat(words[0], &statb) < 0) || + words[0][0] == '\0') { + x_free_words(nwords, words); + words = NULL; + nwords = 0; + } + } + afree(toglob, ATEMP); + + if (nwords) { + *wordsp = words; + } else if (words) { + x_free_words(nwords, words); + *wordsp = NULL; + } + + return nwords; +} + +/* Data structure used in x_command_glob() */ +struct path_order_info { + char *word; + int base; + int path_order; +}; + +static int path_order_cmp(const void *aa, const void *bb); + +/* Compare routine used in x_command_glob() */ +static int +path_order_cmp(const void *aa, const void *bb) +{ + const struct path_order_info *a = (const struct path_order_info *) aa; + const struct path_order_info *b = (const struct path_order_info *) bb; + int t; + + t = strcmp(a->word + a->base, b->word + b->base); + return t ? t : a->path_order - b->path_order; +} + +static int +x_command_glob(int flags, const char *str, int slen, char ***wordsp) +{ + char *toglob; + char *pat; + char *fpath; + int nwords; + XPtrV w; + struct block *l; + + if (slen < 0) + return 0; + + toglob = add_glob(str, slen); + + /* Convert "foo*" (toglob) to a pattern for future use */ + pat = evalstr(toglob, DOPAT|DOTILDE); + afree(toglob, ATEMP); + + XPinit(w, 32); + + glob_table(pat, &w, &keywords); + glob_table(pat, &w, &aliases); + glob_table(pat, &w, &builtins); + for (l = genv->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 = NULL; + 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 = NULL; + char **words = (char **) XPptrv(w); + int path_order = 0; + int i; + + info = areallocarray(NULL, nwords, + sizeof(struct path_order_info), ATEMP); + + for (i = 0; i < nwords; i++) { + info[i].word = words[i]; + info[i].base = x_basename(words[i], NULL); + if (!last_info || info[i].base != last_info->base || + strncmp(words[i], last_info->word, info[i].base) != 0) { + last_info = &info[i]; + path_order++; + } + info[i].path_order = path_order; + } + qsort(info, nwords, sizeof(struct path_order_info), + path_order_cmp); + for (i = 0; i < nwords; i++) + words[i] = info[i].word; + afree(info, ATEMP); + } else { + /* Sort and remove duplicate entries */ + char **words = (char **) XPptrv(w); + int i, j; + + qsortp(XPptrv(w), (size_t) nwords, xstrcmp); + + for (i = j = 0; i < nwords - 1; i++) { + if (strcmp(words[i], words[i + 1])) + words[j++] = words[i]; + else + afree(words[i], ATEMP); + } + words[j++] = words[i]; + nwords = j; + w.cur = (void **) &words[j]; + } + + XPput(w, NULL); + *wordsp = (char **) XPclose(w); + + return nwords; +} + +#define IS_WORDC(c) !( ctype(c, C_LEX1) || (c) == '\'' || (c) == '"' || \ + (c) == '`' || (c) == '=' || (c) == ':' ) + +static int +x_locate_word(const char *buf, int buflen, int pos, int *startp, + int *is_commandp) +{ + int p; + int start, end; + + /* Bad call? Probably should report error */ + if (pos < 0 || pos > buflen) { + *startp = pos; + *is_commandp = 0; + return 0; + } + /* The case where pos == buflen happens to take care of itself... */ + + start = pos; + /* Keep going backwards to start of word (has effect of allowing + * one blank after the end of a word) + */ + for (; (start > 0 && IS_WORDC(buf[start - 1])) || + (start > 1 && buf[start-2] == '\\'); start--) + ; + /* Go forwards to end of word */ + for (end = start; end < buflen && IS_WORDC(buf[end]); end++) { + if (buf[end] == '\\' && (end+1) < buflen) + end++; + } + + if (is_commandp) { + int iscmd; + + /* Figure out if this is a command */ + for (p = start - 1; p >= 0 && isspace((unsigned char)buf[p]); + p--) + ; + iscmd = p < 0 || strchr(";|&()`", buf[p]); + if (iscmd) { + /* If command has a /, path, etc. is not searched; + * only current directory is searched, which is just + * like file globbing. + */ + for (p = start; p < end; p++) + if (buf[p] == '/') + break; + iscmd = p == end; + } + *is_commandp = iscmd; + } + + *startp = start; + + return end - start; +} + +int +x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp, + int *endp, char ***wordsp, int *is_commandp) +{ + int len; + int nwords; + char **words; + int is_command; + + len = x_locate_word(buf, buflen, pos, startp, &is_command); + if (!(flags & XCF_COMMAND)) + is_command = 0; + /* Don't do command globing on zero length strings - it takes too + * long and isn't very useful. File globs are more likely to be + * useful, so allow these. + */ + if (len == 0 && is_command) + return 0; + + nwords = (is_command ? x_command_glob : x_file_glob)(flags, + buf + *startp, len, &words); + if (nwords == 0) { + *wordsp = NULL; + return 0; + } + + if (is_commandp) + *is_commandp = is_command; + *wordsp = words; + *endp = *startp + len; + + return nwords; +} + +/* Given a string, copy it and possibly add a '*' to the end. The + * new string is returned. + */ +static char * +add_glob(const char *str, int slen) +{ + char *toglob; + char *s; + bool saw_slash = false; + + if (slen < 0) + return NULL; + + toglob = str_nsave(str, slen + 1, ATEMP); /* + 1 for "*" */ + toglob[slen] = '\0'; + + /* + * If the pathname contains a wildcard (an unquoted '*', + * '?', or '[') or parameter expansion ('$'), or a ~username + * with no trailing slash, then it is globbed based on that + * value (i.e., without the appended '*'). + */ + for (s = toglob; *s; s++) { + if (*s == '\\' && s[1]) + s++; + else if (*s == '*' || *s == '[' || *s == '?' || *s == '$' || + (s[1] == '(' /*)*/ && strchr("+@!", *s))) + break; + else if (*s == '/') + saw_slash = true; + } + if (!*s && (*toglob != '~' || saw_slash)) { + toglob[slen] = '*'; + toglob[slen + 1] = '\0'; + } + + return toglob; +} + +/* + * Find longest common prefix + */ +int +x_longest_prefix(int nwords, char *const *words) +{ + int i, j; + int prefix_len; + char *p; + + if (nwords <= 0) + return 0; + + prefix_len = strlen(words[0]); + for (i = 1; i < nwords; i++) + for (j = 0, p = words[i]; j < prefix_len; j++) + if (p[j] != words[0][j]) { + prefix_len = j; + break; + } + return prefix_len; +} + +void +x_free_words(int nwords, char **words) +{ + int i; + + for (i = 0; i < nwords; i++) + afree(words[i], ATEMP); + afree(words, ATEMP); +} + +/* Return the offset of the basename of string s (which ends at se - need not + * be null terminated). Trailing slashes are ignored. If s is just a slash, + * then the offset is 0 (actually, length - 1). + * s Return + * /etc 1 + * /etc/ 1 + * /etc// 1 + * /etc/fo 5 + * foo 0 + * /// 2 + * 0 + */ +int +x_basename(const char *s, const char *se) +{ + const char *p; + + if (se == NULL) + se = s + strlen(s); + if (s == se) + return 0; + + /* Skip trailing slashes */ + for (p = se - 1; p > s && *p == '/'; p--) + ; + for (; p > s && *p != '/'; p--) + ; + if (*p == '/' && p + 1 < se) + p++; + + return p - s; +} + +/* + * Apply pattern matching to a table: all table entries that match a pattern + * are added to wp. + */ +static void +glob_table(const char *pat, XPtrV *wp, struct table *tp) +{ + struct tstate ts; + struct tbl *te; + + for (ktwalk(&ts, tp); (te = ktnext(&ts)); ) { + if (gmatch(te->name, pat, false)) + XPput(*wp, str_save(te->name, ATEMP)); + } +} + +static void +glob_path(int flags, const char *pat, XPtrV *wp, const char *path) +{ + const char *sp, *p; + char *xp; + int staterr; + int pathlen; + int patlen; + int oldsize, newsize, i, j; + char **words; + XString xs; + + patlen = strlen(pat) + 1; + sp = path; + Xinit(xs, xp, patlen + 128, ATEMP); + while (sp) { + xp = Xstring(xs, xp); + if (!(p = strchr(sp, ':'))) + p = sp + strlen(sp); + pathlen = p - sp; + if (pathlen) { + /* Copy sp into xp, stuffing any MAGIC characters + * on the way + */ + const char *s = sp; + + XcheckN(xs, xp, pathlen * 2); + while (s < p) { + if (ISMAGIC(*s)) + *xp++ = MAGIC; + *xp++ = *s++; + } + *xp++ = '/'; + pathlen++; + } + sp = p; + XcheckN(xs, xp, patlen); + memcpy(xp, pat, patlen); + + oldsize = XPsize(*wp); + glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */ + newsize = XPsize(*wp); + + /* Check that each match is executable... */ + words = (char **) XPptrv(*wp); + for (i = j = oldsize; i < newsize; i++) { + staterr = 0; + if ((search_access(words[i], X_OK, &staterr) >= 0) || + (staterr == EISDIR)) { + words[j] = words[i]; + if (!(flags & XCF_FULLPATH)) + memmove(words[j], words[j] + pathlen, + strlen(words[j] + pathlen) + 1); + j++; + } else + afree(words[i], ATEMP); + } + wp->cur = (void **) &words[j]; + + if (!*sp++) + break; + } + Xfree(xs, xp); +} + +/* + * if argument string contains any special characters, they will + * be escaped and the result will be put into edit buffer by + * keybinding-specific function + */ +int +x_escape(const char *s, size_t len, int (*putbuf_func) (const char *, size_t)) +{ + size_t add, wlen; + const char *ifs = str_val(local("IFS", 0)); + int rval = 0; + + for (add = 0, wlen = len; wlen - add > 0; add++) { + if (strchr("!\"#$&'()*:;<=>?[\\]`{|}", s[add]) || + strchr(ifs, s[add])) { + if (putbuf_func(s, add) != 0) { + rval = -1; + break; + } + + putbuf_func("\\", 1); + putbuf_func(&s[add], 1); + + add++; + wlen -= add; + s += add; + add = -1; /* after the increment it will go to 0 */ + } + } + if (wlen > 0 && rval == 0) + rval = putbuf_func(s, wlen); + + return (rval); +} +#endif /* EDIT */ diff --git a/bin/ksh/edit.h b/bin/ksh/edit.h @@ -0,0 +1,56 @@ +/* $OpenBSD: edit.h,v 1.11 2016/01/26 17:39:31 mmcc 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 $ + * + */ + +#define BEL 0x07 + +/* tty driver characters we are interested in */ +typedef struct { + int erase; + int kill; + int werase; + int intr; + int quit; + int eof; +} X_chars; + +extern X_chars edchars; + +/* x_cf_glob() flags */ +#define XCF_COMMAND BIT(0) /* Do command completion */ +#define XCF_FILE BIT(1) /* Do file completion */ +#define XCF_FULLPATH BIT(2) /* command completion: store full path */ +#define XCF_COMMAND_FILE (XCF_COMMAND|XCF_FILE) + +/* edit.c */ +int x_getc(void); +void x_flush(void); +void x_putc(int); +void x_puts(const char *); +bool x_mode(bool); +int promptlen(const char *, const char **); +int x_do_comment(char *, int, int *); +void x_print_expansions(int, char *const *, int); +int x_cf_glob(int, const char *, int, int, int *, int *, char ***, int *); +int x_longest_prefix(int , char *const *); +int x_basename(const char *, const char *); +void x_free_words(int, char **); +int x_escape(const char *, size_t, int (*)(const char *, size_t)); +/* emacs.c */ +int x_emacs(char *, size_t); +void x_init_emacs(void); +void x_emacs_keys(X_chars *); +/* vi.c */ +int x_vi(char *, size_t); diff --git a/bin/ksh/emacs.c b/bin/ksh/emacs.c @@ -0,0 +1,2176 @@ +/* $OpenBSD: emacs.c,v 1.65 2016/01/26 17:39:31 mmcc Exp $ */ + +/* + * Emacs-like command line editing and history + * + * created by Ron Natalie at BRL + * modified by Doug Kingston, Doug Gwyn, and Lou Salkind + * adapted to PD ksh by Eric Gisin + * + * partial rewrite by Marco Peereboom <marco@openbsd.org> + * under the same license + */ + +#include "config.h" +#ifdef EMACS + +#include <sys/queue.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "sh.h" +#include "edit.h" + +static Area aedit; +#define AEDIT &aedit /* area for kill ring and macro defns */ + +#define CTRL(x) ((x) == '?' ? 0x7F : (x) & 0x1F) /* ASCII */ +#define UNCTRL(x) ((x) == 0x7F ? '?' : (x) | 0x40) /* ASCII */ + +/* values returned by keyboard functions */ +#define KSTD 0 +#define KEOL 1 /* ^M, ^J */ +#define KINTR 2 /* ^G, ^C */ + +struct x_ftab { + int (*xf_func)(int c); + const char *xf_name; + short xf_flags; +}; + +#define XF_ARG 1 /* command takes number prefix */ +#define XF_NOBIND 2 /* not allowed to bind to function */ +#define XF_PREFIX 4 /* function sets prefix */ + +/* Separator for completion */ +#define is_cfs(c) (c == ' ' || c == '\t' || c == '"' || c == '\'') + +/* Separator for motion */ +#define is_mfs(c) (!(isalnum((unsigned char)c) || \ + c == '_' || c == '$' || c & 0x80)) + +/* Arguments for do_complete() + * 0 = enumerate M-= complete as much as possible and then list + * 1 = complete M-Esc + * 2 = list M-? + */ +typedef enum { + CT_LIST, /* list the possible completions */ + CT_COMPLETE, /* complete to longest prefix */ + CT_COMPLIST /* complete and then list (if non-exact) */ +} Comp_type; + +/* keybindings */ +struct kb_entry { + TAILQ_ENTRY(kb_entry) entry; + unsigned char *seq; + int len; + struct x_ftab *ftab; + void *args; +}; +TAILQ_HEAD(kb_list, kb_entry); +struct kb_list kblist = TAILQ_HEAD_INITIALIZER(kblist); + +/* { from 4.9 edit.h */ +/* + * The following are used for my horizontal scrolling stuff + */ +static char *xbuf; /* beg input buffer */ +static char *xend; /* end input buffer */ +static char *xcp; /* current position */ +static char *xep; /* current end */ +static char *xbp; /* start of visible portion of input buffer */ +static char *xlp; /* last byte visible on screen */ +static int x_adj_ok; +/* + * we use x_adj_done so that functions can tell + * whether x_adjust() has been called while they are active. + */ +static int x_adj_done; + +static int xx_cols; +static int x_col; +static int x_displen; +static int x_arg; /* general purpose arg */ +static int x_arg_defaulted;/* x_arg not explicitly set; defaulted to 1 */ + +static int xlp_valid; +/* end from 4.9 edit.h } */ +static int x_tty; /* are we on a tty? */ +static int x_bind_quiet; /* be quiet when binding keys */ +static int (*x_last_command)(int); + +static char **x_histp; /* history position */ +static int x_nextcmd; /* for newline-and-next */ +static char *xmp; /* mark pointer */ +#define KILLSIZE 20 +static char *killstack[KILLSIZE]; +static int killsp, killtp; +static int x_literal_set; +static int x_arg_set; +static char *macro_args; +static int prompt_skip; +static int prompt_redraw; + +static int x_ins(char *); +static void x_delete(int, int); +static int x_bword(void); +static int x_fword(void); +static void x_goto(char *); +static void x_bs(int); +static int x_size_str(char *); +static int x_size(int); +static void x_zots(char *); +static void x_zotc(int); +static void x_load_hist(char **); +static int x_search(char *, int, int); +static int x_match(char *, char *); +static void x_redraw(int); +static void x_push(int); +static void x_adjust(void); +static void x_e_ungetc(int); +static int x_e_getc(void); +static void x_e_putc(int); +static void x_e_puts(const char *); +static int x_comment(int); +static int x_fold_case(int); +static char *x_lastcp(void); +static void do_complete(int, Comp_type); +static int isu8cont(unsigned char); + +/* proto's for keybindings */ +static int x_abort(int); +static int x_beg_hist(int); +static int x_comp_comm(int); +static int x_comp_file(int); +static int x_complete(int); +static int x_del_back(int); +static int x_del_bword(int); +static int x_del_char(int); +static int x_del_fword(int); +static int x_del_line(int); +static int x_draw_line(int); +static int x_end_hist(int); +static int x_end_of_text(int); +static int x_enumerate(int); +static int x_eot_del(int); +static int x_error(int); +static int x_goto_hist(int); +static int x_ins_string(int); +static int x_insert(int); +static int x_kill(int); +static int x_kill_region(int); +static int x_list_comm(int); +static int x_list_file(int); +static int x_literal(int); +static int x_meta_yank(int); +static int x_mv_back(int); +static int x_mv_begin(int); +static int x_mv_bword(int); +static int x_mv_end(int); +static int x_mv_forw(int); +static int x_mv_fword(int); +static int x_newline(int); +static int x_next_com(int); +static int x_nl_next_com(int); +static int x_noop(int); +static int x_prev_com(int); +static int x_prev_histword(int); +static int x_search_char_forw(int); +static int x_search_char_back(int); +static int x_search_hist(int); +static int x_set_mark(int); +static int x_stuff(int); +static int x_stuffreset(int); +static int x_transpose(int); +static int x_version(int); +static int x_xchg_point_mark(int); +static int x_yank(int); +static int x_comp_list(int); +static int x_expand(int); +static int x_fold_capitalize(int); +static int x_fold_lower(int); +static int x_fold_upper(int); +static int x_set_arg(int); +static int x_comment(int); +#ifdef DEBUG +static int x_debug_info(int); +#endif + +static const struct x_ftab x_ftab[] = { + { x_abort, "abort", 0 }, + { x_beg_hist, "beginning-of-history", 0 }, + { x_comp_comm, "complete-command", 0 }, + { x_comp_file, "complete-file", 0 }, + { x_complete, "complete", 0 }, + { x_del_back, "delete-char-backward", XF_ARG }, + { x_del_bword, "delete-word-backward", XF_ARG }, + { x_del_char, "delete-char-forward", XF_ARG }, + { x_del_fword, "delete-word-forward", XF_ARG }, + { x_del_line, "kill-line", 0 }, + { x_draw_line, "redraw", 0 }, + { x_end_hist, "end-of-history", 0 }, + { x_end_of_text, "eot", 0 }, + { x_enumerate, "list", 0 }, + { x_eot_del, "eot-or-delete", XF_ARG }, + { x_error, "error", 0 }, + { x_goto_hist, "goto-history", XF_ARG }, + { x_ins_string, "macro-string", XF_NOBIND }, + { x_insert, "auto-insert", XF_ARG }, + { x_kill, "kill-to-eol", XF_ARG }, + { x_kill_region, "kill-region", 0 }, + { x_list_comm, "list-command", 0 }, + { x_list_file, "list-file", 0 }, + { x_literal, "quote", 0 }, + { x_meta_yank, "yank-pop", 0 }, + { x_mv_back, "backward-char", XF_ARG }, + { x_mv_begin, "beginning-of-line", 0 }, + { x_mv_bword, "backward-word", XF_ARG }, + { x_mv_end, "end-of-line", 0 }, + { x_mv_forw, "forward-char", XF_ARG }, + { x_mv_fword, "forward-word", XF_ARG }, + { x_newline, "newline", 0 }, + { x_next_com, "down-history", XF_ARG }, + { x_nl_next_com, "newline-and-next", 0 }, + { x_noop, "no-op", 0 }, + { x_prev_com, "up-history", XF_ARG }, + { x_prev_histword, "prev-hist-word", XF_ARG }, + { x_search_char_forw, "search-character-forward", XF_ARG }, + { x_search_char_back, "search-character-backward", XF_ARG }, + { x_search_hist, "search-history", 0 }, + { x_set_mark, "set-mark-command", 0 }, + { x_stuff, "stuff", 0 }, + { x_stuffreset, "stuff-reset", 0 }, + { x_transpose, "transpose-chars", 0 }, + { x_version, "version", 0 }, + { x_xchg_point_mark, "exchange-point-and-mark", 0 }, + { x_yank, "yank", 0 }, + { x_comp_list, "complete-list", 0 }, + { x_expand, "expand-file", 0 }, + { x_fold_capitalize, "capitalize-word", XF_ARG }, + { x_fold_lower, "downcase-word", XF_ARG }, + { x_fold_upper, "upcase-word", XF_ARG }, + { x_set_arg, "set-arg", XF_NOBIND }, + { x_comment, "comment", 0 }, + { 0, 0, 0 }, +#ifdef DEBUG + { x_debug_info, "debug-info", 0 }, +#else + { 0, 0, 0 }, +#endif + { 0, 0, 0 }, +}; + +int +isu8cont(unsigned char c) +{ + return (c & (0x80 | 0x40)) == 0x80; +} + +int +x_emacs(char *buf, size_t len) +{ + struct kb_entry *k, *kmatch = NULL; + char line[LINE + 1]; + int at = 0, submatch, ret, c; + const char *p; + + xbp = xbuf = buf; xend = buf + len; + xlp = xcp = xep = buf; + *xcp = 0; + xlp_valid = true; + xmp = NULL; + x_histp = histptr + 1; + + xx_cols = x_cols; + x_col = promptlen(prompt, &p); + prompt_skip = p - prompt; + x_adj_ok = 1; + prompt_redraw = 1; + if (x_col > xx_cols) + x_col = x_col - (x_col / xx_cols) * xx_cols; + x_displen = xx_cols - 2 - x_col; + x_adj_done = 0; + + pprompt(prompt, 0); + if (x_displen < 1) { + x_col = 0; + x_displen = xx_cols - 2; + x_e_putc('\n'); + prompt_redraw = 0; + } + + if (x_nextcmd >= 0) { + int off = source->line - x_nextcmd; + if (histptr - history >= off) + x_load_hist(histptr - off); + x_nextcmd = -1; + } + + line[0] = '\0'; + x_literal_set = 0; + x_arg = -1; + x_last_command = NULL; + while (1) { + x_flush(); + if ((c = x_e_getc()) < 0) + return 0; + + line[at++] = c; + line[at] = '\0'; + + if (x_arg == -1) { + x_arg = 1; + x_arg_defaulted = 1; + } + + if (x_literal_set) { + /* literal, so insert it */ + x_literal_set = 0; + submatch = 0; + } else { + submatch = 0; + kmatch = NULL; + TAILQ_FOREACH(k, &kblist, entry) { + if (at > k->len) + continue; + + if (memcmp(k->seq, line, at) == 0) { + /* sub match */ + submatch++; + if (k->len == at) + kmatch = k; + } + + /* see if we can abort search early */ + if (submatch > 1) + break; + } + } + + if (submatch == 1 && kmatch) { + if (kmatch->ftab->xf_func == x_ins_string && + kmatch->args && !macro_args) { + /* treat macro string as input */ + macro_args = kmatch->args; + ret = KSTD; + } else + ret = kmatch->ftab->xf_func(c); + } else { + if (submatch) + continue; + if (at == 1) + ret = x_insert(c); + else + ret = x_error(c); /* not matched meta sequence */ + } + + switch (ret) { + case KSTD: + if (kmatch) + x_last_command = kmatch->ftab->xf_func; + else + x_last_command = NULL; + break; + case KEOL: + ret = xep - xbuf; + return (ret); + break; + case KINTR: + trapsig(SIGINT); + x_mode(false); + unwind(LSHELL); + x_arg = -1; + break; + default: + bi_errorf("invalid return code"); /* can't happen */ + } + + /* reset meta sequence */ + at = 0; + line[0] = '\0'; + if (x_arg_set) + x_arg_set = 0; /* reset args next time around */ + else + x_arg = -1; + } +} + +static int +x_insert(int c) +{ + char str[2]; + + /* + * Should allow tab and control chars. + */ + if (c == 0) { + x_e_putc(BEL); + return KSTD; + } + str[0] = c; + str[1] = '\0'; + while (x_arg--) + x_ins(str); + return KSTD; +} + +static int +x_ins_string(int c) +{ + return x_insert(c); +} + +static int +x_do_ins(const char *cp, size_t len) +{ + if (xep+len >= xend) { + x_e_putc(BEL); + return -1; + } + + memmove(xcp+len, xcp, xep - xcp + 1); + memmove(xcp, cp, len); + xcp += len; + xep += len; + return 0; +} + +static int +x_ins(char *s) +{ + char *cp = xcp; + int adj = x_adj_done; + + if (x_do_ins(s, strlen(s)) < 0) + return -1; + /* + * x_zots() may result in a call to x_adjust() + * we want xcp to reflect the new position. + */ + xlp_valid = false; + x_lastcp(); + x_adj_ok = (xcp >= xlp); + x_zots(cp); + if (adj == x_adj_done) { /* has x_adjust() been called? */ + /* no */ + for (cp = xlp; cp > xcp; ) + x_bs(*--cp); + } + + x_adj_ok = 1; + return 0; +} + +static int +x_del_back(int c) +{ + int col = xcp - xbuf; + + if (col == 0) { + x_e_putc(BEL); + return KSTD; + } + if (x_arg > col) + x_arg = col; + while (x_arg < col && isu8cont(xcp[-x_arg])) + x_arg++; + x_goto(xcp - x_arg); + x_delete(x_arg, false); + return KSTD; +} + +static int +x_del_char(int c) +{ + int nleft = xep - xcp; + + if (!nleft) { + x_e_putc(BEL); + return KSTD; + } + if (x_arg > nleft) + x_arg = nleft; + while (x_arg < nleft && isu8cont(xcp[x_arg])) + x_arg++; + x_delete(x_arg, false); + return KSTD; +} + +/* Delete nc bytes to the right of the cursor (including cursor position) */ +static void +x_delete(int nc, int push) +{ + int i,j; + char *cp; + + if (nc == 0) + return; + if (xmp != NULL && xmp > xcp) { + if (xcp + nc > xmp) + xmp = xcp; + else + xmp -= nc; + } + + /* + * This lets us yank a word we have deleted. + */ + if (push) + x_push(nc); + + xep -= nc; + cp = xcp; + j = 0; + i = nc; + while (i--) { + j += x_size((unsigned char)*cp++); + } + memmove(xcp, xcp+nc, xep - xcp + 1); /* Copies the null */ + x_adj_ok = 0; /* don't redraw */ + x_zots(xcp); + /* + * if we are already filling the line, + * there is no need to ' ','\b'. + * But if we must, make sure we do the minimum. + */ + if ((i = xx_cols - 2 - x_col) > 0) { + j = (j < i) ? j : i; + i = j; + while (i--) + x_e_putc(' '); + i = j; + while (i--) + x_e_putc('\b'); + } + /*x_goto(xcp);*/ + x_adj_ok = 1; + xlp_valid = false; + for (cp = x_lastcp(); cp > xcp; ) + x_bs(*--cp); + + return; +} + +static int +x_del_bword(int c) +{ + x_delete(x_bword(), true); + return KSTD; +} + +static int +x_mv_bword(int c) +{ + (void)x_bword(); + return KSTD; +} + +static int +x_mv_fword(int c) +{ + x_goto(xcp + x_fword()); + return KSTD; +} + +static int +x_del_fword(int c) +{ + x_delete(x_fword(), true); + return KSTD; +} + +static int +x_bword(void) +{ + int nc = 0; + char *cp = xcp; + + if (cp == xbuf) { + x_e_putc(BEL); + return 0; + } + while (x_arg--) { + while (cp != xbuf && is_mfs(cp[-1])) { + cp--; + nc++; + } + while (cp != xbuf && !is_mfs(cp[-1])) { + cp--; + nc++; + } + } + x_goto(cp); + return nc; +} + +static int +x_fword(void) +{ + int nc = 0; + char *cp = xcp; + + if (cp == xep) { + x_e_putc(BEL); + return 0; + } + while (x_arg--) { + while (cp != xep && is_mfs(*cp)) { + cp++; + nc++; + } + while (cp != xep && !is_mfs(*cp)) { + cp++; + nc++; + } + } + return nc; +} + +static void +x_goto(char *cp) +{ + if (cp < xbp || cp >= (xbp + x_displen)) { + /* we are heading off screen */ + xcp = cp; + x_adjust(); + } else if (cp < xcp) { /* move back */ + while (cp < xcp) + x_bs((unsigned char)*--xcp); + } else if (cp > xcp) { /* move forward */ + while (cp > xcp) + x_zotc((unsigned char)*xcp++); + } +} + +static void +x_bs(int c) +{ + int i; + + i = x_size(c); + while (i--) + x_e_putc('\b'); +} + +static int +x_size_str(char *cp) +{ + int size = 0; + while (*cp) + size += x_size(*cp++); + return size; +} + +static int +x_size(int c) +{ + if (c=='\t') + return 4; /* Kludge, tabs are always four spaces. */ + if (iscntrl(c)) /* control char */ + return 2; + if (isu8cont(c)) + return 0; + return 1; +} + +static void +x_zots(char *str) +{ + int adj = x_adj_done; + + if (str > xbuf && isu8cont(*str)) { + while (str > xbuf && isu8cont(*str)) + str--; + x_e_putc('\b'); + } + x_lastcp(); + while (*str && str < xlp && adj == x_adj_done) + x_zotc(*str++); +} + +static void +x_zotc(int c) +{ + if (c == '\t') { + /* Kludge, tabs are always four spaces. */ + x_e_puts(" "); + } else if (iscntrl(c)) { + x_e_putc('^'); + x_e_putc(UNCTRL(c)); + } else + x_e_putc(c); +} + +static int +x_mv_back(int c) +{ + int col = xcp - xbuf; + + if (col == 0) { + x_e_putc(BEL); + return KSTD; + } + if (x_arg > col) + x_arg = col; + while (x_arg < col && isu8cont(xcp[-x_arg])) + x_arg++; + x_goto(xcp - x_arg); + return KSTD; +} + +static int +x_mv_forw(int c) +{ + int nleft = xep - xcp; + + if (!nleft) { + x_e_putc(BEL); + return KSTD; + } + if (x_arg > nleft) + x_arg = nleft; + while (x_arg < nleft && isu8cont(xcp[x_arg])) + x_arg++; + x_goto(xcp + x_arg); + return KSTD; +} + +static int +x_search_char_forw(int c) +{ + char *cp = xcp; + + *xep = '\0'; + c = x_e_getc(); + while (x_arg--) { + if (c < 0 || + ((cp = (cp == xep) ? NULL : strchr(cp + 1, c)) == NULL && + (cp = strchr(xbuf, c)) == NULL)) { + x_e_putc(BEL); + return KSTD; + } + } + x_goto(cp); + return KSTD; +} + +static int +x_search_char_back(int c) +{ + char *cp = xcp, *p; + + c = x_e_getc(); + for (; x_arg--; cp = p) + for (p = cp; ; ) { + if (p-- == xbuf) + p = xep; + if (c < 0 || p == cp) { + x_e_putc(BEL); + return KSTD; + } + if (*p == c) + break; + } + x_goto(cp); + return KSTD; +} + +static int +x_newline(int c) +{ + x_e_putc('\r'); + x_e_putc('\n'); + x_flush(); + *xep++ = '\n'; + return KEOL; +} + +static int +x_end_of_text(int c) +{ + x_zotc(edchars.eof); + x_putc('\r'); + x_putc('\n'); + x_flush(); + return KEOL; +} + +static int x_beg_hist(int c) { x_load_hist(history); return KSTD;} + +static int x_end_hist(int c) { x_load_hist(histptr); return KSTD;} + +static int x_prev_com(int c) { x_load_hist(x_histp - x_arg); return KSTD;} + +static int x_next_com(int c) { x_load_hist(x_histp + x_arg); return KSTD;} + +/* Goto a particular history number obtained from argument. + * If no argument is given history 1 is probably not what you + * want so we'll simply go to the oldest one. + */ +static int +x_goto_hist(int c) +{ + if (x_arg_defaulted) + x_load_hist(history); + else + x_load_hist(histptr + x_arg - source->line); + return KSTD; +} + +static void +x_load_hist(char **hp) +{ + int oldsize; + + if (hp < history || hp > histptr) { + x_e_putc(BEL); + return; + } + x_histp = hp; + oldsize = x_size_str(xbuf); + strlcpy(xbuf, *hp, xend - xbuf); + xbp = xbuf; + xep = xcp = xbuf + strlen(xbuf); + xlp_valid = false; + if (xep <= x_lastcp()) + x_redraw(oldsize); + x_goto(xep); +} + +static int +x_nl_next_com(int c) +{ + x_nextcmd = source->line - (histptr - x_histp) + 1; + return (x_newline(c)); +} + +static int +x_eot_del(int c) +{ + if (xep == xbuf && x_arg_defaulted) + return (x_end_of_text(c)); + else + return (x_del_char(c)); +} + +static void * +kb_find_hist_func(char c) +{ + struct kb_entry *k; + char line[LINE + 1]; + + line[0] = c; + line[1] = '\0'; + TAILQ_FOREACH(k, &kblist, entry) + if (!strcmp(k->seq, line)) + return (k->ftab->xf_func); + + return (x_insert); +} + +/* reverse incremental history search */ +static int +x_search_hist(int c) +{ + int offset = -1; /* offset of match in xbuf, else -1 */ + char pat [256+1]; /* pattern buffer */ + char *p = pat; + int (*f)(int); + + *p = '\0'; + while (1) { + if (offset < 0) { + x_e_puts("\nI-search: "); + x_e_puts(pat); + } + x_flush(); + if ((c = x_e_getc()) < 0) + return KSTD; + f = kb_find_hist_func(c); + if (c == CTRL('[')) + break; + else if (f == x_search_hist) + offset = x_search(pat, 0, offset); + else if (f == x_del_back) { + if (p == pat) { + offset = -1; + break; + } + if (p > pat) + *--p = '\0'; + if (p == pat) + offset = -1; + else + offset = x_search(pat, 1, offset); + continue; + } else if (f == x_insert) { + /* add char to pattern */ + /* overflow check... */ + if (p >= &pat[sizeof(pat) - 1]) { + x_e_putc(BEL); + continue; + } + *p++ = c, *p = '\0'; + if (offset >= 0) { + /* already have partial match */ + offset = x_match(xbuf, pat); + if (offset >= 0) { + x_goto(xbuf + offset + (p - pat) - + (*pat == '^')); + continue; + } + } + offset = x_search(pat, 0, offset); + } else { /* other command */ + x_e_ungetc(c); + break; + } + } + if (offset < 0) + x_redraw(-1); + return KSTD; +} + +/* search backward from current line */ +static int +x_search(char *pat, int sameline, int offset) +{ + char **hp; + int i; + + for (hp = x_histp - (sameline ? 0 : 1) ; hp >= history; --hp) { + i = x_match(*hp, pat); + if (i >= 0) { + if (offset < 0) + x_e_putc('\n'); + x_load_hist(hp); + x_goto(xbuf + i + strlen(pat) - (*pat == '^')); + return i; + } + } + x_e_putc(BEL); + x_histp = histptr; + return -1; +} + +/* return position of first match of pattern in string, else -1 */ +static int +x_match(char *str, char *pat) +{ + if (*pat == '^') { + return (strncmp(str, pat+1, strlen(pat+1)) == 0) ? 0 : -1; + } else { + char *q = strstr(str, pat); + return (q == NULL) ? -1 : q - str; + } +} + +static int +x_del_line(int c) +{ + int i, j; + + *xep = 0; + i = xep - xbuf; + j = x_size_str(xbuf); + xcp = xbuf; + x_push(i); + xlp = xbp = xep = xbuf; + xlp_valid = true; + *xcp = 0; + xmp = NULL; + x_redraw(j); + return KSTD; +} + +static int +x_mv_end(int c) +{ + x_goto(xep); + return KSTD; +} + +static int +x_mv_begin(int c) +{ + x_goto(xbuf); + return KSTD; +} + +static int +x_draw_line(int c) +{ + x_redraw(-1); + return KSTD; + +} + +/* Redraw (part of) the line. If limit is < 0, the everything is redrawn + * on a NEW line, otherwise limit is the screen column up to which needs + * redrawing. + */ +static void +x_redraw(int limit) +{ + int i, j, truncate = 0; + char *cp; + + x_adj_ok = 0; + if (limit == -1) + x_e_putc('\n'); + else + x_e_putc('\r'); + x_flush(); + if (xbp == xbuf) { + x_col = promptlen(prompt, NULL); + if (x_col > xx_cols) + truncate = (x_col / xx_cols) * xx_cols; + if (prompt_redraw) + pprompt(prompt + prompt_skip, truncate); + } + if (x_col > xx_cols) + x_col = x_col - (x_col / xx_cols) * xx_cols; + x_displen = xx_cols - 2 - x_col; + if (x_displen < 1) { + x_col = 0; + x_displen = xx_cols - 2; + } + xlp_valid = false; + cp = x_lastcp(); + x_zots(xbp); + if (xbp != xbuf || xep > xlp) + limit = xx_cols; + if (limit >= 0) { + if (xep > xlp) + i = 0; /* we fill the line */ + else + i = limit - (xlp - xbp); + + for (j = 0; j < i && x_col < (xx_cols - 2); j++) + x_e_putc(' '); + i = ' '; + if (xep > xlp) { /* more off screen */ + if (xbp > xbuf) + i = '*'; + else + i = '>'; + } else if (xbp > xbuf) + i = '<'; + x_e_putc(i); + j++; + while (j--) + x_e_putc('\b'); + } + for (cp = xlp; cp > xcp; ) + x_bs(*--cp); + x_adj_ok = 1; +#ifdef DEBUG + x_flush(); +#endif + return; +} + +static int +x_transpose(int c) +{ + char tmp; + + /* What transpose is meant to do seems to be up for debate. This + * is a general summary of the options; the text is abcd with the + * upper case character or underscore indicating the cursor position: + * Who Before After Before After + * at&t ksh in emacs mode: abCd abdC abcd_ (bell) + * at&t ksh in gmacs mode: abCd baCd abcd_ abdc_ + * gnu emacs: abCd acbD abcd_ abdc_ + * Pdksh currently goes with GNU behavior since I believe this is the + * most common version of emacs, unless in gmacs mode, in which case + * it does the at&t ksh gmacs mode. + * This should really be broken up into 3 functions so users can bind + * to the one they want. + */ + if (xcp == xbuf) { + x_e_putc(BEL); + return KSTD; + } else if (xcp == xep || Flag(FGMACS)) { + if (xcp - xbuf == 1) { + x_e_putc(BEL); + return KSTD; + } + /* Gosling/Unipress emacs style: Swap two characters before the + * cursor, do not change cursor position + */ + x_bs(xcp[-1]); + x_bs(xcp[-2]); + x_zotc(xcp[-1]); + x_zotc(xcp[-2]); + tmp = xcp[-1]; + xcp[-1] = xcp[-2]; + xcp[-2] = tmp; + } else { + /* GNU emacs style: Swap the characters before and under the + * cursor, move cursor position along one. + */ + x_bs(xcp[-1]); + x_zotc(xcp[0]); + x_zotc(xcp[-1]); + tmp = xcp[-1]; + xcp[-1] = xcp[0]; + xcp[0] = tmp; + x_bs(xcp[0]); + x_goto(xcp + 1); + } + return KSTD; +} + +static int +x_literal(int c) +{ + x_literal_set = 1; + return KSTD; +} + +static int +x_kill(int c) +{ + int col = xcp - xbuf; + int lastcol = xep - xbuf; + int ndel; + + if (x_arg_defaulted) + x_arg = lastcol; + else if (x_arg > lastcol) + x_arg = lastcol; + while (x_arg < lastcol && isu8cont(xbuf[x_arg])) + x_arg++; + ndel = x_arg - col; + if (ndel < 0) { + x_goto(xbuf + x_arg); + ndel = -ndel; + } + x_delete(ndel, true); + return KSTD; +} + +static void +x_push(int nchars) +{ + char *cp = str_nsave(xcp, nchars, AEDIT); + afree(killstack[killsp], AEDIT); + killstack[killsp] = cp; + killsp = (killsp + 1) % KILLSIZE; +} + +static int +x_yank(int c) +{ + if (killsp == 0) + killtp = KILLSIZE; + else + killtp = killsp; + killtp --; + if (killstack[killtp] == 0) { + x_e_puts("\nnothing to yank"); + x_redraw(-1); + return KSTD; + } + xmp = xcp; + x_ins(killstack[killtp]); + return KSTD; +} + +static int +x_meta_yank(int c) +{ + int len; + if ((x_last_command != x_yank && x_last_command != x_meta_yank) || + killstack[killtp] == 0) { + killtp = killsp; + x_e_puts("\nyank something first"); + x_redraw(-1); + return KSTD; + } + len = strlen(killstack[killtp]); + x_goto(xcp - len); + x_delete(len, false); + do { + if (killtp == 0) + killtp = KILLSIZE - 1; + else + killtp--; + } while (killstack[killtp] == 0); + x_ins(killstack[killtp]); + return KSTD; +} + +static int +x_abort(int c) +{ + /* x_zotc(c); */ + xlp = xep = xcp = xbp = xbuf; + xlp_valid = true; + *xcp = 0; + return KINTR; +} + +static int +x_error(int c) +{ + x_e_putc(BEL); + return KSTD; +} + +static int +x_stuffreset(int c) +{ +#ifdef TIOCSTI + (void)x_stuff(c); + return KINTR; +#else + x_zotc(c); + xlp = xcp = xep = xbp = xbuf; + xlp_valid = true; + *xcp = 0; + x_redraw(-1); + return KSTD; +#endif +} + +static int +x_stuff(int c) +{ +#ifdef TIOCSTI + char ch = c; + bool savmode = x_mode(false); + + (void)ioctl(TTY, TIOCSTI, &ch); + (void)x_mode(savmode); + x_redraw(-1); +#endif + return KSTD; +} + +static char * +kb_encode(const char *s) +{ + static char l[LINE + 1]; + int at = 0; + + l[at] = '\0'; + while (*s) { + if (*s == '^') { + s++; + if (*s >= '?') + l[at++] = CTRL(*s); + else { + l[at++] = '^'; + s--; + } + } else + l[at++] = *s; + l[at] = '\0'; + s++; + } + return (l); +} + +static char * +kb_decode(const char *s) +{ + static char l[LINE + 1]; + int i, at = 0; + + l[0] = '\0'; + for (i = 0; i < strlen(s); i++) { + if (iscntrl((unsigned char)s[i])) { + l[at++] = '^'; + l[at++] = UNCTRL(s[i]); + } else + l[at++] = s[i]; + l[at] = '\0'; + } + + return (l); +} + +static int +kb_match(char *s) +{ + int len = strlen(s); + struct kb_entry *k; + + TAILQ_FOREACH(k, &kblist, entry) { + if (len > k->len) + continue; + + if (memcmp(k->seq, s, len) == 0) + return (1); + } + + return (0); +} + +static void +kb_del(struct kb_entry *k) +{ + TAILQ_REMOVE(&kblist, k, entry); + free(k->args); + afree(k, AEDIT); +} + +static struct kb_entry * +kb_add_string(void *func, void *args, char *str) +{ + int i, count; + struct kb_entry *k; + struct x_ftab *xf = NULL; + + for (i = 0; i < NELEM(x_ftab); i++) + if (x_ftab[i].xf_func == func) { + xf = (struct x_ftab *)&x_ftab[i]; + break; + } + if (xf == NULL) + return (NULL); + + if (kb_match(str)) { + if (x_bind_quiet == 0) + bi_errorf("duplicate binding for %s", kb_decode(str)); + return (NULL); + } + count = strlen(str); + + k = alloc(sizeof *k + count + 1, AEDIT); + k->seq = (unsigned char *)(k + 1); + k->len = count; + k->ftab = xf; + k->args = args ? strdup(args) : NULL; + + strlcpy(k->seq, str, count + 1); + + TAILQ_INSERT_TAIL(&kblist, k, entry); + + return (k); +} + +static struct kb_entry * +kb_add(void *func, void *args, ...) +{ + va_list ap; + int i, count; + char l[LINE + 1]; + + va_start(ap, args); + count = 0; + while (va_arg(ap, unsigned int) != 0) + count++; + va_end(ap); + + va_start(ap, args); + for (i = 0; i <= count /* <= is correct */; i++) + l[i] = (unsigned char)va_arg(ap, unsigned int); + va_end(ap); + + return (kb_add_string(func, args, l)); +} + +static void +kb_print(struct kb_entry *k) +{ + if (!(k->ftab->xf_flags & XF_NOBIND)) + shprintf("%s = %s\n", + kb_decode(k->seq), k->ftab->xf_name); + else if (k->args) { + shprintf("%s = ", kb_decode(k->seq)); + shprintf("'%s'\n", kb_decode(k->args)); + } +} + +int +x_bind(const char *a1, const char *a2, + int macro, /* bind -m */ + int list) /* bind -l */ +{ + int i; + struct kb_entry *k, *kb; + char in[LINE + 1]; + + if (x_tty == 0) { + bi_errorf("cannot bind, not a tty"); + return (1); + } + + if (list) { + /* show all function names */ + for (i = 0; i < NELEM(x_ftab); i++) { + if (x_ftab[i].xf_name == NULL) + continue; + if (x_ftab[i].xf_name && + !(x_ftab[i].xf_flags & XF_NOBIND)) + shprintf("%s\n", x_ftab[i].xf_name); + } + return (0); + } + + if (a1 == NULL) { + /* show all bindings */ + TAILQ_FOREACH(k, &kblist, entry) + kb_print(k); + return (0); + } + + snprintf(in, sizeof in, "%s", kb_encode(a1)); + if (a2 == NULL) { + /* print binding */ + TAILQ_FOREACH(k, &kblist, entry) + if (!strcmp(k->seq, in)) { + kb_print(k); + return (0); + } + shprintf("%s = %s\n", kb_decode(a1), "auto-insert"); + return (0); + } + + if (strlen(a2) == 0) { + /* clear binding */ + TAILQ_FOREACH_SAFE(k, &kblist, entry, kb) + if (!strcmp(k->seq, in)) { + kb_del(k); + break; + } + return (0); + } + + /* set binding */ + if (macro) { + /* delete old mapping */ + TAILQ_FOREACH_SAFE(k, &kblist, entry, kb) + if (!strcmp(k->seq, in)) { + kb_del(k); + break; + } + kb_add_string(x_ins_string, kb_encode(a2), in); + return (0); + } + + /* set non macro binding */ + for (i = 0; i < NELEM(x_ftab); i++) { + if (x_ftab[i].xf_name == NULL) + continue; + if (!strcmp(x_ftab[i].xf_name, a2)) { + /* delete old mapping */ + TAILQ_FOREACH_SAFE(k, &kblist, entry, kb) + if (!strcmp(k->seq, in)) { + kb_del(k); + break; + } + kb_add_string(x_ftab[i].xf_func, NULL, in); + return (0); + } + } + bi_errorf("%s: no such function", a2); + return (1); +} + +void +x_init_emacs(void) +{ + char *locale; + + x_tty = 1; + ainit(AEDIT); + x_nextcmd = -1; + + /* Determine if we can translate meta key or use 8-bit AscII + * XXX - It would be nice if there was a locale attribute to + * determine if the locale is 7-bit or not. + */ + locale = setlocale(LC_CTYPE, NULL); + if (locale == NULL || !strcmp(locale, "C") || !strcmp(locale, "POSIX")) + Flag(FEMACSUSEMETA) = 1; + + /* new keybinding stuff */ + TAILQ_INIT(&kblist); + + /* man page order */ + kb_add(x_abort, NULL, CTRL('G'), 0); + kb_add(x_mv_back, NULL, CTRL('B'), 0); + kb_add(x_mv_back, NULL, CTRL('X'), CTRL('D'), 0); + kb_add(x_mv_bword, NULL, CTRL('['), 'b', 0); + kb_add(x_beg_hist, NULL, CTRL('['), '<', 0); + kb_add(x_mv_begin, NULL, CTRL('A'), 0); + kb_add(x_fold_capitalize, NULL, CTRL('['), 'C', 0); + kb_add(x_fold_capitalize, NULL, CTRL('['), 'c', 0); + kb_add(x_comment, NULL, CTRL('['), '#', 0); + kb_add(x_complete, NULL, CTRL('['), CTRL('['), 0); + kb_add(x_comp_comm, NULL, CTRL('X'), CTRL('['), 0); + kb_add(x_comp_file, NULL, CTRL('['), CTRL('X'), 0); + kb_add(x_comp_list, NULL, CTRL('I'), 0); + kb_add(x_comp_list, NULL, CTRL('['), '=', 0); + kb_add(x_del_back, NULL, CTRL('?'), 0); + kb_add(x_del_back, NULL, CTRL('H'), 0); + kb_add(x_del_char, NULL, CTRL('['), '[', '3', '~', 0); /* delete */ + kb_add(x_del_bword, NULL, CTRL('['), CTRL('?'), 0); + kb_add(x_del_bword, NULL, CTRL('['), CTRL('H'), 0); + kb_add(x_del_bword, NULL, CTRL('['), 'h', 0); + kb_add(x_del_fword, NULL, CTRL('['), 'd', 0); + kb_add(x_next_com, NULL, CTRL('N'), 0); + kb_add(x_next_com, NULL, CTRL('X'), 'B', 0); + kb_add(x_fold_lower, NULL, CTRL('['), 'L', 0); + kb_add(x_fold_lower, NULL, CTRL('['), 'l', 0); + kb_add(x_end_hist, NULL, CTRL('['), '>', 0); + kb_add(x_mv_end, NULL, CTRL('E'), 0); + /* how to handle: eot: ^_, underneath copied from original keybindings */ + kb_add(x_end_of_text, NULL, CTRL('_'), 0); + kb_add(x_eot_del, NULL, CTRL('D'), 0); + /* error */ + kb_add(x_xchg_point_mark, NULL, CTRL('X'), CTRL('X'), 0); + kb_add(x_expand, NULL, CTRL('['), '*', 0); + kb_add(x_mv_forw, NULL, CTRL('F'), 0); + kb_add(x_mv_forw, NULL, CTRL('X'), 'C', 0); + kb_add(x_mv_fword, NULL, CTRL('['), 'f', 0); + kb_add(x_goto_hist, NULL, CTRL('['), 'g', 0); + /* kill-line */ + kb_add(x_del_bword, NULL, CTRL('W'), 0); /* not what man says */ + kb_add(x_kill, NULL, CTRL('K'), 0); + kb_add(x_enumerate, NULL, CTRL('['), '?', 0); + kb_add(x_list_comm, NULL, CTRL('X'), '?', 0); + kb_add(x_list_file, NULL, CTRL('X'), CTRL('Y'), 0); + kb_add(x_newline, NULL, CTRL('J'), 0); + kb_add(x_newline, NULL, CTRL('M'), 0); + kb_add(x_nl_next_com, NULL, CTRL('O'), 0); + /* no-op */ + kb_add(x_prev_histword, NULL, CTRL('['), '.', 0); + kb_add(x_prev_histword, NULL, CTRL('['), '_', 0); + /* how to handle: quote: ^^ */ + kb_add(x_draw_line, NULL, CTRL('L'), 0); + kb_add(x_search_char_back, NULL, CTRL('['), CTRL(']'), 0); + kb_add(x_search_char_forw, NULL, CTRL(']'), 0); + kb_add(x_search_hist, NULL, CTRL('R'), 0); + kb_add(x_set_mark, NULL, CTRL('['), ' ', 0); +#if defined(TIOCSTI) + kb_add(x_stuff, NULL, CTRL('T'), 0); + /* stuff-reset */ +#else + kb_add(x_transpose, NULL, CTRL('T'), 0); +#endif + kb_add(x_prev_com, NULL, CTRL('P'), 0); + kb_add(x_prev_com, NULL, CTRL('X'), 'A', 0); + kb_add(x_fold_upper, NULL, CTRL('['), 'U', 0); + kb_add(x_fold_upper, NULL, CTRL('['), 'u', 0); + kb_add(x_literal, NULL, CTRL('V'), 0); + kb_add(x_literal, NULL, CTRL('^'), 0); + kb_add(x_yank, NULL, CTRL('Y'), 0); + kb_add(x_meta_yank, NULL, CTRL('['), 'y', 0); + /* man page ends here */ + + /* arrow keys */ + kb_add(x_prev_com, NULL, CTRL('['), '[', 'A', 0); /* up */ + kb_add(x_next_com, NULL, CTRL('['), '[', 'B', 0); /* down */ + kb_add(x_mv_forw, NULL, CTRL('['), '[', 'C', 0); /* right */ + kb_add(x_mv_back, NULL, CTRL('['), '[', 'D', 0); /* left */ + kb_add(x_prev_com, NULL, CTRL('['), 'O', 'A', 0); /* up */ + kb_add(x_next_com, NULL, CTRL('['), 'O', 'B', 0); /* down */ + kb_add(x_mv_forw, NULL, CTRL('['), 'O', 'C', 0); /* right */ + kb_add(x_mv_back, NULL, CTRL('['), 'O', 'D', 0); /* left */ + + /* more navigation keys */ + kb_add(x_mv_begin, NULL, CTRL('['), '[', 'H', 0); /* home */ + kb_add(x_mv_end, NULL, CTRL('['), '[', 'F', 0); /* end */ + kb_add(x_mv_begin, NULL, CTRL('['), 'O', 'H', 0); /* home */ + kb_add(x_mv_end, NULL, CTRL('['), 'O', 'F', 0); /* end */ + kb_add(x_mv_begin, NULL, CTRL('['), '[', '1', '~', 0); /* home */ + kb_add(x_mv_end, NULL, CTRL('['), '[', '4', '~', 0); /* end */ + + /* can't be bound */ + kb_add(x_set_arg, NULL, CTRL('['), '0', 0); + kb_add(x_set_arg, NULL, CTRL('['), '1', 0); + kb_add(x_set_arg, NULL, CTRL('['), '2', 0); + kb_add(x_set_arg, NULL, CTRL('['), '3', 0); + kb_add(x_set_arg, NULL, CTRL('['), '4', 0); + kb_add(x_set_arg, NULL, CTRL('['), '5', 0); + kb_add(x_set_arg, NULL, CTRL('['), '6', 0); + kb_add(x_set_arg, NULL, CTRL('['), '7', 0); + kb_add(x_set_arg, NULL, CTRL('['), '8', 0); + kb_add(x_set_arg, NULL, CTRL('['), '9', 0); + + /* ctrl arrow keys */ + kb_add(x_mv_end, NULL, CTRL('['), '[', '1', ';', '5', 'A', 0); /* ctrl up */ + kb_add(x_mv_begin, NULL, CTRL('['), '[', '1', ';', '5', 'B', 0); /* ctrl down */ + kb_add(x_mv_fword, NULL, CTRL('['), '[', '1', ';', '5', 'C', 0); /* ctrl right */ + kb_add(x_mv_bword, NULL, CTRL('['), '[', '1', ';', '5', 'D', 0); /* ctrl left */ +} + +void +x_emacs_keys(X_chars *ec) +{ + x_bind_quiet = 1; + if (ec->erase >= 0) { + kb_add(x_del_back, NULL, ec->erase, 0); + kb_add(x_del_bword, NULL, CTRL('['), ec->erase, 0); + } + if (ec->kill >= 0) + kb_add(x_del_line, NULL, ec->kill, 0); + if (ec->werase >= 0) + kb_add(x_del_bword, NULL, ec->werase, 0); + if (ec->intr >= 0) + kb_add(x_abort, NULL, ec->intr, 0); + if (ec->quit >= 0) + kb_add(x_noop, NULL, ec->quit, 0); + x_bind_quiet = 0; +} + +static int +x_set_mark(int c) +{ + xmp = xcp; + return KSTD; +} + +static int +x_kill_region(int c) +{ + int rsize; + char *xr; + + if (xmp == NULL) { + x_e_putc(BEL); + return KSTD; + } + if (xmp > xcp) { + rsize = xmp - xcp; + xr = xcp; + } else { + rsize = xcp - xmp; + xr = xmp; + } + x_goto(xr); + x_delete(rsize, true); + xmp = xr; + return KSTD; +} + +static int +x_xchg_point_mark(int c) +{ + char *tmp; + + if (xmp == NULL) { + x_e_putc(BEL); + return KSTD; + } + tmp = xmp; + xmp = xcp; + x_goto( tmp ); + return KSTD; +} + +static int +x_version(int c) +{ + char *o_xbuf = xbuf, *o_xend = xend; + char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp; + int lim = x_lastcp() - xbp; + + xbuf = xbp = xcp = (char *) ksh_version + 4; + xend = xep = (char *) ksh_version + 4 + strlen(ksh_version + 4); + x_redraw(lim); + x_flush(); + + c = x_e_getc(); + xbuf = o_xbuf; + xend = o_xend; + xbp = o_xbp; + xep = o_xep; + xcp = o_xcp; + x_redraw(strlen(ksh_version)); + + if (c < 0) + return KSTD; + /* This is what at&t ksh seems to do... Very bizarre */ + if (c != ' ') + x_e_ungetc(c); + + return KSTD; +} + +static int +x_noop(int c) +{ + return KSTD; +} + +/* + * File/command name completion routines + */ + +static int +x_comp_comm(int c) +{ + do_complete(XCF_COMMAND, CT_COMPLETE); + return KSTD; +} +static int +x_list_comm(int c) +{ + do_complete(XCF_COMMAND, CT_LIST); + return KSTD; +} +static int +x_complete(int c) +{ + do_complete(XCF_COMMAND_FILE, CT_COMPLETE); + return KSTD; +} +static int +x_enumerate(int c) +{ + do_complete(XCF_COMMAND_FILE, CT_LIST); + return KSTD; +} +static int +x_comp_file(int c) +{ + do_complete(XCF_FILE, CT_COMPLETE); + return KSTD; +} +static int +x_list_file(int c) +{ + do_complete(XCF_FILE, CT_LIST); + return KSTD; +} +static int +x_comp_list(int c) +{ + do_complete(XCF_COMMAND_FILE, CT_COMPLIST); + return KSTD; +} +static int +x_expand(int c) +{ + char **words; + int nwords = 0; + int start, end; + int is_command; + int i; + + nwords = x_cf_glob(XCF_FILE, xbuf, xep - xbuf, xcp - xbuf, + &start, &end, &words, &is_command); + + if (nwords == 0) { + x_e_putc(BEL); + return KSTD; + } + + x_goto(xbuf + start); + x_delete(end - start, false); + for (i = 0; i < nwords;) { + if (x_escape(words[i], strlen(words[i]), x_do_ins) < 0 || + (++i < nwords && x_ins(" ") < 0)) { + x_e_putc(BEL); + return KSTD; + } + } + x_adjust(); + + return KSTD; +} + +/* type == 0 for list, 1 for complete and 2 for complete-list */ +static void +do_complete(int flags, /* XCF_{COMMAND,FILE,COMMAND_FILE} */ + Comp_type type) +{ + char **words; + int nwords; + int start, end, nlen, olen; + int is_command; + int completed = 0; + + nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf, + &start, &end, &words, &is_command); + /* no match */ + if (nwords == 0) { + x_e_putc(BEL); + return; + } + + if (type == CT_LIST) { + x_print_expansions(nwords, words, is_command); + x_redraw(0); + x_free_words(nwords, words); + return; + } + + olen = end - start; + nlen = x_longest_prefix(nwords, words); + /* complete */ + if (nwords == 1 || nlen > olen) { + x_goto(xbuf + start); + x_delete(olen, false); + x_escape(words[0], nlen, x_do_ins); + x_adjust(); + completed = 1; + } + /* add space if single non-dir match */ + if (nwords == 1 && words[0][nlen - 1] != '/') { + x_ins(" "); + completed = 1; + } + + if (type == CT_COMPLIST && !completed) { + x_print_expansions(nwords, words, is_command); + completed = 1; + } + + if (completed) + x_redraw(0); + + x_free_words(nwords, words); +} + +/* NAME: + * x_adjust - redraw the line adjusting starting point etc. + * + * DESCRIPTION: + * This function is called when we have exceeded the bounds + * of the edit window. It increments x_adj_done so that + * functions like x_ins and x_delete know that we have been + * called and can skip the x_bs() stuff which has already + * been done by x_redraw. + * + * RETURN VALUE: + * None + */ + +static void +x_adjust(void) +{ + x_adj_done++; /* flag the fact that we were called. */ + /* + * we had a problem if the prompt length > xx_cols / 2 + */ + if ((xbp = xcp - (x_displen / 2)) < xbuf) + xbp = xbuf; + xlp_valid = false; + x_redraw(xx_cols); + x_flush(); +} + +static int unget_char = -1; + +static void +x_e_ungetc(int c) +{ + unget_char = c; +} + +static int +x_e_getc(void) +{ + int c; + + if (unget_char >= 0) { + c = unget_char; + unget_char = -1; + } else if (macro_args) { + c = *macro_args++; + if (!c) { + macro_args = NULL; + c = x_getc(); + } + } else + c = x_getc(); + + return c; +} + +static void +x_e_putc(int c) +{ + if (c == '\r' || c == '\n') + x_col = 0; + if (x_col < xx_cols) { + x_putc(c); + switch (c) { + case BEL: + break; + case '\r': + case '\n': + break; + case '\b': + x_col--; + break; + default: + x_col++; + break; + } + } + if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2))) + x_adjust(); +} + +#ifdef DEBUG +static int +x_debug_info(int c) +{ + x_flush(); + shellf("\nksh debug:\n"); + shellf("\tx_col == %d,\t\tx_cols == %d,\tx_displen == %d\n", + x_col, xx_cols, x_displen); + shellf("\txcp == 0x%lx,\txep == 0x%lx\n", (long) xcp, (long) xep); + shellf("\txbp == 0x%lx,\txbuf == 0x%lx\n", (long) xbp, (long) xbuf); + shellf("\txlp == 0x%lx\n", (long) xlp); + shellf("\txlp == 0x%lx\n", (long) x_lastcp()); + shellf("\n"); + x_redraw(-1); + return 0; +} +#endif + +static void +x_e_puts(const char *s) +{ + int adj = x_adj_done; + + while (*s && adj == x_adj_done) + x_e_putc(*s++); +} + +/* NAME: + * x_set_arg - set an arg value for next function + * + * DESCRIPTION: + * This is a simple implementation of M-[0-9]. + * + * RETURN VALUE: + * KSTD + */ + +static int +x_set_arg(int c) +{ + int n = 0; + int first = 1; + + for (; c >= 0 && isdigit(c); c = x_e_getc(), first = 0) + n = n * 10 + (c - '0'); + if (c < 0 || first) { + x_e_putc(BEL); + x_arg = 1; + x_arg_defaulted = 1; + } else { + x_e_ungetc(c); + x_arg = n; + x_arg_defaulted = 0; + x_arg_set = 1; + } + return KSTD; +} + + +/* Comment or uncomment the current line. */ +static int +x_comment(int c) +{ + int oldsize = x_size_str(xbuf); + int len = xep - xbuf; + int ret = x_do_comment(xbuf, xend - xbuf, &len); + + if (ret < 0) + x_e_putc(BEL); + else { + xep = xbuf + len; + *xep = '\0'; + xcp = xbp = xbuf; + x_redraw(oldsize); + if (ret > 0) + return x_newline('\n'); + } + return KSTD; +} + + +/* NAME: + * x_prev_histword - recover word from prev command + * + * DESCRIPTION: + * This function recovers the last word from the previous + * command and inserts it into the current edit line. If a + * numeric arg is supplied then the n'th word from the + * start of the previous command is used. + * + * Bound to M-. + * + * RETURN VALUE: + * KSTD + */ + +static int +x_prev_histword(int c) +{ + char *rcp; + char *cp; + + cp = *histptr; + if (!cp) + x_e_putc(BEL); + else if (x_arg_defaulted) { + rcp = &cp[strlen(cp) - 1]; + /* + * ignore white-space after the last word + */ + while (rcp > cp && is_cfs(*rcp)) + rcp--; + while (rcp > cp && !is_cfs(*rcp)) + rcp--; + if (is_cfs(*rcp)) + rcp++; + x_ins(rcp); + } else { + int c; + + rcp = cp; + /* + * ignore white-space at start of line + */ + while (*rcp && is_cfs(*rcp)) + rcp++; + while (x_arg-- > 1) { + while (*rcp && !is_cfs(*rcp)) + rcp++; + while (*rcp && is_cfs(*rcp)) + rcp++; + } + cp = rcp; + while (*rcp && !is_cfs(*rcp)) + rcp++; + c = *rcp; + *rcp = '\0'; + x_ins(cp); + *rcp = c; + } + return KSTD; +} + +/* Uppercase N(1) words */ +static int +x_fold_upper(int c) +{ + return x_fold_case('U'); +} + +/* Lowercase N(1) words */ +static int +x_fold_lower(int c) +{ + return x_fold_case('L'); +} + +/* Lowercase N(1) words */ +static int +x_fold_capitalize(int c) +{ + return x_fold_case('C'); +} + +/* NAME: + * x_fold_case - convert word to UPPER/lower/Capital case + * + * DESCRIPTION: + * This function is used to implement M-U,M-u,M-L,M-l,M-C and M-c + * to UPPER case, lower case or Capitalize words. + * + * RETURN VALUE: + * None + */ + +static int +x_fold_case(int c) +{ + char *cp = xcp; + + if (cp == xep) { + x_e_putc(BEL); + return KSTD; + } + while (x_arg--) { + /* + * first skip over any white-space + */ + while (cp != xep && is_mfs(*cp)) + cp++; + /* + * do the first char on its own since it may be + * a different action than for the rest. + */ + if (cp != xep) { + if (c == 'L') { /* lowercase */ + if (isupper((unsigned char)*cp)) + *cp = tolower((unsigned char)*cp); + } else { /* uppercase, capitalize */ + if (islower((unsigned char)*cp)) + *cp = toupper((unsigned char)*cp); + } + cp++; + } + /* + * now for the rest of the word + */ + while (cp != xep && !is_mfs(*cp)) { + if (c == 'U') { /* uppercase */ + if (islower((unsigned char)*cp)) + *cp = toupper((unsigned char)*cp); + } else { /* lowercase, capitalize */ + if (isupper((unsigned char)*cp)) + *cp = tolower((unsigned char)*cp); + } + cp++; + } + } + x_goto(cp); + return KSTD; +} + +/* NAME: + * x_lastcp - last visible byte + * + * SYNOPSIS: + * x_lastcp() + * + * DESCRIPTION: + * This function returns a pointer to that byte in the + * edit buffer that will be the last displayed on the + * screen. The sequence: + * + * for (cp = x_lastcp(); cp > xcp; cp) + * x_bs(*--cp); + * + * Will position the cursor correctly on the screen. + * + * RETURN VALUE: + * cp or NULL + */ + +static char * +x_lastcp(void) +{ + char *rcp; + int i; + + if (!xlp_valid) { + for (i = 0, rcp = xbp; rcp < xep && i < x_displen; rcp++) + i += x_size((unsigned char)*rcp); + xlp = rcp; + } + xlp_valid = true; + return (xlp); +} + +#endif /* EDIT */ diff --git a/bin/ksh/eval.c b/bin/ksh/eval.c @@ -0,0 +1,1340 @@ +/* $OpenBSD: eval.c,v 1.50 2016/03/05 12:30:17 czarkoff Exp $ */ + +/* + * Expansion - quoting, separation, substitution, globbing + */ + +#include <sys/stat.h> + +#include <ctype.h> +#include <dirent.h> +#include <fcntl.h> +#include <pwd.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "sh.h" + +/* + * string expansion + * + * first pass: quoting, IFS separation, ~, ${}, $() and $(()) substitution. + * second pass: alternation ({,}), filename expansion (*?[]). + */ + +/* expansion generator state */ +typedef struct Expand { + /* int type; */ /* see expand() */ + const char *str; /* string */ + union { + const char **strv;/* string[] */ + struct shf *shf;/* file */ + } u; /* source */ + struct tbl *var; /* variable in ${var..} */ + short split; /* split "$@" / call waitlast $() */ +} Expand; + +#define XBASE 0 /* scanning original */ +#define XSUB 1 /* expanding ${} string */ +#define XARGSEP 2 /* ifs0 between "$*" */ +#define XARG 3 /* expanding $*, $@ */ +#define XCOM 4 /* expanding $() */ +#define XNULLSUB 5 /* "$@" when $# is 0 (don't generate word) */ +#define XSUBMID 6 /* middle of expanding ${} */ + +/* States used for field splitting */ +#define IFS_WORD 0 /* word has chars (or quotes) */ +#define IFS_WS 1 /* have seen IFS white-space */ +#define IFS_NWS 2 /* have seen IFS non-white-space */ + +static int varsub(Expand *, char *, char *, int *, int *); +static int comsub(Expand *, char *); +static char *trimsub(char *, char *, int); +static void glob(char *, XPtrV *, int); +static void globit(XString *, char **, char *, XPtrV *, int); +static char *maybe_expand_tilde(char *, XString *, char **, int); +static char *tilde(char *); +static char *homedir(char *); +#ifdef BRACE_EXPAND +static void alt_expand(XPtrV *, char *, char *, char *, int); +#endif + +/* compile and expand word */ +char * +substitute(const char *cp, int f) +{ + struct source *s, *sold; + + sold = source; + s = pushs(SWSTR, ATEMP); + s->start = s->str = cp; + source = s; + if (yylex(ONEWORD) != LWORD) + internal_errorf(1, "substitute"); + source = sold; + afree(s, ATEMP); + return evalstr(yylval.cp, f); +} + +/* + * expand arg-list + */ +char ** +eval(char **ap, int f) +{ + XPtrV w; + + if (*ap == NULL) + return ap; + XPinit(w, 32); + XPput(w, NULL); /* space for shell name */ + while (*ap != NULL) + expand(*ap++, &w, f); + XPput(w, NULL); + return (char **) XPclose(w) + 1; +} + +/* + * expand string + */ +char * +evalstr(char *cp, int f) +{ + XPtrV w; + + XPinit(w, 1); + expand(cp, &w, f); + cp = (XPsize(w) == 0) ? null : (char*) *XPptrv(w); + XPfree(w); + return cp; +} + +/* + * expand string - return only one component + * used from iosetup to expand redirection files + */ +char * +evalonestr(char *cp, int f) +{ + XPtrV w; + + XPinit(w, 1); + expand(cp, &w, f); + switch (XPsize(w)) { + case 0: + cp = null; + break; + case 1: + cp = (char*) *XPptrv(w); + break; + default: + cp = evalstr(cp, f&~DOGLOB); + break; + } + XPfree(w); + return cp; +} + +/* for nested substitution: ${var:=$var2} */ +typedef struct SubType { + short stype; /* [=+-?%#] action after expanded word */ + short base; /* begin position of expanded word */ + short f; /* saved value of f (DOPAT, etc) */ + struct tbl *var; /* variable for ${var..} */ + short quote; /* saved value of quote (for ${..[%#]..}) */ + struct SubType *prev; /* old type */ + struct SubType *next; /* poped type (to avoid re-allocating) */ +} SubType; + +void +expand(char *cp, /* input word */ + XPtrV *wp, /* output words */ + int f) /* DO* flags */ +{ + int c = 0; + int type; /* expansion type */ + int quote = 0; /* quoted */ + XString ds; /* destination string */ + char *dp, *sp; /* dest., source */ + int fdo, word; /* second pass flags; have word */ + int doblank; /* field splitting of parameter/command subst */ + Expand x = { + /* expansion variables */ + NULL, { NULL }, NULL, 0 + }; + SubType st_head, *st; + int newlines = 0; /* For trailing newlines in COMSUB */ + int saw_eq, tilde_ok; + int make_magic; + size_t len; + + if (cp == NULL) + internal_errorf(1, "expand(NULL)"); + /* for alias, readonly, set, typeset commands */ + if ((f & DOVACHECK) && is_wdvarassign(cp)) { + f &= ~(DOVACHECK|DOBLANK|DOGLOB|DOTILDE); + f |= DOASNTILDE; + } + if (Flag(FNOGLOB)) + f &= ~DOGLOB; + if (Flag(FMARKDIRS)) + f |= DOMARKDIRS; +#ifdef BRACE_EXPAND + if (Flag(FBRACEEXPAND) && (f & DOGLOB)) + f |= DOBRACE_; +#endif /* BRACE_EXPAND */ + + Xinit(ds, dp, 128, ATEMP); /* init dest. string */ + type = XBASE; + sp = cp; + fdo = 0; + saw_eq = 0; + tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0; /* must be 1/0 */ + doblank = 0; + make_magic = 0; + word = (f&DOBLANK) ? IFS_WS : IFS_WORD; + st_head.next = NULL; + st = &st_head; + + while (1) { + Xcheck(ds, dp); + + switch (type) { + case XBASE: /* original prefixed string */ + c = *sp++; + switch (c) { + case EOS: + c = 0; + break; + case CHAR: + c = *sp++; + break; + case QCHAR: + quote |= 2; /* temporary quote */ + c = *sp++; + break; + case OQUOTE: + word = IFS_WORD; + tilde_ok = 0; + quote = 1; + continue; + case CQUOTE: + quote = 0; + continue; + case COMSUB: + tilde_ok = 0; + if (f & DONTRUNCOMMAND) { + word = IFS_WORD; + *dp++ = '$'; *dp++ = '('; + while (*sp != '\0') { + Xcheck(ds, dp); + *dp++ = *sp++; + } + *dp++ = ')'; + } else { + type = comsub(&x, sp); + if (type == XCOM && (f&DOBLANK)) + doblank++; + sp = strchr(sp, 0) + 1; + newlines = 0; + } + continue; + case EXPRSUB: + word = IFS_WORD; + tilde_ok = 0; + if (f & DONTRUNCOMMAND) { + *dp++ = '$'; *dp++ = '('; *dp++ = '('; + while (*sp != '\0') { + Xcheck(ds, dp); + *dp++ = *sp++; + } + *dp++ = ')'; *dp++ = ')'; + } else { + struct tbl v; + char *p; + + v.flag = DEFINED|ISSET|INTEGER; + v.type = 10; /* not default */ + v.name[0] = '\0'; + v_evaluate(&v, substitute(sp, 0), + KSH_UNWIND_ERROR, true); + sp = strchr(sp, 0) + 1; + for (p = str_val(&v); *p; ) { + Xcheck(ds, dp); + *dp++ = *p++; + } + } + continue; + case OSUBST: /* ${{#}var{:}[=+-?#%]word} */ + /* format is: + * OSUBST [{x] plain-variable-part \0 + * compiled-word-part CSUBST [}x] + * This is where all syntax checking gets done... + */ + { + char *varname = ++sp; /* skip the { or x (}) */ + int stype; + int slen = 0; + + sp = strchr(sp, '\0') + 1; /* skip variable */ + type = varsub(&x, varname, sp, &stype, &slen); + if (type < 0) { + char endc; + char *str, *end; + + sp = varname - 2; /* restore sp */ + end = (char *) wdscan(sp, CSUBST); + /* ({) the } or x is already skipped */ + endc = *end; + *end = EOS; + str = snptreef(NULL, 64, "%S", sp); + *end = endc; + errorf("%s: bad substitution", str); + } + if (f&DOBLANK) + doblank++; + tilde_ok = 0; + if (type == XBASE) { /* expand? */ + if (!st->next) { + SubType *newst; + + newst = alloc( + sizeof(SubType), ATEMP); + newst->next = NULL; + newst->prev = st; + st->next = newst; + } + st = st->next; + st->stype = stype; + st->base = Xsavepos(ds, dp); + st->f = f; + st->var = x.var; + st->quote = quote; + /* skip qualifier(s) */ + if (stype) + sp += slen; + switch (stype & 0x7f) { + case '#': + case '%': + /* ! DOBLANK,DOBRACE_,DOTILDE */ + f = DOPAT | (f&DONTRUNCOMMAND) | + DOTEMP_; + quote = 0; + /* Prepend open pattern (so | + * in a trim will work as + * expected) + */ + *dp++ = MAGIC; + *dp++ = '@' + 0x80; + break; + case '=': + /* Enabling tilde expansion + * after :'s here is + * non-standard ksh, but is + * consistent with rules for + * other assignments. Not + * sure what POSIX thinks of + * this. + * Not doing tilde expansion + * for integer variables is a + * non-POSIX thing - makes + * sense though, since ~ is + * a arithmetic operator. + */ + if (!(x.var->flag & INTEGER)) + f |= DOASNTILDE|DOTILDE; + f |= DOTEMP_; + /* These will be done after the + * value has been assigned. + */ + f &= ~(DOBLANK|DOGLOB|DOBRACE_); + tilde_ok = 1; + break; + case '?': + f &= ~DOBLANK; + f |= DOTEMP_; + /* FALLTHROUGH */ + default: + /* Enable tilde expansion */ + tilde_ok = 1; + f |= DOTILDE; + } + } else + /* skip word */ + sp = (char *) wdscan(sp, CSUBST); + continue; + } + case CSUBST: /* only get here if expanding word */ + sp++; /* ({) skip the } or x */ + tilde_ok = 0; /* in case of ${unset:-} */ + *dp = '\0'; + quote = st->quote; + f = st->f; + if (f&DOBLANK) + doblank--; + switch (st->stype&0x7f) { + case '#': + case '%': + /* Append end-pattern */ + *dp++ = MAGIC; *dp++ = ')'; *dp = '\0'; + dp = Xrestpos(ds, dp, st->base); + /* Must use st->var since calling + * global would break things + * like x[i+=1]. + */ + x.str = trimsub(str_val(st->var), + dp, st->stype); + if (x.str[0] != '\0' || st->quote) + type = XSUB; + else + type = XNULLSUB; + if (f&DOBLANK) + doblank++; + st = st->prev; + continue; + case '=': + /* Restore our position and substitute + * the value of st->var (may not be + * the assigned value in the presence + * of integer/right-adj/etc attributes). + */ + dp = Xrestpos(ds, dp, st->base); + /* Must use st->var since calling + * global would cause with things + * like x[i+=1] to be evaluated twice. + */ + /* Note: not exported by FEXPORT + * in at&t ksh. + */ + /* XXX POSIX says readonly is only + * fatal for special builtins (setstr + * does readonly check). + */ + len = strlen(dp) + 1; + setstr(st->var, + debunk(alloc(len, ATEMP), + dp, len), KSH_UNWIND_ERROR); + x.str = str_val(st->var); + type = XSUB; + if (f&DOBLANK) + doblank++; + st = st->prev; + continue; + case '?': + { + char *s = Xrestpos(ds, dp, st->base); + + errorf("%s: %s", st->var->name, + dp == s ? + "parameter null or not set" : + (debunk(s, s, strlen(s) + 1), s)); + } + } + st = st->prev; + type = XBASE; + continue; + + case OPAT: /* open pattern: *(foo|bar) */ + /* Next char is the type of pattern */ + make_magic = 1; + c = *sp++ + 0x80; + break; + + case SPAT: /* pattern separator (|) */ + make_magic = 1; + c = '|'; + break; + + case CPAT: /* close pattern */ + make_magic = 1; + c = /*(*/ ')'; + break; + } + break; + + case XNULLSUB: + /* Special case for "$@" (and "${foo[@]}") - no + * word is generated if $# is 0 (unless there is + * other stuff inside the quotes). + */ + type = XBASE; + if (f&DOBLANK) { + doblank--; + /* not really correct: x=; "$x$@" should + * generate a null argument and + * set A; "${@:+}" shouldn't. + */ + if (dp == Xstring(ds, dp)) + word = IFS_WS; + } + continue; + + case XSUB: + case XSUBMID: + if ((c = *x.str++) == 0) { + type = XBASE; + if (f&DOBLANK) + doblank--; + continue; + } + break; + + case XARGSEP: + type = XARG; + quote = 1; + case XARG: + if ((c = *x.str++) == '\0') { + /* force null words to be created so + * set -- '' 2 ''; foo "$@" will do + * the right thing + */ + if (quote && x.split) + word = IFS_WORD; + if ((x.str = *x.u.strv++) == NULL) { + type = XBASE; + if (f&DOBLANK) + doblank--; + continue; + } + c = ifs0; + if (c == 0) { + if (quote && !x.split) + continue; + c = ' '; + } + if (quote && x.split) { + /* terminate word for "$@" */ + type = XARGSEP; + quote = 0; + } + } + break; + + case XCOM: + if (x.u.shf == NULL) /* $(< ...) failed, fake EOF */ + c = EOF; + else if (newlines) { /* Spit out saved nl's */ + c = '\n'; + --newlines; + } else { + while ((c = shf_getc(x.u.shf)) == 0 || c == '\n') + if (c == '\n') + newlines++; /* Save newlines */ + if (newlines && c != EOF) { + shf_ungetc(c, x.u.shf); + c = '\n'; + --newlines; + } + } + if (c == EOF) { + newlines = 0; + if (x.u.shf != NULL) + shf_close(x.u.shf); + if (x.split) + subst_exstat = waitlast(); + else + subst_exstat = (x.u.shf == NULL); + type = XBASE; + if (f&DOBLANK) + doblank--; + continue; + } + break; + } + + /* check for end of word or IFS separation */ + if (c == 0 || (!quote && (f & DOBLANK) && doblank && + !make_magic && ctype(c, C_IFS))) { + /* How words are broken up: + * | value of c + * word | ws nws 0 + * ----------------------------------- + * IFS_WORD w/WS w/NWS w + * IFS_WS -/WS w/NWS - + * IFS_NWS -/NWS w/NWS w + * (w means generate a word) + * Note that IFS_NWS/0 generates a word (at&t ksh + * doesn't do this, but POSIX does). + */ + if (word == IFS_WORD || + (!ctype(c, C_IFSWS) && c && word == IFS_NWS)) { + char *p; + + *dp++ = '\0'; + p = Xclose(ds, dp); +#ifdef BRACE_EXPAND + if (fdo & DOBRACE_) + /* also does globbing */ + alt_expand(wp, p, p, + p + Xlength(ds, (dp - 1)), + fdo | (f & DOMARKDIRS)); + else +#endif /* BRACE_EXPAND */ + if (fdo & DOGLOB) + glob(p, wp, f & DOMARKDIRS); + else if ((f & DOPAT) || !(fdo & DOMAGIC_)) + XPput(*wp, p); + else + XPput(*wp, debunk(p, p, strlen(p) + 1)); + fdo = 0; + saw_eq = 0; + tilde_ok = (f & (DOTILDE|DOASNTILDE)) ? 1 : 0; + if (c != 0) + Xinit(ds, dp, 128, ATEMP); + } + if (c == 0) + return; + if (word != IFS_NWS) + word = ctype(c, C_IFSWS) ? IFS_WS : IFS_NWS; + } else { + if (type == XSUB) { + if (word == IFS_NWS && + Xlength(ds, dp) == 0) { + char *p; + + if ((p = strdup("")) == NULL) + internal_errorf(1, "unable " + "to allocate memory"); + XPput(*wp, p); + } + type = XSUBMID; + } + + /* age tilde_ok info - ~ code tests second bit */ + tilde_ok <<= 1; + /* mark any special second pass chars */ + if (!quote) + switch (c) { + case '[': + case '!': + case '-': + case ']': + /* For character classes - doesn't hurt + * to have magic !,-,]'s outside of + * [...] expressions. + */ + if (f & (DOPAT | DOGLOB)) { + fdo |= DOMAGIC_; + if (c == '[') + fdo |= f & DOGLOB; + *dp++ = MAGIC; + } + break; + case '*': + case '?': + if (f & (DOPAT | DOGLOB)) { + fdo |= DOMAGIC_ | (f & DOGLOB); + *dp++ = MAGIC; + } + break; +#ifdef BRACE_EXPAND + case OBRACE: + case ',': + case CBRACE: + if ((f & DOBRACE_) && (c == OBRACE || + (fdo & DOBRACE_))) { + fdo |= DOBRACE_|DOMAGIC_; + *dp++ = MAGIC; + } + break; +#endif /* BRACE_EXPAND */ + case '=': + /* Note first unquoted = for ~ */ + if (!(f & DOTEMP_) && !saw_eq) { + saw_eq = 1; + tilde_ok = 1; + } + break; + case ':': /* : */ + /* Note unquoted : for ~ */ + if (!(f & DOTEMP_) && (f & DOASNTILDE)) + tilde_ok = 1; + break; + case '~': + /* tilde_ok is reset whenever + * any of ' " $( $(( ${ } are seen. + * Note that tilde_ok must be preserved + * through the sequence ${A=a=}~ + */ + if (type == XBASE && + (f & (DOTILDE|DOASNTILDE)) && + (tilde_ok & 2)) { + char *p, *dp_x; + + dp_x = dp; + p = maybe_expand_tilde(sp, + &ds, &dp_x, + f & DOASNTILDE); + if (p) { + if (dp != dp_x) + word = IFS_WORD; + dp = dp_x; + sp = p; + continue; + } + } + break; + } + else + quote &= ~2; /* undo temporary */ + + if (make_magic) { + make_magic = 0; + fdo |= DOMAGIC_ | (f & DOGLOB); + *dp++ = MAGIC; + } else if (ISMAGIC(c)) { + fdo |= DOMAGIC_; + *dp++ = MAGIC; + } + *dp++ = c; /* save output char */ + word = IFS_WORD; + } + } +} + +/* + * Prepare to generate the string returned by ${} substitution. + */ +static int +varsub(Expand *xp, char *sp, char *word, + int *stypep, /* becomes qualifier type */ + int *slenp) /* " " len (=, :=, etc.) valid iff *stypep != 0 */ +{ + int c; + int state; /* next state: XBASE, XARG, XSUB, XNULLSUB */ + int stype; /* substitution type */ + int slen; + char *p; + struct tbl *vp; + int zero_ok = 0; + + if (sp[0] == '\0') /* Bad variable name */ + return -1; + + xp->var = NULL; + + /* ${#var}, string length or array size */ + if (sp[0] == '#' && (c = sp[1]) != '\0') { + /* Can't have any modifiers for ${#...} */ + if (*word != CSUBST) + return -1; + sp++; + /* Check for size of array */ + if ((p=strchr(sp,'[')) && (p[1]=='*'||p[1]=='@') && p[2]==']') { + int n = 0; + + vp = global(arrayname(sp)); + if (vp->flag & (ISSET|ARRAY)) + zero_ok = 1; + for (; vp; vp = vp->u.array) + if (vp->flag & ISSET) + n++; + c = n; /* ksh88/ksh93 go for number, not max index */ + } else if (c == '*' || c == '@') + c = genv->loc->argc; + else { + p = str_val(global(sp)); + zero_ok = p != null; + c = strlen(p); + } + if (Flag(FNOUNSET) && c == 0 && !zero_ok) + errorf("%s: parameter not set", sp); + *stypep = 0; /* unqualified variable/string substitution */ + xp->str = str_save(ulton((unsigned long)c, 10), ATEMP); + return XSUB; + } + + /* Check for qualifiers in word part */ + stype = 0; + c = word[slen = 0] == CHAR ? word[1] : 0; + if (c == ':') { + slen += 2; + stype = 0x80; + c = word[slen + 0] == CHAR ? word[slen + 1] : 0; + } + if (ctype(c, C_SUBOP1)) { + slen += 2; + stype |= c; + } else if (ctype(c, C_SUBOP2)) { /* Note: ksh88 allows :%, :%%, etc */ + slen += 2; + stype = c; + if (word[slen + 0] == CHAR && c == word[slen + 1]) { + stype |= 0x80; + slen += 2; + } + } else if (stype) /* : is not ok */ + return -1; + if (!stype && *word != CSUBST) + return -1; + *stypep = stype; + *slenp = slen; + + c = sp[0]; + if (c == '*' || c == '@') { + switch (stype & 0x7f) { + case '=': /* can't assign to a vector */ + case '%': /* can't trim a vector (yet) */ + case '#': + return -1; + } + if (genv->loc->argc == 0) { + xp->str = null; + xp->var = global(sp); + state = c == '@' ? XNULLSUB : XSUB; + } else { + xp->u.strv = (const char **) genv->loc->argv + 1; + xp->str = *xp->u.strv++; + xp->split = c == '@'; /* $@ */ + state = XARG; + } + zero_ok = 1; /* exempt "$@" and "$*" from 'set -u' */ + } else { + if ((p=strchr(sp,'[')) && (p[1]=='*'||p[1]=='@') && p[2]==']') { + XPtrV wv; + + switch (stype & 0x7f) { + case '=': /* can't assign to a vector */ + case '%': /* can't trim a vector (yet) */ + case '#': + case '?': + return -1; + } + XPinit(wv, 32); + vp = global(arrayname(sp)); + for (; vp; vp = vp->u.array) { + if (!(vp->flag&ISSET)) + continue; + XPput(wv, str_val(vp)); + } + if (XPsize(wv) == 0) { + xp->str = null; + state = p[1] == '@' ? XNULLSUB : XSUB; + XPfree(wv); + } else { + XPput(wv, 0); + xp->u.strv = (const char **) XPptrv(wv); + xp->str = *xp->u.strv++; + xp->split = p[1] == '@'; /* ${foo[@]} */ + state = XARG; + } + } else { + /* Can't assign things like $! or $1 */ + if ((stype & 0x7f) == '=' && + (ctype(*sp, C_VAR1) || digit(*sp))) + return -1; + xp->var = global(sp); + xp->str = str_val(xp->var); + state = XSUB; + } + } + + c = stype&0x7f; + /* test the compiler's code generator */ + if (ctype(c, C_SUBOP2) || + (((stype&0x80) ? *xp->str=='\0' : xp->str==null) ? /* undef? */ + c == '=' || c == '-' || c == '?' : c == '+')) + state = XBASE; /* expand word instead of variable value */ + if (Flag(FNOUNSET) && xp->str == null && !zero_ok && + (ctype(c, C_SUBOP2) || (state != XBASE && c != '+'))) + errorf("%s: parameter not set", sp); + return state; +} + +/* + * Run the command in $(...) and read its output. + */ +static int +comsub(Expand *xp, char *cp) +{ + Source *s, *sold; + struct op *t; + struct shf *shf; + + s = pushs(SSTRING, ATEMP); + s->start = s->str = cp; + sold = source; + t = compile(s); + afree(s, ATEMP); + source = sold; + + if (t == NULL) + return XBASE; + + if (t != NULL && t->type == TCOM && /* $(<file) */ + *t->args == NULL && *t->vars == NULL && t->ioact != NULL) { + struct ioword *io = *t->ioact; + char *name; + + if ((io->flag&IOTYPE) != IOREAD) + errorf("funny $() command: %s", + snptreef(NULL, 32, "%R", io)); + shf = shf_open(name = evalstr(io->name, DOTILDE), O_RDONLY, 0, + SHF_MAPHI|SHF_CLEXEC); + if (shf == NULL) + warningf(!Flag(FTALKING), + "%s: cannot open $(<) input", name); + xp->split = 0; /* no waitlast() */ + } else { + int ofd1, pv[2]; + openpipe(pv); + shf = shf_fdopen(pv[0], SHF_RD, NULL); + ofd1 = savefd(1); + if (pv[1] != 1) { + ksh_dup2(pv[1], 1, false); + close(pv[1]); + } + execute(t, XFORK|XXCOM|XPIPEO, NULL); + restfd(1, ofd1); + startlast(); + xp->split = 1; /* waitlast() */ + } + + xp->u.shf = shf; + return XCOM; +} + +/* + * perform #pattern and %pattern substitution in ${} + */ + +static char * +trimsub(char *str, char *pat, int how) +{ + char *end = strchr(str, 0); + char *p, c; + + switch (how&0xff) { /* UCHAR_MAX maybe? */ + case '#': /* shortest at beginning */ + for (p = str; p <= end; p++) { + c = *p; *p = '\0'; + if (gmatch(str, pat, false)) { + *p = c; + return p; + } + *p = c; + } + break; + case '#'|0x80: /* longest match at beginning */ + for (p = end; p >= str; p--) { + c = *p; *p = '\0'; + if (gmatch(str, pat, false)) { + *p = c; + return p; + } + *p = c; + } + break; + case '%': /* shortest match at end */ + for (p = end; p >= str; p--) { + if (gmatch(p, pat, false)) + return str_nsave(str, p - str, ATEMP); + } + break; + case '%'|0x80: /* longest match at end */ + for (p = str; p <= end; p++) { + if (gmatch(p, pat, false)) + return str_nsave(str, p - str, ATEMP); + } + break; + } + + return str; /* no match, return string */ +} + +/* + * glob + * Name derived from V6's /etc/glob, the program that expanded filenames. + */ + +/* XXX cp not const 'cause slashes are temporarily replaced with nulls... */ +static void +glob(char *cp, XPtrV *wp, int markdirs) +{ + int oldsize = XPsize(*wp); + + if (glob_str(cp, wp, markdirs) == 0) + XPput(*wp, debunk(cp, cp, strlen(cp) + 1)); + else + qsortp(XPptrv(*wp) + oldsize, (size_t)(XPsize(*wp) - oldsize), + xstrcmp); +} + +#define GF_NONE 0 +#define GF_EXCHECK BIT(0) /* do existence check on file */ +#define GF_GLOBBED BIT(1) /* some globbing has been done */ +#define GF_MARKDIR BIT(2) /* add trailing / to directories */ + +/* Apply file globbing to cp and store the matching files in wp. Returns + * the number of matches found. + */ +int +glob_str(char *cp, XPtrV *wp, int markdirs) +{ + int oldsize = XPsize(*wp); + XString xs; + char *xp; + + Xinit(xs, xp, 256, ATEMP); + globit(&xs, &xp, cp, wp, markdirs ? GF_MARKDIR : GF_NONE); + Xfree(xs, xp); + + return XPsize(*wp) - oldsize; +} + +static void +globit(XString *xs, /* dest string */ + char **xpp, /* ptr to dest end */ + char *sp, /* source path */ + XPtrV *wp, /* output list */ + int check) /* GF_* flags */ +{ + char *np; /* next source component */ + char *xp = *xpp; + char *se; + char odirsep; + + /* This to allow long expansions to be interrupted */ + intrcheck(); + + if (sp == NULL) { /* end of source path */ + /* We only need to check if the file exists if a pattern + * is followed by a non-pattern (eg, foo*x/bar; no check + * is needed for foo* since the match must exist) or if + * any patterns were expanded and the markdirs option is set. + * Symlinks make things a bit tricky... + */ + if ((check & GF_EXCHECK) || + ((check & GF_MARKDIR) && (check & GF_GLOBBED))) { +#define stat_check() (stat_done ? stat_done : \ + (stat_done = stat(Xstring(*xs, xp), &statb) < 0 \ + ? -1 : 1)) + struct stat lstatb, statb; + int stat_done = 0; /* -1: failed, 1 ok */ + + if (lstat(Xstring(*xs, xp), &lstatb) < 0) + return; + /* special case for systems which strip trailing + * slashes from regular files (eg, /etc/passwd/). + * SunOS 4.1.3 does this... + */ + if ((check & GF_EXCHECK) && xp > Xstring(*xs, xp) && + xp[-1] == '/' && !S_ISDIR(lstatb.st_mode) && + (!S_ISLNK(lstatb.st_mode) || + stat_check() < 0 || !S_ISDIR(statb.st_mode))) + return; + /* Possibly tack on a trailing / if there isn't already + * one and if the file is a directory or a symlink to a + * directory + */ + if (((check & GF_MARKDIR) && (check & GF_GLOBBED)) && + xp > Xstring(*xs, xp) && xp[-1] != '/' && + (S_ISDIR(lstatb.st_mode) || + (S_ISLNK(lstatb.st_mode) && stat_check() > 0 && + S_ISDIR(statb.st_mode)))) { + *xp++ = '/'; + *xp = '\0'; + } + } + XPput(*wp, str_nsave(Xstring(*xs, xp), Xlength(*xs, xp), ATEMP)); + return; + } + + if (xp > Xstring(*xs, xp)) + *xp++ = '/'; + while (*sp == '/') { + Xcheck(*xs, xp); + *xp++ = *sp++; + } + np = strchr(sp, '/'); + if (np != NULL) { + se = np; + odirsep = *np; /* don't assume '/', can be multiple kinds */ + *np++ = '\0'; + } else { + odirsep = '\0'; /* keep gcc quiet */ + se = sp + strlen(sp); + } + + + /* Check if sp needs globbing - done to avoid pattern checks for strings + * containing MAGIC characters, open ['s without the matching close ], + * etc. (otherwise opendir() will be called which may fail because the + * directory isn't readable - if no globbing is needed, only execute + * permission should be required (as per POSIX)). + */ + if (!has_globbing(sp, se)) { + XcheckN(*xs, xp, se - sp + 1); + debunk(xp, sp, Xnleft(*xs, xp)); + xp += strlen(xp); + *xpp = xp; + globit(xs, xpp, np, wp, check); + } else { + DIR *dirp; + struct dirent *d; + char *name; + int len; + int prefix_len; + + /* xp = *xpp; copy_non_glob() may have re-alloc'd xs */ + *xp = '\0'; + prefix_len = Xlength(*xs, xp); + dirp = opendir(prefix_len ? Xstring(*xs, xp) : "."); + if (dirp == NULL) + goto Nodir; + while ((d = readdir(dirp)) != NULL) { + name = d->d_name; + if (name[0] == '.' && + (name[1] == 0 || (name[1] == '.' && name[2] == 0))) + continue; /* always ignore . and .. */ + if ((*name == '.' && *sp != '.') || + !gmatch(name, sp, true)) + continue; + + len = strlen(d->d_name) + 1; + XcheckN(*xs, xp, len); + memcpy(xp, name, len); + *xpp = xp + len - 1; + globit(xs, xpp, np, wp, + (check & GF_MARKDIR) | GF_GLOBBED + | (np ? GF_EXCHECK : GF_NONE)); + xp = Xstring(*xs, xp) + prefix_len; + } + closedir(dirp); + Nodir:; + } + + if (np != NULL) + *--np = odirsep; +} + +#if 0 +/* Check if p contains something that needs globbing; if it does, 0 is + * returned; if not, p is copied into xs/xp after stripping any MAGICs + */ +static int copy_non_glob(XString *xs, char **xpp, char *p); +static int +copy_non_glob(XString *xs, char **xpp, char *p) +{ + char *xp; + int len = strlen(p); + + XcheckN(*xs, *xpp, len); + xp = *xpp; + for (; *p; p++) { + if (ISMAGIC(*p)) { + int c = *++p; + + if (c == '*' || c == '?') + return 0; + if (*p == '[') { + char *q = p + 1; + + if (ISMAGIC(*q) && q[1] == '!') + q += 2; + if (ISMAGIC(*q) && q[1] == ']') + q += 2; + for (; *q; q++) + if (ISMAGIC(*q) && *++q == ']') + return 0; + /* pass a literal [ through */ + } + /* must be a MAGIC-MAGIC, or MAGIC-!, MAGIC--, etc. */ + } + *xp++ = *p; + } + *xp = '\0'; + *xpp = xp; + return 1; +} +#endif /* 0 */ + +/* remove MAGIC from string */ +char * +debunk(char *dp, const char *sp, size_t dlen) +{ + char *d, *s; + + if ((s = strchr(sp, MAGIC))) { + if (s - sp >= dlen) + return dp; + memcpy(dp, sp, s - sp); + for (d = dp + (s - sp); *s && (d - dp < dlen); s++) + if (!ISMAGIC(*s) || !(*++s & 0x80) || + !strchr("*+?@! ", *s & 0x7f)) + *d++ = *s; + else { + /* extended pattern operators: *+?@! */ + if ((*s & 0x7f) != ' ') + *d++ = *s & 0x7f; + if (d - dp < dlen) + *d++ = '('; + } + *d = '\0'; + } else if (dp != sp) + strlcpy(dp, sp, dlen); + return dp; +} + +/* Check if p is an unquoted name, possibly followed by a / or :. If so + * puts the expanded version in *dcp,dp and returns a pointer in p just + * past the name, otherwise returns 0. + */ +static char * +maybe_expand_tilde(char *p, XString *dsp, char **dpp, int isassign) +{ + XString ts; + char *dp = *dpp; + char *tp, *r; + + Xinit(ts, tp, 16, ATEMP); + /* : only for DOASNTILDE form */ + while (p[0] == CHAR && p[1] != '/' && (!isassign || p[1] != ':')) + { + Xcheck(ts, tp); + *tp++ = p[1]; + p += 2; + } + *tp = '\0'; + r = (p[0] == EOS || p[0] == CHAR || p[0] == CSUBST) ? + tilde(Xstring(ts, tp)) : NULL; + Xfree(ts, tp); + if (r) { + while (*r) { + Xcheck(*dsp, dp); + if (ISMAGIC(*r)) + *dp++ = MAGIC; + *dp++ = *r++; + } + *dpp = dp; + r = p; + } + return r; +} + +/* + * tilde expansion + * + * based on a version by Arnold Robbins + */ + +static char * +tilde(char *cp) +{ + char *dp; + + if (cp[0] == '\0') + dp = str_val(global("HOME")); + else if (cp[0] == '+' && cp[1] == '\0') + dp = str_val(global("PWD")); + else if (cp[0] == '-' && cp[1] == '\0') + dp = str_val(global("OLDPWD")); + else + dp = homedir(cp); + /* If HOME, PWD or OLDPWD are not set, don't expand ~ */ + if (dp == null) + dp = NULL; + return dp; +} + +/* + * map userid to user's home directory. + * note that 4.3's getpw adds more than 6K to the shell, + * and the YP version probably adds much more. + * we might consider our own version of getpwnam() to keep the size down. + */ + +static char * +homedir(char *name) +{ + struct tbl *ap; + + ap = ktenter(&homedirs, name, hash(name)); + if (!(ap->flag & ISSET)) { + struct passwd *pw; + + pw = getpwnam(name); + if (pw == NULL) + return NULL; + ap->val.s = str_save(pw->pw_dir, APERM); + ap->flag |= DEFINED|ISSET|ALLOC; + } + return ap->val.s; +} + +#ifdef BRACE_EXPAND +static void +alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo) +{ + int count = 0; + char *brace_start, *brace_end, *comma = NULL; + char *field_start; + char *p; + + /* search for open brace */ + for (p = exp_start; (p = strchr(p, MAGIC)) && p[1] != OBRACE; p += 2) + ; + brace_start = p; + + /* find matching close brace, if any */ + if (p) { + comma = NULL; + count = 1; + for (p += 2; *p && count; p++) { + if (ISMAGIC(*p)) { + if (*++p == OBRACE) + count++; + else if (*p == CBRACE) + --count; + else if (*p == ',' && count == 1) + comma = p; + } + } + } + /* no valid expansions... */ + if (!p || count != 0) { + /* Note that given a{{b,c} we do not expand anything (this is + * what at&t ksh does. This may be changed to do the {b,c} + * expansion. } + */ + if (fdo & DOGLOB) + glob(start, wp, fdo & DOMARKDIRS); + else + XPput(*wp, debunk(start, start, end - start)); + return; + } + brace_end = p; + if (!comma) { + alt_expand(wp, start, brace_end, end, fdo); + return; + } + + /* expand expression */ + field_start = brace_start + 2; + count = 1; + for (p = brace_start + 2; p != brace_end; p++) { + if (ISMAGIC(*p)) { + if (*++p == OBRACE) + count++; + else if ((*p == CBRACE && --count == 0) || + (*p == ',' && count == 1)) { + char *new; + int l1, l2, l3; + + l1 = brace_start - start; + l2 = (p - 1) - field_start; + l3 = end - brace_end; + new = 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/ksh/exec.c b/bin/ksh/exec.c @@ -0,0 +1,1444 @@ +/* $OpenBSD: exec.c,v 1.64 2015/12/30 09:07:00 tedu Exp $ */ + +/* + * execute command tree + */ + +#include <sys/stat.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "sh.h" +#include "c_test.h" + +/* Does ps4 get parameter substitutions done? */ +# define PS4_SUBSTITUTE(s) substitute((s), 0) + +static int comexec(struct op *, struct tbl *volatile, char **, + int volatile, volatile int *); +static void scriptexec(struct op *, char **); +static int call_builtin(struct tbl *, char **); +static int iosetup(struct ioword *, struct tbl *); +static int herein(const char *, int); +static char *do_selectargs(char **, bool); +static int dbteste_isa(Test_env *, Test_meta); +static const char *dbteste_getopnd(Test_env *, Test_op, int); +static int dbteste_eval(Test_env *, Test_op, const char *, const char *, + int); +static void dbteste_error(Test_env *, int, const char *); + + +/* + * execute command tree + */ +int +execute(struct op *volatile t, + volatile int flags, volatile int *xerrok) /* if XEXEC don't fork */ +{ + int i, dummy = 0; + volatile int rv = 0; + int pv[2]; + char ** volatile ap; + char *s, *cp; + struct ioword **iowp; + struct tbl *tp = NULL; + + if (t == NULL) + return 0; + + /* Caller doesn't care if XERROK should propagate. */ + if (xerrok == NULL) + xerrok = &dummy; + + /* Is this the end of a pipeline? If so, we want to evaluate the + * command arguments + bool eval_done = false; + if ((flags&XFORK) && !(flags&XEXEC) && (flags&XPCLOSE)) { + eval_done = true; + tp = eval_execute_args(t, &ap); + } + */ + if ((flags&XFORK) && !(flags&XEXEC) && t->type != TPIPE) + return exchild(t, flags & ~XTIME, xerrok, -1); /* run in sub-process */ + + newenv(E_EXEC); + if (trap) + runtraps(0); + + if (t->type == TCOM) { + /* Clear subst_exstat before argument expansion. Used by + * null commands (see comexec() and c_eval()) and by c_set(). + */ + subst_exstat = 0; + + current_lineno = t->lineno; /* for $LINENO */ + + /* POSIX says expand command words first, then redirections, + * and assignments last.. + */ + ap = eval(t->args, t->u.evalflags | DOBLANK | DOGLOB | DOTILDE); + if (flags & XTIME) + /* Allow option parsing (bizarre, but POSIX) */ + timex_hook(t, &ap); + if (Flag(FXTRACE) && ap[0]) { + shf_fprintf(shl_out, "%s", + PS4_SUBSTITUTE(str_val(global("PS4")))); + for (i = 0; ap[i]; i++) + shf_fprintf(shl_out, "%s%s", ap[i], + ap[i + 1] ? " " : "\n"); + shf_flush(shl_out); + } + if (ap[0]) + tp = findcom(ap[0], FC_BI|FC_FUNC); + } + flags &= ~XTIME; + + if (t->ioact != NULL || t->type == TPIPE || t->type == TCOPROC) { + genv->savefd = areallocarray(NULL, NUFILE, sizeof(short), ATEMP); + /* initialize to not redirected */ + memset(genv->savefd, 0, NUFILE * sizeof(short)); + } + + /* do redirection, to be restored in quitenv() */ + if (t->ioact != NULL) + for (iowp = t->ioact; *iowp != NULL; iowp++) { + if (iosetup(*iowp, tp) < 0) { + exstat = rv = 1; + /* Redirection failures for special commands + * cause (non-interactive) shell to exit. + */ + if (tp && tp->type == CSHELL && + (tp->flag & SPEC_BI)) + errorf(NULL); + /* Deal with FERREXIT, quitenv(), etc. */ + goto Break; + } + } + + switch (t->type) { + case TCOM: + rv = comexec(t, tp, ap, flags, xerrok); + break; + + case TPAREN: + rv = execute(t->left, flags|XFORK, xerrok); + break; + + case TPIPE: + flags |= XFORK; + flags &= ~XEXEC; + genv->savefd[0] = savefd(0); + genv->savefd[1] = savefd(1); + while (t->type == TPIPE) { + openpipe(pv); + (void) ksh_dup2(pv[1], 1, false); /* stdout of curr */ + /* Let exchild() close pv[0] in child + * (if this isn't done, commands like + * (: ; cat /etc/termcap) | sleep 1 + * will hang forever). + */ + exchild(t->left, flags|XPIPEO|XCCLOSE, NULL, pv[0]); + (void) ksh_dup2(pv[0], 0, false); /* stdin of next */ + closepipe(pv); + flags |= XPIPEI; + t = t->right; + } + restfd(1, genv->savefd[1]); /* stdout of last */ + genv->savefd[1] = 0; /* no need to re-restore this */ + /* Let exchild() close 0 in parent, after fork, before wait */ + i = exchild(t, flags|XPCLOSE, xerrok, 0); + if (!(flags&XBGND) && !(flags&XXCOM)) + rv = i; + break; + + case TLIST: + while (t->type == TLIST) { + execute(t->left, flags & XERROK, NULL); + t = t->right; + } + rv = execute(t, flags & XERROK, xerrok); + break; + + case TCOPROC: + { + sigset_t omask; + + /* Block sigchild as we are using things changed in the + * signal handler + */ + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + genv->type = E_ERRH; + i = sigsetjmp(genv->jbuf, 0); + if (i) { + sigprocmask(SIG_SETMASK, &omask, NULL); + quitenv(NULL); + unwind(i); + /* NOTREACHED */ + } + /* Already have a (live) co-process? */ + if (coproc.job && coproc.write >= 0) + errorf("coprocess already exists"); + + /* Can we re-use the existing co-process pipe? */ + coproc_cleanup(true); + + /* do this before opening pipes, in case these fail */ + genv->savefd[0] = savefd(0); + genv->savefd[1] = savefd(1); + + openpipe(pv); + if (pv[0] != 0) { + ksh_dup2(pv[0], 0, false); + close(pv[0]); + } + coproc.write = pv[1]; + coproc.job = NULL; + + if (coproc.readw >= 0) + ksh_dup2(coproc.readw, 1, false); + else { + openpipe(pv); + coproc.read = pv[0]; + ksh_dup2(pv[1], 1, false); + coproc.readw = pv[1]; /* closed before first read */ + coproc.njobs = 0; + /* create new coprocess id */ + ++coproc.id; + } + sigprocmask(SIG_SETMASK, &omask, NULL); + genv->type = E_EXEC; /* no more need for error handler */ + + /* exchild() closes coproc.* in child after fork, + * will also increment coproc.njobs when the + * job is actually created. + */ + flags &= ~XEXEC; + exchild(t->left, flags|XBGND|XFORK|XCOPROC|XCCLOSE, + NULL, coproc.readw); + break; + } + + case TASYNC: + /* XXX non-optimal, I think - "(foo &)", forks for (), + * forks again for async... parent should optimize + * this to "foo &"... + */ + rv = execute(t->left, (flags&~XEXEC)|XBGND|XFORK, xerrok); + break; + + case TOR: + case TAND: + rv = execute(t->left, XERROK, xerrok); + if ((rv == 0) == (t->type == TAND)) + rv = execute(t->right, flags & XERROK, xerrok); + else { + flags |= XERROK; + if (xerrok) + *xerrok = 1; + } + break; + + case TBANG: + rv = !execute(t->right, XERROK, xerrok); + flags |= XERROK; + if (xerrok) + *xerrok = 1; + break; + + case TDBRACKET: + { + Test_env te; + + te.flags = TEF_DBRACKET; + te.pos.wp = t->args; + te.isa = dbteste_isa; + te.getopnd = dbteste_getopnd; + te.eval = dbteste_eval; + te.error = dbteste_error; + + rv = test_parse(&te); + break; + } + + case TFOR: + case TSELECT: + { + volatile bool is_first = true; + ap = (t->vars != NULL) ? eval(t->vars, DOBLANK|DOGLOB|DOTILDE) : + genv->loc->argv + 1; + genv->type = E_LOOP; + while (1) { + i = sigsetjmp(genv->jbuf, 0); + if (!i) + break; + if ((genv->flags&EF_BRKCONT_PASS) || + (i != LBREAK && i != LCONTIN)) { + quitenv(NULL); + unwind(i); + } else if (i == LBREAK) { + rv = 0; + goto Break; + } + } + rv = 0; /* in case of a continue */ + if (t->type == TFOR) { + while (*ap != NULL) { + setstr(global(t->str), *ap++, KSH_UNWIND_ERROR); + rv = execute(t->left, flags & XERROK, xerrok); + } + } else { /* TSELECT */ + for (;;) { + if (!(cp = do_selectargs(ap, is_first))) { + rv = 1; + break; + } + is_first = false; + setstr(global(t->str), cp, KSH_UNWIND_ERROR); + rv = execute(t->left, flags & XERROK, xerrok); + } + } + } + break; + + case TWHILE: + case TUNTIL: + genv->type = E_LOOP; + while (1) { + i = sigsetjmp(genv->jbuf, 0); + if (!i) + break; + if ((genv->flags&EF_BRKCONT_PASS) || + (i != LBREAK && i != LCONTIN)) { + quitenv(NULL); + unwind(i); + } else if (i == LBREAK) { + rv = 0; + goto Break; + } + } + rv = 0; /* in case of a continue */ + while ((execute(t->left, XERROK, NULL) == 0) == (t->type == TWHILE)) + rv = execute(t->right, flags & XERROK, xerrok); + break; + + case TIF: + case TELIF: + if (t->right == NULL) + break; /* should be error */ + rv = execute(t->left, XERROK, NULL) == 0 ? + execute(t->right->left, flags & XERROK, xerrok) : + execute(t->right->right, flags & XERROK, xerrok); + break; + + case TCASE: + cp = evalstr(t->str, DOTILDE); + for (t = t->left; t != NULL && t->type == TPAT; t = t->right) + for (ap = t->vars; *ap; ap++) + if ((s = evalstr(*ap, DOTILDE|DOPAT)) && + gmatch(cp, s, false)) + goto Found; + break; + Found: + rv = execute(t->left, flags & XERROK, xerrok); + break; + + case TBRACE: + rv = execute(t->left, flags & XERROK, xerrok); + break; + + case TFUNCT: + rv = define(t->str, t); + break; + + case TTIME: + /* Clear XEXEC so nested execute() call doesn't exit + * (allows "ls -l | time grep foo"). + */ + rv = timex(t, flags & ~XEXEC, xerrok); + break; + + case TEXEC: /* an eval'd TCOM */ + s = t->args[0]; + ap = makenv(); + restoresigs(); + cleanup_proc_env(); + execve(t->str, t->args, ap); + if (errno == ENOEXEC) + scriptexec(t, ap); + else + errorf("%s: %s", s, strerror(errno)); + } + Break: + exstat = rv; + + quitenv(NULL); /* restores IO */ + if ((flags&XEXEC)) + unwind(LEXIT); /* exit child */ + if (rv != 0 && !(flags & XERROK) && + (xerrok == NULL || !*xerrok)) { + trapsig(SIGERR_); + if (Flag(FERREXIT)) + unwind(LERROR); + } + return rv; +} + +/* + * execute simple command + */ + +static int +comexec(struct op *t, struct tbl *volatile tp, char **ap, volatile int flags, + volatile int *xerrok) +{ + int i; + volatile int rv = 0; + char *cp; + char **lastp; + static struct op texec; /* Must be static (XXX but why?) */ + int type_flags; + int keepasn_ok; + int fcflags = FC_BI|FC_FUNC|FC_PATH; + int bourne_function_call = 0; + + /* snag the last argument for $_ XXX not the same as at&t ksh, + * which only seems to set $_ after a newline (but not in + * functions/dot scripts, but in interactive and script) - + * perhaps save last arg here and set it in shell()?. + */ + if (!Flag(FSH) && Flag(FTALKING) && *(lastp = ap)) { + while (*++lastp) + ; + /* setstr() can't fail here */ + setstr(typeset("_", LOCAL, 0, INTEGER, 0), *--lastp, + KSH_RETURN_ERROR); + } + + /* Deal with the shell builtins builtin, exec and command since + * they can be followed by other commands. This must be done before + * we know if we should create a local block, which must be done + * before we can do a path search (in case the assignments change + * PATH). + * Odd cases: + * FOO=bar exec > /dev/null FOO is kept but not exported + * FOO=bar exec foobar FOO is exported + * FOO=bar command exec > /dev/null FOO is neither kept nor exported + * FOO=bar command FOO is neither kept nor exported + * PATH=... foobar use new PATH in foobar search + */ + keepasn_ok = 1; + while (tp && tp->type == CSHELL) { + fcflags = FC_BI|FC_FUNC|FC_PATH;/* undo effects of command */ + if (tp->val.f == c_builtin) { + if ((cp = *++ap) == NULL) { + tp = NULL; + break; + } + tp = findcom(cp, FC_BI); + if (tp == NULL) + errorf("builtin: %s: not a builtin", cp); + continue; + } else if (tp->val.f == c_exec) { + if (ap[1] == NULL) + break; + ap++; + flags |= XEXEC; + } else if (tp->val.f == c_command) { + int optc, saw_p = 0; + + /* Ugly dealing with options in two places (here and + * in c_command(), but such is life) + */ + ksh_getopt_reset(&builtin_opt, 0); + while ((optc = ksh_getopt(ap, &builtin_opt, ":p")) == 'p') + saw_p = 1; + if (optc != EOF) + break; /* command -vV or something */ + /* don't look for functions */ + fcflags = FC_BI|FC_PATH; + if (saw_p) { + if (Flag(FRESTRICTED)) { + warningf(true, + "command -p: restricted"); + rv = 1; + goto Leave; + } + fcflags |= FC_DEFPATH; + } + ap += builtin_opt.optind; + /* POSIX says special builtins lose their status + * if accessed using command. + */ + keepasn_ok = 0; + if (!ap[0]) { + /* ensure command with no args exits with 0 */ + subst_exstat = 0; + break; + } + } else + break; + tp = findcom(ap[0], fcflags & (FC_BI|FC_FUNC)); + } + if (keepasn_ok && (!ap[0] || (tp && (tp->flag & KEEPASN)))) + type_flags = 0; + else { + /* create new variable/function block */ + newblock(); + /* ksh functions don't keep assignments, POSIX functions do. */ + if (keepasn_ok && tp && tp->type == CFUNC && + !(tp->flag & FKSH)) { + bourne_function_call = 1; + type_flags = 0; + } else + type_flags = LOCAL|LOCAL_COPY|EXPORT; + } + if (Flag(FEXPORT)) + type_flags |= EXPORT; + for (i = 0; t->vars[i]; i++) { + cp = evalstr(t->vars[i], DOASNTILDE); + if (Flag(FXTRACE)) { + if (i == 0) + shf_fprintf(shl_out, "%s", + PS4_SUBSTITUTE(str_val(global("PS4")))); + shf_fprintf(shl_out, "%s%s", cp, + t->vars[i + 1] ? " " : "\n"); + if (!t->vars[i + 1]) + shf_flush(shl_out); + } + typeset(cp, type_flags, 0, 0, 0); + if (bourne_function_call && !(type_flags & EXPORT)) + typeset(cp, LOCAL|LOCAL_COPY|EXPORT, 0, 0, 0); + } + + if ((cp = *ap) == NULL) { + rv = subst_exstat; + goto Leave; + } else if (!tp) { + if (Flag(FRESTRICTED) && strchr(cp, '/')) { + warningf(true, "%s: restricted", cp); + rv = 1; + goto Leave; + } + tp = findcom(cp, fcflags); + } + + switch (tp->type) { + case CSHELL: /* shell built-in */ + rv = call_builtin(tp, ap); + break; + + case CFUNC: /* function call */ + { + volatile int old_xflag, old_inuse; + const char *volatile old_kshname; + + if (!(tp->flag & ISSET)) { + struct tbl *ftp; + + if (!tp->u.fpath) { + if (tp->u2.errno_) { + warningf(true, + "%s: can't find function " + "definition file - %s", + cp, strerror(tp->u2.errno_)); + rv = 126; + } else { + warningf(true, + "%s: can't find function " + "definition file", cp); + rv = 127; + } + break; + } + if (include(tp->u.fpath, 0, NULL, 0) < 0) { + warningf(true, + "%s: can't open function definition file %s - %s", + cp, tp->u.fpath, strerror(errno)); + rv = 127; + break; + } + if (!(ftp = findfunc(cp, hash(cp), false)) || + !(ftp->flag & ISSET)) { + warningf(true, + "%s: function not defined by %s", + cp, tp->u.fpath); + rv = 127; + break; + } + tp = ftp; + } + + /* ksh functions set $0 to function name, POSIX functions leave + * $0 unchanged. + */ + old_kshname = kshname; + if (tp->flag & FKSH) + kshname = ap[0]; + else + ap[0] = (char *) kshname; + genv->loc->argv = ap; + for (i = 0; *ap++ != NULL; i++) + ; + genv->loc->argc = i - 1; + /* ksh-style functions handle getopts sanely, + * bourne/posix functions are insane... + */ + if (tp->flag & FKSH) { + genv->loc->flags |= BF_DOGETOPTS; + genv->loc->getopts_state = user_opt; + getopts_reset(1); + } + + old_xflag = Flag(FXTRACE); + Flag(FXTRACE) = tp->flag & TRACE ? true : false; + + old_inuse = tp->flag & FINUSE; + tp->flag |= FINUSE; + + genv->type = E_FUNC; + i = sigsetjmp(genv->jbuf, 0); + if (i == 0) { + /* seems odd to pass XERROK here, but at&t ksh does */ + exstat = execute(tp->val.t, flags & XERROK, xerrok); + i = LRETURN; + } + kshname = old_kshname; + Flag(FXTRACE) = old_xflag; + tp->flag = (tp->flag & ~FINUSE) | old_inuse; + /* Were we deleted while executing? If so, free the execution + * tree. todo: Unfortunately, the table entry is never re-used + * until the lookup table is expanded. + */ + if ((tp->flag & (FDELETE|FINUSE)) == FDELETE) { + if (tp->flag & ALLOC) { + tp->flag &= ~ALLOC; + tfree(tp->val.t, tp->areap); + } + tp->flag = 0; + } + switch (i) { + case LRETURN: + case LERROR: + rv = exstat; + break; + case LINTR: + case LEXIT: + case LLEAVE: + case LSHELL: + quitenv(NULL); + unwind(i); + /* NOTREACHED */ + default: + quitenv(NULL); + internal_errorf(1, "CFUNC %d", i); + } + break; + } + + case CEXEC: /* executable command */ + case CTALIAS: /* tracked alias */ + if (!(tp->flag&ISSET)) { + /* errno_ will be set if the named command was found + * but could not be executed (permissions, no execute + * bit, directory, etc). Print out a (hopefully) + * useful error message and set the exit status to 126. + */ + if (tp->u2.errno_) { + warningf(true, "%s: cannot execute - %s", cp, + strerror(tp->u2.errno_)); + rv = 126; /* POSIX */ + } else { + warningf(true, "%s: not found", cp); + rv = 127; + } + break; + } + + if (!Flag(FSH)) { + /* set $_ to program's full path */ + /* setstr() can't fail here */ + setstr(typeset("_", LOCAL|EXPORT, 0, INTEGER, 0), + tp->val.s, KSH_RETURN_ERROR); + } + + if (flags&XEXEC) { + j_exit(); + if (!(flags&XBGND) || Flag(FMONITOR)) { + setexecsig(&sigtraps[SIGINT], SS_RESTORE_ORIG); + setexecsig(&sigtraps[SIGQUIT], SS_RESTORE_ORIG); + } + } + + /* to fork we set up a TEXEC node and call execute */ + texec.type = TEXEC; + texec.left = t; /* for tprint */ + texec.str = tp->val.s; + texec.args = ap; + rv = exchild(&texec, flags, xerrok, -1); + break; + } + Leave: + if (flags & XEXEC) { + exstat = rv; + unwind(LLEAVE); + } + return rv; +} + +static void +scriptexec(struct op *tp, char **ap) +{ + char *shell; + + shell = str_val(global("EXECSHELL")); + if (shell && *shell) + shell = search(shell, path, X_OK, NULL); + if (!shell || !*shell) + shell = _PATH_BSHELL; + + *tp->args-- = tp->str; + *tp->args = shell; + + execve(tp->args[0], tp->args, ap); + + /* report both the program that was run and the bogus shell */ + errorf("%s: %s: %s", tp->str, shell, strerror(errno)); +} + +int +shcomexec(char **wp) +{ + struct tbl *tp; + + tp = ktsearch(&builtins, *wp, hash(*wp)); + if (tp == NULL) + internal_errorf(1, "shcomexec: %s", *wp); + return call_builtin(tp, wp); +} + +/* + * Search function tables for a function. If create set, a table entry + * is created if none is found. + */ +struct tbl * +findfunc(const char *name, unsigned int h, int create) +{ + struct block *l; + struct tbl *tp = NULL; + + for (l = genv->loc; l; l = l->next) { + tp = ktsearch(&l->funs, name, h); + if (tp) + break; + if (!l->next && create) { + tp = ktenter(&l->funs, name, h); + tp->flag = DEFINED; + tp->type = CFUNC; + tp->val.t = NULL; + break; + } + } + return tp; +} + +/* + * define function. Returns 1 if function is being undefined (t == 0) and + * function did not exist, returns 0 otherwise. + */ +int +define(const char *name, struct op *t) +{ + struct tbl *tp; + int was_set = 0; + + while (1) { + tp = findfunc(name, hash(name), true); + + if (tp->flag & ISSET) + was_set = 1; + /* If this function is currently being executed, we zap this + * table entry so findfunc() won't see it + */ + if (tp->flag & FINUSE) { + tp->name[0] = '\0'; + tp->flag &= ~DEFINED; /* ensure it won't be found */ + tp->flag |= FDELETE; + } else + break; + } + + if (tp->flag & ALLOC) { + tp->flag &= ~(ISSET|ALLOC); + tfree(tp->val.t, tp->areap); + } + + if (t == NULL) { /* undefine */ + ktdelete(tp); + return was_set ? 0 : 1; + } + + tp->val.t = tcopy(t->left, tp->areap); + tp->flag |= (ISSET|ALLOC); + if (t->u.ksh_func) + tp->flag |= FKSH; + + return 0; +} + +/* + * add builtin + */ +void +builtin(const char *name, int (*func) (char **)) +{ + struct tbl *tp; + 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 = ktenter(&builtins, name, hash(name)); + tp->flag = DEFINED | flag; + tp->type = CSHELL; + tp->val.f = func; +} + +/* + * find command + * either function, hashed command, or built-in (in that order) + */ +struct tbl * +findcom(const char *name, int flags) +{ + static struct tbl temp; + unsigned int h = hash(name); + struct tbl *tp = NULL, *tbi; + int insert = Flag(FTRACKALL); /* insert if not found */ + char *fpath; /* for function autoloading */ + char *npath; + + if (strchr(name, '/') != NULL) { + insert = 0; + /* prevent FPATH search below */ + flags &= ~FC_FUNC; + goto Search; + } + tbi = (flags & FC_BI) ? ktsearch(&builtins, name, h) : NULL; + /* POSIX says special builtins first, then functions, then + * POSIX regular builtins, then search path... + */ + if ((flags & FC_SPECBI) && tbi && (tbi->flag & SPEC_BI)) + tp = tbi; + if (!tp && (flags & FC_FUNC)) { + tp = findfunc(name, h, false); + if (tp && !(tp->flag & ISSET)) { + if ((fpath = str_val(global("FPATH"))) == null) { + tp->u.fpath = NULL; + tp->u2.errno_ = 0; + } else + tp->u.fpath = search(name, fpath, R_OK, + &tp->u2.errno_); + } + } + if (!tp && (flags & FC_REGBI) && tbi && (tbi->flag & REG_BI)) + tp = tbi; + /* todo: posix says non-special/non-regular builtins must + * be triggered by some user-controllable means like a + * special directory in PATH. Requires modifications to + * the search() function. Tracked aliases should be + * modified to allow tracking of builtin commands. + * This should be under control of the FPOSIX flag. + * If this is changed, also change c_whence... + */ + if (!tp && (flags & FC_UNREGBI) && tbi) + tp = tbi; + if (!tp && (flags & FC_PATH) && !(flags & FC_DEFPATH)) { + tp = ktsearch(&taliases, name, h); + if (tp && (tp->flag & ISSET) && access(tp->val.s, X_OK) != 0) { + if (tp->flag & ALLOC) { + tp->flag &= ~ALLOC; + afree(tp->val.s, APERM); + } + tp->flag &= ~ISSET; + } + } + + Search: + if ((!tp || (tp->type == CTALIAS && !(tp->flag&ISSET))) && + (flags & FC_PATH)) { + if (!tp) { + if (insert && !(flags & FC_DEFPATH)) { + tp = ktenter(&taliases, name, h); + tp->type = CTALIAS; + } else { + tp = &temp; + tp->type = CEXEC; + } + tp->flag = DEFINED; /* make ~ISSET */ + } + npath = search(name, flags & FC_DEFPATH ? def_path : path, + X_OK, &tp->u2.errno_); + if (npath) { + if (tp == &temp) { + tp->val.s = npath; + } else { + tp->val.s = str_save(npath, APERM); + if (npath != name) + afree(npath, ATEMP); + } + tp->flag |= ISSET|ALLOC; + } else if ((flags & FC_FUNC) && + (fpath = str_val(global("FPATH"))) != null && + (npath = search(name, fpath, R_OK, + &tp->u2.errno_)) != NULL) { + /* An undocumented feature of at&t ksh is that it + * searches FPATH if a command is not found, even + * if the command hasn't been set up as an autoloaded + * function (ie, no typeset -uf). + */ + tp = &temp; + tp->type = CFUNC; + tp->flag = DEFINED; /* make ~ISSET */ + tp->u.fpath = npath; + } + } + return tp; +} + +/* + * flush executable commands with relative paths + */ +void +flushcom(int all) /* just relative or all */ +{ + struct tbl *tp; + struct tstate ts; + + for (ktwalk(&ts, &taliases); (tp = ktnext(&ts)) != NULL; ) + if ((tp->flag&ISSET) && (all || tp->val.s[0] != '/')) { + if (tp->flag&ALLOC) { + tp->flag &= ~(ALLOC|ISSET); + afree(tp->val.s, APERM); + } + tp->flag &= ~ISSET; + } +} + +/* Check if path is something we want to find. Returns -1 for failure. */ +int +search_access(const char *path, int mode, + int *errnop) /* set if candidate found, but not suitable */ +{ + int ret, err = 0; + struct stat statb; + + if (stat(path, &statb) < 0) + return -1; + ret = access(path, mode); + if (ret < 0) + err = errno; /* File exists, but we can't access it */ + else if (mode == X_OK && (!S_ISREG(statb.st_mode) || + !(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) { + /* This 'cause access() says root can execute everything */ + ret = -1; + err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES; + } + if (err && errnop && !*errnop) + *errnop = err; + return ret; +} + +/* + * search for command with PATH + */ +char * +search(const char *name, const char *path, + int mode, /* R_OK or X_OK */ + int *errnop) /* set if candidate found, but not suitable */ +{ + const char *sp, *p; + char *xp; + XString xs; + int namelen; + + if (errnop) + *errnop = 0; + if (strchr(name, '/')) { + if (search_access(name, mode, errnop) == 0) + return (char *) name; + return NULL; + } + + namelen = strlen(name) + 1; + Xinit(xs, xp, 128, ATEMP); + + sp = path; + while (sp != NULL) { + xp = Xstring(xs, xp); + if (!(p = strchr(sp, ':'))) + p = sp + strlen(sp); + if (p != sp) { + XcheckN(xs, xp, p - sp); + memcpy(xp, sp, p - sp); + xp += p - sp; + *xp++ = '/'; + } + sp = p; + XcheckN(xs, xp, namelen); + memcpy(xp, name, namelen); + if (search_access(Xstring(xs, xp), mode, errnop) == 0) + return Xclose(xs, xp + namelen); + if (*sp++ == '\0') + sp = NULL; + } + Xfree(xs, xp); + return NULL; +} + +static int +call_builtin(struct tbl *tp, char **wp) +{ + int rv; + + builtin_argv0 = wp[0]; + builtin_flag = tp->flag; + shf_reopen(1, SHF_WR, shl_stdout); + shl_stdout_ok = 1; + ksh_getopt_reset(&builtin_opt, GF_ERROR); + rv = (*tp->val.f)(wp); + shf_flush(shl_stdout); + shl_stdout_ok = 0; + builtin_flag = 0; + builtin_argv0 = NULL; + return rv; +} + +/* + * set up redirection, saving old fd's in e->savefd + */ +static int +iosetup(struct ioword *iop, struct tbl *tp) +{ + int u = -1; + char *cp = iop->name; + int iotype = iop->flag & IOTYPE; + int do_open = 1, do_close = 0, flags = 0; + struct ioword iotmp; + struct stat statb; + + if (iotype != IOHERE) + cp = evalonestr(cp, DOTILDE|(Flag(FTALKING_I) ? DOGLOB : 0)); + + /* Used for tracing and error messages to print expanded cp */ + iotmp = *iop; + iotmp.name = (iotype == IOHERE) ? NULL : cp; + iotmp.flag |= IONAMEXP; + + if (Flag(FXTRACE)) + shellf("%s%s\n", + PS4_SUBSTITUTE(str_val(global("PS4"))), + snptreef(NULL, 32, "%R", &iotmp)); + + switch (iotype) { + case IOREAD: + flags = O_RDONLY; + break; + + case IOCAT: + flags = O_WRONLY | O_APPEND | O_CREAT; + break; + + case IOWRITE: + flags = O_WRONLY | O_CREAT | O_TRUNC; + /* The stat() is here to allow redirections to + * things like /dev/null without error. + */ + if (Flag(FNOCLOBBER) && !(iop->flag & IOCLOB) && + (stat(cp, &statb) < 0 || S_ISREG(statb.st_mode))) + flags |= O_EXCL; + break; + + case IORDWR: + flags = O_RDWR | O_CREAT; + break; + + case IOHERE: + do_open = 0; + /* herein() returns -2 if error has been printed */ + u = herein(iop->heredoc, iop->flag & IOEVAL); + /* cp may have wrong name */ + break; + + case IODUP: + { + const char *emsg; + + do_open = 0; + if (*cp == '-' && !cp[1]) { + u = 1009; /* prevent error return below */ + do_close = 1; + } else if ((u = check_fd(cp, + X_OK | ((iop->flag & IORDUP) ? R_OK : W_OK), + &emsg)) < 0) { + warningf(true, "%s: %s", + snptreef(NULL, 32, "%R", &iotmp), emsg); + return -1; + } + if (u == iop->unit) + return 0; /* "dup from" == "dup to" */ + break; + } + } + + if (do_open) { + if (Flag(FRESTRICTED) && (flags & O_CREAT)) { + warningf(true, "%s: restricted", cp); + return -1; + } + u = open(cp, flags, 0666); + } + if (u < 0) { + /* herein() may already have printed message */ + if (u == -1) + warningf(true, "cannot %s %s: %s", + iotype == IODUP ? "dup" : + (iotype == IOREAD || iotype == IOHERE) ? + "open" : "create", cp, strerror(errno)); + return -1; + } + /* Do not save if it has already been redirected (i.e. "cat >x >y"). */ + if (genv->savefd[iop->unit] == 0) { + /* If these are the same, it means unit was previously closed */ + if (u == iop->unit) + genv->savefd[iop->unit] = -1; + else + /* c_exec() assumes e->savefd[fd] set for any + * redirections. Ask savefd() not to close iop->unit; + * this allows error messages to be seen if iop->unit + * is 2; also means we can't lose the fd (eg, both + * dup2 below and dup2 in restfd() failing). + */ + genv->savefd[iop->unit] = savefd(iop->unit); + } + + if (do_close) + close(iop->unit); + else if (u != iop->unit) { + if (ksh_dup2(u, iop->unit, true) < 0) { + warningf(true, + "could not finish (dup) redirection %s: %s", + snptreef(NULL, 32, "%R", &iotmp), + strerror(errno)); + if (iotype != IODUP) + close(u); + return -1; + } + if (iotype != IODUP) + close(u); + /* Touching any co-process fd in an empty exec + * causes the shell to close its copies + */ + else if (tp && tp->type == CSHELL && tp->val.f == c_exec) { + if (iop->flag & IORDUP) /* possible exec <&p */ + coproc_read_close(u); + else /* possible exec >&p */ + coproc_write_close(u); + } + } + if (u == 2) /* Clear any write errors */ + shf_reopen(2, SHF_WR, shl_out); + return 0; +} + +/* + * open here document temp file. + * if unquoted here, expand here temp file into second temp file. + */ +static int +herein(const char *content, int sub) +{ + volatile int fd = -1; + struct source *s, *volatile osource; + struct shf *volatile shf; + struct temp *h; + int i; + + /* ksh -c 'cat << EOF' can cause this... */ + if (content == NULL) { + warningf(true, "here document missing"); + return -2; /* special to iosetup(): don't print error */ + } + + /* Create temp file to hold content (done before newenv so temp + * doesn't get removed too soon). + */ + h = maketemp(ATEMP, TT_HEREDOC_EXP, &genv->temps); + if (!(shf = h->shf) || (fd = open(h->name, O_RDONLY, 0)) < 0) { + warningf(true, "can't %s temporary file %s: %s", + !shf ? "create" : "open", + h->name, strerror(errno)); + if (shf) + shf_close(shf); + return -2 /* special to iosetup(): don't print error */; + } + + osource = source; + newenv(E_ERRH); + i = sigsetjmp(genv->jbuf, 0); + if (i) { + source = osource; + quitenv(shf); + close(fd); + return -2; /* special to iosetup(): don't print error */ + } + if (sub) { + /* Do substitutions on the content of heredoc */ + s = pushs(SSTRING, ATEMP); + s->start = s->str = content; + source = s; + if (yylex(ONEWORD|HEREDOC) != LWORD) + internal_errorf(1, "herein: yylex"); + source = osource; + shf_puts(evalstr(yylval.cp, 0), shf); + } else + shf_puts(content, shf); + + quitenv(NULL); + + if (shf_close(shf) == EOF) { + close(fd); + warningf(true, "error writing %s: %s", h->name, + strerror(errno)); + return -2; /* special to iosetup(): don't print error */ + } + + return fd; +} + +#ifdef EDIT +/* + * ksh special - the select command processing section + * print the args in column form - assuming that we can + */ +static char * +do_selectargs(char **ap, bool print_menu) +{ + static const char *const read_args[] = { + "read", "-r", "REPLY", NULL + }; + const char *errstr; + char *s; + int i, argct; + + for (argct = 0; ap[argct]; argct++) + ; + while (1) { + /* Menu is printed if + * - this is the first time around the select loop + * - the user enters a blank line + * - the REPLY parameter is empty + */ + if (print_menu || !*str_val(global("REPLY"))) + pr_menu(ap); + shellf("%s", str_val(global("PS3"))); + if (call_builtin(findcom("read", FC_BI), (char **) read_args)) + return NULL; + s = str_val(global("REPLY")); + if (*s) { + i = strtonum(s, 1, argct, &errstr); + if (errstr) + return null; + return ap[i - 1]; + } + print_menu = 1; + } +} + +struct select_menu_info { + char *const *args; + int arg_width; + int num_width; +}; + +static char *select_fmt_entry(void *arg, int i, char *buf, int buflen); + +/* format a single select menu item */ +static char * +select_fmt_entry(void *arg, int i, char *buf, int buflen) +{ + struct select_menu_info *smi = (struct select_menu_info *) arg; + + shf_snprintf(buf, buflen, "%*d) %s", + smi->num_width, i + 1, smi->args[i]); + return buf; +} + +/* + * print a select style menu + */ +int +pr_menu(char *const *ap) +{ + struct select_menu_info smi; + char *const *pp; + int nwidth, dwidth; + int i, n; + + /* Width/column calculations were done once and saved, but this + * means select can't be used recursively so we re-calculate each + * time (could save in a structure that is returned, but its probably + * not worth the bother). + */ + + /* + * get dimensions of the list + */ + for (n = 0, nwidth = 0, pp = ap; *pp; n++, pp++) { + i = strlen(*pp); + nwidth = (i > nwidth) ? i : nwidth; + } + /* + * we will print an index of the form + * %d) + * in front of each entry + * get the max width of this + */ + for (i = n, dwidth = 1; i >= 10; i /= 10) + dwidth++; + + smi.args = ap; + smi.arg_width = nwidth; + smi.num_width = dwidth; + print_columns(shl_out, n, select_fmt_entry, (void *) &smi, + dwidth + nwidth + 2, 1); + + return n; +} + +/* XXX: horrible kludge to fit within the framework */ + +static char *plain_fmt_entry(void *arg, int i, char *buf, int buflen); + +static char * +plain_fmt_entry(void *arg, int i, char *buf, int buflen) +{ + shf_snprintf(buf, buflen, "%s", ((char *const *)arg)[i]); + return buf; +} + +int +pr_list(char *const *ap) +{ + char *const *pp; + int nwidth; + int i, n; + + for (n = 0, nwidth = 0, pp = ap; *pp; n++, pp++) { + i = strlen(*pp); + nwidth = (i > nwidth) ? i : nwidth; + } + print_columns(shl_out, n, plain_fmt_entry, (void *) ap, nwidth + 1, 0); + + return n; +} +#endif /* EDIT */ + +/* + * [[ ... ]] evaluation routines + */ + +extern const char *const dbtest_tokens[]; +extern const char db_close[]; + +/* Test if the current token is a whatever. Accepts the current token if + * it is. Returns 0 if it is not, non-zero if it is (in the case of + * TM_UNOP and TM_BINOP, the returned value is a Test_op). + */ +static int +dbteste_isa(Test_env *te, Test_meta meta) +{ + int ret = 0; + int uqword; + char *p; + + if (!*te->pos.wp) + return meta == TM_END; + + /* unquoted word? */ + for (p = *te->pos.wp; *p == CHAR; p += 2) + ; + uqword = *p == EOS; + + if (meta == TM_UNOP || meta == TM_BINOP) { + if (uqword) { + char buf[8]; /* longer than the longest operator */ + char *q = buf; + for (p = *te->pos.wp; + *p == CHAR && q < &buf[sizeof(buf) - 1]; p += 2) + *q++ = p[1]; + *q = '\0'; + ret = (int) test_isop(te, meta, buf); + } + } else if (meta == TM_END) + ret = 0; + else + ret = uqword && + strcmp(*te->pos.wp, dbtest_tokens[(int) meta]) == 0; + + /* Accept the token? */ + if (ret) + te->pos.wp++; + + return ret; +} + +static const char * +dbteste_getopnd(Test_env *te, Test_op op, int do_eval) +{ + char *s = *te->pos.wp; + + if (!s) + return NULL; + + te->pos.wp++; + + if (!do_eval) + return null; + + if (op == TO_STEQL || op == TO_STNEQ) + s = evalstr(s, DOTILDE | DOPAT); + else + s = evalstr(s, DOTILDE); + + return s; +} + +static int +dbteste_eval(Test_env *te, Test_op op, const char *opnd1, const char *opnd2, + int do_eval) +{ + return test_eval(te, op, opnd1, opnd2, do_eval); +} + +static void +dbteste_error(Test_env *te, int offset, const char *msg) +{ + te->flags |= TEF_ERROR; + internal_errorf(0, "dbteste_error: %s (offset %d)", msg, offset); +} diff --git a/bin/ksh/expand.h b/bin/ksh/expand.h @@ -0,0 +1,106 @@ +/* $OpenBSD: expand.h,v 1.12 2015/11/08 17:52:43 mmcc Exp $ */ + +/* + * Expanding strings + */ + +#define X_EXTRA 8 /* this many extra bytes in X string */ + +#if 0 /* Usage */ + XString xs; + char *xp; + + Xinit(xs, xp, 128, ATEMP); /* allocate initial string */ + while ((c = generate()) { + Xcheck(xs, xp); /* expand string if necessary */ + Xput(xs, xp, c); /* add character */ + } + return Xclose(xs, xp); /* resize string */ +/* + * NOTE: + * The Xcheck and Xinit macros have a magic + X_EXTRA in the lengths. + * This is so that you can put up to X_EXTRA characters in a XString + * before calling Xcheck. (See yylex in lex.c) + */ +#endif /* 0 */ + +typedef struct XString { + char *end, *beg; /* end, begin of string */ + size_t len; /* length */ + Area *areap; /* area to allocate/free from */ +} XString; + +typedef char * XStringP; + +/* initialize expandable string */ +#define Xinit(xs, xp, length, area) do { \ + (xs).len = length; \ + (xs).areap = (area); \ + (xs).beg = alloc((xs).len + X_EXTRA, (xs).areap); \ + (xs).end = (xs).beg + (xs).len; \ + xp = (xs).beg; \ + } while (0) + +/* stuff char into string */ +#define Xput(xs, xp, c) (*xp++ = (c)) + +/* check if there are at least n bytes left */ +#define XcheckN(xs, xp, n) do { \ + int more = ((xp) + (n)) - (xs).end; \ + if (more > 0) \ + xp = Xcheck_grow_(&xs, xp, more); \ + } while (0) + +/* check for overflow, expand string */ +#define Xcheck(xs, xp) XcheckN(xs, xp, 1) + +/* free string */ +#define Xfree(xs, xp) afree((xs).beg, (xs).areap) + +/* close, return string */ +#define Xclose(xs, xp) aresize((xs).beg, ((xp) - (xs).beg), (xs).areap) +/* begin of string */ +#define Xstring(xs, xp) ((xs).beg) + +#define Xnleft(xs, xp) ((xs).end - (xp)) /* may be less than 0 */ +#define Xlength(xs, xp) ((xp) - (xs).beg) +#define Xsize(xs, xp) ((xs).end - (xs).beg) +#define Xsavepos(xs, xp) ((xp) - (xs).beg) +#define Xrestpos(xs, xp, n) ((xs).beg + (n)) + +char * Xcheck_grow_(XString *xsp, char *xp, int more); + +/* + * expandable vector of generic pointers + */ + +typedef struct XPtrV { + void **cur; /* next avail pointer */ + void **beg, **end; /* begin, end of vector */ +} XPtrV; + +#define XPinit(x, n) do { \ + void **vp__; \ + vp__ = areallocarray(NULL, n, sizeof(void *), 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 = areallocarray((x).beg, n, \ + 2 * sizeof(void *), 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) areallocarray((x).beg, XPsize(x), \ + sizeof(void *), ATEMP) + +#define XPfree(x) afree((x).beg, ATEMP) diff --git a/bin/ksh/expr.c b/bin/ksh/expr.c @@ -0,0 +1,596 @@ +/* $OpenBSD: expr.c,v 1.32 2015/12/30 09:07:00 tedu Exp $ */ + +/* + * Korn expression evaluation + */ +/* + * todo: better error handling: if in builtin, should be builtin error, etc. + */ + +#include <ctype.h> +#include <limits.h> +#include <string.h> + +#include "sh.h" + +/* The order of these enums is constrained by the order of opinfo[] */ +enum token { + /* some (long) unary operators */ + O_PLUSPLUS = 0, O_MINUSMINUS, + /* binary operators */ + O_EQ, O_NE, + /* assignments are assumed to be in range O_ASN .. O_BORASN */ + O_ASN, O_TIMESASN, O_DIVASN, O_MODASN, O_PLUSASN, O_MINUSASN, + O_LSHIFTASN, O_RSHIFTASN, O_BANDASN, O_BXORASN, O_BORASN, + O_LSHIFT, O_RSHIFT, + O_LE, O_GE, O_LT, O_GT, + O_LAND, + O_LOR, + O_TIMES, O_DIV, O_MOD, + O_PLUS, O_MINUS, + O_BAND, + O_BXOR, + O_BOR, + O_TERN, + O_COMMA, + /* things after this aren't used as binary operators */ + /* unary that are not also binaries */ + O_BNOT, O_LNOT, + /* misc */ + OPEN_PAREN, CLOSE_PAREN, CTERN, + /* things that don't appear in the opinfo[] table */ + VAR, LIT, END, BAD +}; +#define IS_BINOP(op) (((int)op) >= (int)O_EQ && ((int)op) <= (int)O_COMMA) +#define IS_ASSIGNOP(op) ((int)(op) >= (int)O_ASN && (int)(op) <= (int)O_BORASN) + +enum prec { + P_PRIMARY = 0, /* VAR, LIT, (), ~ ! - + */ + P_MULT, /* * / % */ + P_ADD, /* + - */ + P_SHIFT, /* << >> */ + P_RELATION, /* < <= > >= */ + P_EQUALITY, /* == != */ + P_BAND, /* & */ + P_BXOR, /* ^ */ + P_BOR, /* | */ + P_LAND, /* && */ + P_LOR, /* || */ + P_TERN, /* ?: */ + P_ASSIGN, /* = *= /= %= += -= <<= >>= &= ^= |= */ + P_COMMA /* , */ +}; +#define MAX_PREC P_COMMA + +struct opinfo { + char name[4]; + int len; /* name length */ + enum prec prec; /* precedence: lower is higher */ +}; + +/* Tokens in this table must be ordered so the longest are first + * (eg, += before +). If you change something, change the order + * of enum token too. + */ +static const struct opinfo opinfo[] = { + { "++", 2, P_PRIMARY }, /* before + */ + { "--", 2, P_PRIMARY }, /* before - */ + { "==", 2, P_EQUALITY }, /* before = */ + { "!=", 2, P_EQUALITY }, /* before ! */ + { "=", 1, P_ASSIGN }, /* keep assigns in a block */ + { "*=", 2, P_ASSIGN }, + { "/=", 2, P_ASSIGN }, + { "%=", 2, P_ASSIGN }, + { "+=", 2, P_ASSIGN }, + { "-=", 2, P_ASSIGN }, + { "<<=", 3, P_ASSIGN }, + { ">>=", 3, P_ASSIGN }, + { "&=", 2, P_ASSIGN }, + { "^=", 2, P_ASSIGN }, + { "|=", 2, P_ASSIGN }, + { "<<", 2, P_SHIFT }, + { ">>", 2, P_SHIFT }, + { "<=", 2, P_RELATION }, + { ">=", 2, P_RELATION }, + { "<", 1, P_RELATION }, + { ">", 1, P_RELATION }, + { "&&", 2, P_LAND }, + { "||", 2, P_LOR }, + { "*", 1, P_MULT }, + { "/", 1, P_MULT }, + { "%", 1, P_MULT }, + { "+", 1, P_ADD }, + { "-", 1, P_ADD }, + { "&", 1, P_BAND }, + { "^", 1, P_BXOR }, + { "|", 1, P_BOR }, + { "?", 1, P_TERN }, + { ",", 1, P_COMMA }, + { "~", 1, P_PRIMARY }, + { "!", 1, P_PRIMARY }, + { "(", 1, P_PRIMARY }, + { ")", 1, P_PRIMARY }, + { ":", 1, P_PRIMARY }, + { "", 0, P_PRIMARY } /* end of table */ +}; + + +typedef struct expr_state Expr_state; +struct expr_state { + const char *expression; /* expression being evaluated */ + const char *tokp; /* lexical position */ + enum token tok; /* token from token() */ + int noassign; /* don't do assigns (for ?:,&&,||) */ + bool arith; /* true if evaluating an $(()) + * expression + */ + struct tbl *val; /* value from token() */ + struct tbl *evaling; /* variable that is being recursively + * expanded (EXPRINEVAL flag set) + */ +}; + +enum error_type { + ET_UNEXPECTED, ET_BADLIT, ET_RECURSIVE, + ET_LVALUE, ET_RDONLY, ET_STR +}; + +static void evalerr(Expr_state *, enum error_type, const char *) + __attribute__((__noreturn__)); +static struct tbl *evalexpr(Expr_state *, enum prec); +static void token(Expr_state *); +static struct tbl *do_ppmm(Expr_state *, enum token, struct tbl *, bool); +static void assign_check(Expr_state *, enum token, struct tbl *); +static struct tbl *tempvar(void); +static struct tbl *intvar(Expr_state *, struct tbl *); + +/* + * parse and evaluate expression + */ +int +evaluate(const char *expr, long int *rval, int error_ok, bool arith) +{ + struct tbl v; + int ret; + + v.flag = DEFINED|INTEGER; + v.type = 0; + ret = v_evaluate(&v, expr, error_ok, arith); + *rval = v.val.i; + return ret; +} + +/* + * parse and evaluate expression, storing result in vp. + */ +int +v_evaluate(struct tbl *vp, const char *expr, volatile int error_ok, + bool arith) +{ + struct tbl *v; + Expr_state curstate; + Expr_state * const es = &curstate; + int i; + + /* save state to allow recursive calls */ + curstate.expression = curstate.tokp = expr; + curstate.noassign = 0; + curstate.arith = arith; + curstate.evaling = NULL; + curstate.val = NULL; + + newenv(E_ERRH); + i = sigsetjmp(genv->jbuf, 0); + if (i) { + /* Clear EXPRINEVAL in of any variables we were playing with */ + if (curstate.evaling) + curstate.evaling->flag &= ~EXPRINEVAL; + quitenv(NULL); + if (i == LAEXPR) { + if (error_ok == KSH_RETURN_ERROR) + return 0; + errorf(NULL); + } + unwind(i); + /* NOTREACHED */ + } + + token(es); +#if 1 /* ifdef-out to disallow empty expressions to be treated as 0 */ + if (es->tok == END) { + es->tok = LIT; + es->val = tempvar(); + } +#endif /* 0 */ + v = intvar(es, evalexpr(es, MAX_PREC)); + + if (es->tok != END) + evalerr(es, ET_UNEXPECTED, NULL); + + if (vp->flag & INTEGER) + setint_v(vp, v, es->arith); + else + /* can fail if readonly */ + setstr(vp, str_val(v), error_ok); + + quitenv(NULL); + + return 1; +} + +static void +evalerr(Expr_state *es, enum error_type type, const char *str) +{ + char tbuf[2]; + const char *s; + + es->arith = false; + switch (type) { + case ET_UNEXPECTED: + switch (es->tok) { + case VAR: + s = es->val->name; + break; + case LIT: + s = str_val(es->val); + break; + case END: + s = "end of expression"; + break; + case BAD: + tbuf[0] = *es->tokp; + tbuf[1] = '\0'; + s = tbuf; + break; + default: + s = opinfo[(int)es->tok].name; + } + warningf(true, "%s: unexpected `%s'", es->expression, s); + break; + + case ET_BADLIT: + warningf(true, "%s: bad number `%s'", es->expression, str); + break; + + case ET_RECURSIVE: + warningf(true, "%s: expression recurses on parameter `%s'", + es->expression, str); + break; + + case ET_LVALUE: + warningf(true, "%s: %s requires lvalue", + es->expression, str); + break; + + case ET_RDONLY: + warningf(true, "%s: %s applied to read only variable", + es->expression, str); + break; + + default: /* keep gcc happy */ + case ET_STR: + warningf(true, "%s: %s", es->expression, str); + break; + } + unwind(LAEXPR); +} + +static struct tbl * +evalexpr(Expr_state *es, enum prec prec) +{ + struct tbl *vl, *vr = NULL, *vasn; + enum token op; + long res = 0; + + if (prec == P_PRIMARY) { + op = es->tok; + if (op == O_BNOT || op == O_LNOT || op == O_MINUS || + op == O_PLUS) { + token(es); + vl = intvar(es, evalexpr(es, P_PRIMARY)); + if (op == O_BNOT) + vl->val.i = ~vl->val.i; + else if (op == O_LNOT) + vl->val.i = !vl->val.i; + else if (op == O_MINUS) + vl->val.i = -vl->val.i; + /* op == O_PLUS is a no-op */ + } else if (op == OPEN_PAREN) { + token(es); + vl = evalexpr(es, MAX_PREC); + if (es->tok != CLOSE_PAREN) + evalerr(es, ET_STR, "missing )"); + token(es); + } else if (op == O_PLUSPLUS || op == O_MINUSMINUS) { + token(es); + vl = do_ppmm(es, op, es->val, true); + token(es); + } else if (op == VAR || op == LIT) { + vl = es->val; + token(es); + } else { + evalerr(es, ET_UNEXPECTED, NULL); + /* NOTREACHED */ + } + if (es->tok == O_PLUSPLUS || es->tok == O_MINUSMINUS) { + vl = do_ppmm(es, es->tok, vl, false); + token(es); + } + return vl; + } + vl = evalexpr(es, ((int) prec) - 1); + for (op = es->tok; IS_BINOP(op) && opinfo[(int) op].prec == prec; + op = es->tok) { + token(es); + vasn = vl; + if (op != O_ASN) /* vl may not have a value yet */ + vl = intvar(es, vl); + if (IS_ASSIGNOP(op)) { + assign_check(es, op, vasn); + vr = intvar(es, evalexpr(es, P_ASSIGN)); + } else if (op != O_TERN && op != O_LAND && op != O_LOR) + vr = intvar(es, evalexpr(es, ((int) prec) - 1)); + if ((op == O_DIV || op == O_MOD || op == O_DIVASN || + op == O_MODASN) && vr->val.i == 0) { + if (es->noassign) + vr->val.i = 1; + else + evalerr(es, ET_STR, "zero divisor"); + } + switch ((int) op) { + case O_TIMES: + case O_TIMESASN: + res = vl->val.i * vr->val.i; + break; + case O_DIV: + case O_DIVASN: + if (vl->val.i == LONG_MIN && vr->val.i == -1) + res = LONG_MIN; + else + res = vl->val.i / vr->val.i; + break; + case O_MOD: + case O_MODASN: + if (vl->val.i == LONG_MIN && vr->val.i == -1) + res = 0; + else + res = vl->val.i % vr->val.i; + break; + case O_PLUS: + case O_PLUSASN: + res = vl->val.i + vr->val.i; + break; + case O_MINUS: + case O_MINUSASN: + res = vl->val.i - vr->val.i; + break; + case O_LSHIFT: + case O_LSHIFTASN: + res = vl->val.i << vr->val.i; + break; + case O_RSHIFT: + case O_RSHIFTASN: + res = vl->val.i >> vr->val.i; + break; + case O_LT: + res = vl->val.i < vr->val.i; + break; + case O_LE: + res = vl->val.i <= vr->val.i; + break; + case O_GT: + res = vl->val.i > vr->val.i; + break; + case O_GE: + res = vl->val.i >= vr->val.i; + break; + case O_EQ: + res = vl->val.i == vr->val.i; + break; + case O_NE: + res = vl->val.i != vr->val.i; + break; + case O_BAND: + case O_BANDASN: + res = vl->val.i & vr->val.i; + break; + case O_BXOR: + case O_BXORASN: + res = vl->val.i ^ vr->val.i; + break; + case O_BOR: + case O_BORASN: + res = vl->val.i | vr->val.i; + break; + case O_LAND: + if (!vl->val.i) + es->noassign++; + vr = intvar(es, evalexpr(es, ((int) prec) - 1)); + res = vl->val.i && vr->val.i; + if (!vl->val.i) + es->noassign--; + break; + case O_LOR: + if (vl->val.i) + es->noassign++; + vr = intvar(es, evalexpr(es, ((int) prec) - 1)); + res = vl->val.i || vr->val.i; + if (vl->val.i) + es->noassign--; + break; + case O_TERN: + { + int e = vl->val.i != 0; + + if (!e) + es->noassign++; + vl = evalexpr(es, MAX_PREC); + if (!e) + es->noassign--; + if (es->tok != CTERN) + evalerr(es, ET_STR, "missing :"); + token(es); + if (e) + es->noassign++; + vr = evalexpr(es, P_TERN); + if (e) + es->noassign--; + vl = e ? vl : vr; + } + break; + case O_ASN: + res = vr->val.i; + break; + case O_COMMA: + res = vr->val.i; + break; + } + if (IS_ASSIGNOP(op)) { + vr->val.i = res; + if (vasn->flag & INTEGER) + setint_v(vasn, vr, es->arith); + else + setint(vasn, res); + vl = vr; + } else if (op != O_TERN) + vl->val.i = res; + } + return vl; +} + +static void +token(Expr_state *es) +{ + const char *cp; + int c; + char *tvar; + + /* skip white space */ + for (cp = es->tokp; (c = *cp), isspace((unsigned char)c); cp++) + ; + es->tokp = cp; + + if (c == '\0') + es->tok = END; + else if (letter(c)) { + for (; letnum(c); c = *cp) + cp++; + if (c == '[') { + int len; + + len = array_ref_len(cp); + if (len == 0) + evalerr(es, ET_STR, "missing ]"); + cp += len; + } else if (c == '(' /*)*/ ) { + /* todo: add math functions (all take single argument): + * abs acos asin atan cos cosh exp int log sin sinh sqrt + * tan tanh + */ + ; + } + if (es->noassign) { + es->val = tempvar(); + es->val->flag |= EXPRLVALUE; + } else { + tvar = str_nsave(es->tokp, cp - es->tokp, ATEMP); + es->val = global(tvar); + afree(tvar, ATEMP); + } + es->tok = VAR; + } else if (digit(c)) { + for (; c != '_' && (letnum(c) || c == '#'); c = *cp++) + ; + tvar = str_nsave(es->tokp, --cp - es->tokp, ATEMP); + es->val = tempvar(); + es->val->flag &= ~INTEGER; + es->val->type = 0; + es->val->val.s = tvar; + if (setint_v(es->val, es->val, es->arith) == NULL) + evalerr(es, ET_BADLIT, tvar); + afree(tvar, ATEMP); + es->tok = LIT; + } else { + int i, n0; + + for (i = 0; (n0 = opinfo[i].name[0]); i++) + if (c == n0 && + strncmp(cp, opinfo[i].name, opinfo[i].len) == 0) { + es->tok = (enum token) i; + cp += opinfo[i].len; + break; + } + if (!n0) + es->tok = BAD; + } + es->tokp = cp; +} + +/* Do a ++ or -- operation */ +static struct tbl * +do_ppmm(Expr_state *es, enum token op, struct tbl *vasn, bool is_prefix) +{ + struct tbl *vl; + int oval; + + assign_check(es, op, vasn); + + vl = intvar(es, vasn); + oval = op == O_PLUSPLUS ? vl->val.i++ : vl->val.i--; + if (vasn->flag & INTEGER) + setint_v(vasn, vl, es->arith); + else + setint(vasn, vl->val.i); + if (!is_prefix) /* undo the inc/dec */ + vl->val.i = oval; + + return vl; +} + +static void +assign_check(Expr_state *es, enum token op, struct tbl *vasn) +{ + if (es->tok == END || vasn == NULL || + (vasn->name[0] == '\0' && !(vasn->flag & EXPRLVALUE))) + evalerr(es, ET_LVALUE, opinfo[(int) op].name); + else if (vasn->flag & RDONLY) + evalerr(es, ET_RDONLY, opinfo[(int) op].name); +} + +static struct tbl * +tempvar(void) +{ + struct tbl *vp; + + vp = alloc(sizeof(struct tbl), ATEMP); + vp->flag = ISSET|INTEGER; + vp->type = 0; + vp->areap = ATEMP; + vp->val.i = 0; + vp->name[0] = '\0'; + return vp; +} + +/* cast (string) variable to temporary integer variable */ +static struct tbl * +intvar(Expr_state *es, struct tbl *vp) +{ + struct tbl *vq; + + /* try to avoid replacing a temp var with another temp var */ + if (vp->name[0] == '\0' && + (vp->flag & (ISSET|INTEGER|EXPRLVALUE)) == (ISSET|INTEGER)) + return vp; + + vq = tempvar(); + if (setint_v(vq, vp, es->arith) == NULL) { + if (vp->flag & EXPRINEVAL) + evalerr(es, ET_RECURSIVE, vp->name); + es->evaling = vp; + vp->flag |= EXPRINEVAL; + v_evaluate(vq, str_val(vp), KSH_UNWIND_ERROR, es->arith); + vp->flag &= ~EXPRINEVAL; + es->evaling = NULL; + } + return vq; +} diff --git a/bin/ksh/history.c b/bin/ksh/history.c @@ -0,0 +1,981 @@ +/* $OpenBSD: history.c,v 1.56 2015/12/30 09:07:00 tedu Exp $ */ + +/* + * command history + */ + +/* + * This file contains + * a) the original in-memory history mechanism + * b) a more complicated mechanism done by pc@hillside.co.uk + * that more closely follows the real ksh way of doing + * things. You need to have the mmap system call for this + * to work on your system + */ + +#include <sys/stat.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "sh.h" + +#ifdef HISTORY +# include <sys/mman.h> + +/* + * variables for handling the data file + */ +static int histfd; +static int hsize; + +static int hist_count_lines(unsigned char *, int); +static int hist_shrink(unsigned char *, int); +static unsigned char *hist_skip_back(unsigned char *,int *,int); +static void histload(Source *, unsigned char *, int); +static void histinsert(Source *, int, unsigned char *); +static void writehistfile(int, char *); +static int sprinkle(int); + +static int hist_execute(char *); +static int hist_replace(char **, const char *, const char *, int); +static char **hist_get(const char *, int, int); +static char **hist_get_oldest(void); +static void histbackup(void); + +static char **current; /* current position in history[] */ +static char *hname; /* current name of history file */ +static int hstarted; /* set after hist_init() called */ +static Source *hist_source; + + +int +c_fc(char **wp) +{ + struct shf *shf; + struct temp *tf = NULL; + char *p, *editor = NULL; + int gflag = 0, lflag = 0, nflag = 0, sflag = 0, rflag = 0; + int optc; + char *first = NULL, *last = NULL; + char **hfirst, **hlast, **hp; + + if (!Flag(FTALKING_I)) { + bi_errorf("history functions not available"); + return 1; + } + + while ((optc = ksh_getopt(wp, &builtin_opt, + "e:glnrs0,1,2,3,4,5,6,7,8,9,")) != -1) + switch (optc) { + case 'e': + p = builtin_opt.optarg; + if (strcmp(p, "-") == 0) + sflag++; + else { + size_t len = strlen(p) + 4; + editor = str_nsave(p, len, ATEMP); + strlcat(editor, " $_", len); + } + break; + case 'g': /* non-at&t ksh */ + gflag++; + break; + case 'l': + lflag++; + break; + case 'n': + nflag++; + break; + case 'r': + rflag++; + break; + case 's': /* posix version of -e - */ + sflag++; + break; + /* kludge city - accept -num as -- -num (kind of) */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + p = shf_smprintf("-%c%s", + optc, builtin_opt.optarg); + if (!first) + first = p; + else if (!last) + last = p; + else { + bi_errorf("too many arguments"); + return 1; + } + break; + case '?': + return 1; + } + wp += builtin_opt.optind; + + /* Substitute and execute command */ + if (sflag) { + char *pat = NULL, *rep = NULL; + + if (editor || lflag || nflag || rflag) { + bi_errorf("can't use -e, -l, -n, -r with -s (-e -)"); + return 1; + } + + /* Check for pattern replacement argument */ + if (*wp && **wp && (p = strchr(*wp + 1, '='))) { + pat = str_save(*wp, ATEMP); + p = pat + (p - *wp); + *p++ = '\0'; + rep = p; + wp++; + } + /* Check for search prefix */ + if (!first && (first = *wp)) + wp++; + if (last || *wp) { + bi_errorf("too many arguments"); + return 1; + } + + hp = first ? hist_get(first, false, false) : + hist_get_newest(false); + if (!hp) + return 1; + return hist_replace(hp, pat, rep, gflag); + } + + if (editor && (lflag || nflag)) { + bi_errorf("can't use -l, -n with -e"); + return 1; + } + + if (!first && (first = *wp)) + wp++; + if (!last && (last = *wp)) + wp++; + if (*wp) { + bi_errorf("too many arguments"); + return 1; + } + if (!first) { + hfirst = lflag ? hist_get("-16", true, true) : + hist_get_newest(false); + if (!hfirst) + return 1; + /* can't fail if hfirst didn't fail */ + hlast = hist_get_newest(false); + } else { + /* POSIX says not an error if first/last out of bounds + * when range is specified; at&t ksh and pdksh allow out of + * bounds for -l as well. + */ + hfirst = hist_get(first, (lflag || last) ? true : false, + lflag ? true : false); + if (!hfirst) + return 1; + hlast = last ? hist_get(last, true, lflag ? true : false) : + (lflag ? hist_get_newest(false) : hfirst); + if (!hlast) + return 1; + } + if (hfirst > hlast) { + char **temp; + + temp = hfirst; hfirst = hlast; hlast = temp; + rflag = !rflag; /* POSIX */ + } + + /* List history */ + if (lflag) { + char *s, *t; + const char *nfmt = nflag ? "\t" : "%d\t"; + + for (hp = rflag ? hlast : hfirst; + hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) { + shf_fprintf(shl_stdout, nfmt, + hist_source->line - (int) (histptr - hp)); + /* print multi-line commands correctly */ + for (s = *hp; (t = strchr(s, '\n')); s = t) + shf_fprintf(shl_stdout, "%.*s\t", ++t - s, s); + shf_fprintf(shl_stdout, "%s\n", s); + } + shf_flush(shl_stdout); + return 0; + } + + /* Run editor on selected lines, then run resulting commands */ + + tf = maketemp(ATEMP, TT_HIST_EDIT, &genv->temps); + if (!(shf = tf->shf)) { + bi_errorf("cannot create temp file %s - %s", + tf->name, strerror(errno)); + return 1; + } + for (hp = rflag ? hlast : hfirst; + hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) + shf_fprintf(shf, "%s\n", *hp); + if (shf_close(shf) == EOF) { + bi_errorf("error writing temporary file - %s", strerror(errno)); + return 1; + } + + /* Ignore setstr errors here (arbitrary) */ + setstr(local("_", false), tf->name, KSH_RETURN_ERROR); + + /* XXX: source should not get trashed by this.. */ + { + Source *sold = source; + int ret; + + ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_", 0); + source = sold; + if (ret) + return ret; + } + + { + struct stat statb; + XString xs; + char *xp; + int n; + + if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) { + bi_errorf("cannot open temp file %s", tf->name); + return 1; + } + + n = fstat(shf->fd, &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_close(shf); + return 1; + } + shf_close(shf); + *xp = '\0'; + strip_nuls(Xstring(xs, xp), Xlength(xs, xp)); + return hist_execute(Xstring(xs, xp)); + } +} + +/* Save cmd in history, execute cmd (cmd gets trashed) */ +static int +hist_execute(char *cmd) +{ + Source *sold; + int ret; + char *p, *q; + + histbackup(); + + for (p = cmd; p; p = q) { + if ((q = strchr(p, '\n'))) { + *q++ = '\0'; /* kill the newline */ + if (!*q) /* ignore trailing newline */ + q = NULL; + } + histsave(++(hist_source->line), p, 1); + + shellf("%s\n", p); /* POSIX doesn't say this is done... */ + if ((p = q)) /* restore \n (trailing \n not restored) */ + q[-1] = '\n'; + } + + /* Commands are executed here instead of pushing them onto the + * input 'cause posix says the redirection and variable assignments + * in + * X=y fc -e - 42 2> /dev/null + * are to effect the repeated commands environment. + */ + /* XXX: source should not get trashed by this.. */ + sold = source; + ret = command(cmd, 0); + source = sold; + return ret; +} + +static int +hist_replace(char **hp, const char *pat, const char *rep, int global) +{ + char *line; + + if (!pat) + line = str_save(*hp, ATEMP); + else { + char *s, *s1; + int pat_len = strlen(pat); + int rep_len = strlen(rep); + int len; + XString xs; + char *xp; + int any_subst = 0; + + Xinit(xs, xp, 128, ATEMP); + for (s = *hp; (s1 = strstr(s, pat)) && (!any_subst || global); + s = s1 + pat_len) { + any_subst = 1; + len = s1 - s; + XcheckN(xs, xp, len + rep_len); + memcpy(xp, s, len); /* first part */ + xp += len; + memcpy(xp, rep, rep_len); /* replacement */ + xp += rep_len; + } + if (!any_subst) { + bi_errorf("substitution failed"); + return 1; + } + len = strlen(s) + 1; + XcheckN(xs, xp, len); + memcpy(xp, s, len); + xp += len; + line = Xclose(xs, xp); + } + return hist_execute(line); +} + +/* + * get pointer to history given pattern + * pattern is a number or string + */ +static char ** +hist_get(const char *str, int approx, int allow_cur) +{ + char **hp = NULL; + int n; + + if (getn(str, &n)) { + hp = histptr + (n < 0 ? n : (n - hist_source->line)); + if ((long)hp < (long)history) { + if (approx) + hp = hist_get_oldest(); + else { + bi_errorf("%s: not in history", str); + hp = NULL; + } + } else if (hp > histptr) { + if (approx) + hp = hist_get_newest(allow_cur); + else { + bi_errorf("%s: not in history", str); + hp = NULL; + } + } else if (!allow_cur && hp == histptr) { + bi_errorf("%s: invalid range", str); + hp = NULL; + } + } 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 = NULL; + } else + hp = &history[n]; + } + return hp; +} + +/* Return a pointer to the newest command in the history */ +char ** +hist_get_newest(int allow_cur) +{ + if (histptr < history || (!allow_cur && histptr == history)) { + bi_errorf("no history (yet)"); + return NULL; + } + if (allow_cur) + return histptr; + return histptr - 1; +} + +/* Return a pointer to the oldest command in the history */ +static char ** +hist_get_oldest(void) +{ + if (histptr <= history) { + bi_errorf("no history (yet)"); + return NULL; + } + return history; +} + +/******************************/ +/* Back up over last histsave */ +/******************************/ +static void +histbackup(void) +{ + static int last_line = -1; + + if (histptr >= history && last_line != hist_source->line) { + hist_source->line--; + afree(*histptr, APERM); + histptr--; + last_line = hist_source->line; + } +} + +/* + * Return the current position. + */ +char ** +histpos(void) +{ + return current; +} + +int +histnum(int n) +{ + int last = histptr - history; + + if (n < 0 || n >= last) { + current = histptr; + return last; + } else { + current = &history[n]; + return n; + } +} + +/* + * This will become unnecessary if hist_get is modified to allow + * searching from positions other than the end, and in either + * direction. + */ +int +findhist(int start, int fwd, const char *str, int anchored) +{ + char **hp; + int maxhist = histptr - history; + int incr = fwd ? 1 : -1; + int len = strlen(str); + + if (start < 0 || start >= maxhist) + start = maxhist; + + hp = &history[start]; + for (; hp >= history && hp <= histptr; hp += incr) + if ((anchored && strncmp(*hp, str, len) == 0) || + (!anchored && strstr(*hp, str))) + return hp - history; + + return -1; +} + +int +findhistrel(const char *str) +{ + int maxhist = histptr - history; + int start = maxhist - 1; + int rec = atoi(str); + + if (rec == 0) + return -1; + if (rec > 0) { + if (rec > maxhist) + return -1; + return rec - 1; + } + if (rec > maxhist) + return -1; + return start + rec + 1; +} + +/* + * set history + * this means reallocating the dataspace + */ +void +sethistsize(int n) +{ + if (n > 0 && n != histsize) { + int cursize = histptr - history; + + /* save most recent history */ + if (n < cursize) { + memmove(history, histptr - n, n * sizeof(char *)); + cursize = n; + } + + history = areallocarray(history, n, sizeof(char *), APERM); + + histsize = n; + histptr = history + cursize; + } +} + +/* + * set history file + * This can mean reloading/resetting/starting history file + * maintenance + */ +void +sethistfile(const char *name) +{ + /* if not started then nothing to do */ + if (hstarted == 0) + return; + + /* if the name is the same as the name we have */ + if (hname && strcmp(hname, name) == 0) + return; + + /* + * its a new name - possibly + */ + if (histfd) { + /* yes the file is open */ + (void) close(histfd); + histfd = 0; + hsize = 0; + afree(hname, APERM); + hname = NULL; + /* let's reset the history */ + histptr = history - 1; + hist_source->line = 0; + } + + hist_init(hist_source); +} + +/* + * initialise the history vector + */ +void +init_histvec(void) +{ + if (history == NULL) { + histsize = HISTORYSIZE; + history = areallocarray(NULL, histsize, sizeof(char *), APERM); + histptr = history - 1; + } +} + + +/* + * Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to + * a) permit HISTSIZE to control number of lines of history stored + * b) maintain a physical history file + * + * It turns out that there is a lot of ghastly hackery here + */ + + +/* + * save command in history + */ +void +histsave(int lno, const char *cmd, int dowrite) +{ + char **hp; + char *c, *cp; + + c = str_save(cmd, APERM); + if ((cp = strchr(c, '\n')) != NULL) + *cp = '\0'; + + if (histfd && dowrite) + writehistfile(lno, c); + + hp = histptr; + + if (++hp >= history + histsize) { /* remove oldest command */ + afree(*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 is still recorded, 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. + */ + +/* + * History file format: + * Bytes 1, 2: HMAGIC - just to check that we are dealing with + the correct object + * Each command, in the format: + <command byte><command number(4 bytes)><bytes><null> + */ +#define HMAGIC1 0xab +#define HMAGIC2 0xcd +#define COMMAND 0xff + +void +hist_init(Source *s) +{ + unsigned char *base; + int lines; + int fd; + struct stat sb; + + 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; + if (fstat(fd, &sb) == -1 || sb.st_uid != getuid()) { + close(fd); + return; + } + + histfd = savefd(fd); + if (histfd != fd) + close(fd); + + (void) flock(histfd, LOCK_EX); + + hsize = lseek(histfd, 0L, SEEK_END); + + if (hsize == 0) { + /* add magic */ + if (sprinkle(histfd)) { + hist_finish(); + return; + } + } + else if (hsize > 0) { + /* + * we have some data + */ + base = mmap(0, hsize, PROT_READ, + MAP_FILE|MAP_PRIVATE, histfd, 0); + /* + * check on its validity + */ + if (base == MAP_FAILED || *base != HMAGIC1 || base[1] != HMAGIC2) { + if (base != MAP_FAILED) + munmap((caddr_t)base, hsize); + hist_finish(); + if (unlink(hname) != 0) + return; + goto retry; + } + if (hsize > 2) { + lines = hist_count_lines(base+2, hsize-2); + if (lines > histsize) { + /* we need to make the file smaller */ + if (hist_shrink(base, hsize)) + if (unlink(hname) != 0) + return; + munmap((caddr_t)base, hsize); + hist_finish(); + goto retry; + } + } + histload(hist_source, base+2, hsize-2); + munmap((caddr_t)base, hsize); + } + (void) flock(histfd, LOCK_UN); + hsize = lseek(histfd, 0L, SEEK_END); +} + +typedef enum state { + shdr, /* expecting a header */ + sline, /* looking for a null byte to end the line */ + sn1, /* bytes 1 to 4 of a line no */ + sn2, sn3, sn4 +} State; + +static int +hist_count_lines(unsigned char *base, int bytes) +{ + State state = shdr; + int lines = 0; + + while (bytes--) { + switch (state) { + case shdr: + if (*base == COMMAND) + state = sn1; + break; + case sn1: + state = sn2; break; + case sn2: + state = sn3; break; + case sn3: + state = sn4; break; + case sn4: + state = sline; break; + case sline: + if (*base == '\0') + lines++, state = shdr; + } + base++; + } + return lines; +} + +/* + * Shrink the history file to histsize lines + */ +static int +hist_shrink(unsigned char *oldbase, int oldbytes) +{ + int fd; + char nfile[1024]; + unsigned char *nbase = oldbase; + int nbytes = oldbytes; + + nbase = hist_skip_back(nbase, &nbytes, histsize); + if (nbase == NULL) + return 1; + if (nbase == oldbase) + return 0; + + /* + * create temp file + */ + (void) shf_snprintf(nfile, sizeof(nfile), "%s.%d", hname, procpid); + if ((fd = open(nfile, O_CREAT | O_TRUNC | O_WRONLY, 0600)) < 0) + return 1; + + if (sprinkle(fd)) { + close(fd); + unlink(nfile); + return 1; + } + if (write(fd, nbase, nbytes) != nbytes) { + close(fd); + unlink(nfile); + return 1; + } + close(fd); + + /* + * rename + */ + if (rename(nfile, hname) < 0) + return 1; + return 0; +} + + +/* + * find a pointer to the data `no' back from the end of the file + * return the pointer and the number of bytes left + */ +static unsigned char * +hist_skip_back(unsigned char *base, int *bytes, int no) +{ + int lines = 0; + unsigned char *ep; + + for (ep = base + *bytes; --ep > base; ) { + /* this doesn't really work: the 4 byte line number that is + * encoded after the COMMAND byte can itself contain the + * COMMAND byte.... + */ + for (; ep > base && *ep != COMMAND; ep--) + ; + if (ep == base) + break; + if (++lines == no) { + *bytes = *bytes - ((char *)ep - (char *)base); + return ep; + } + } + return NULL; +} + +/* + * load the history structure from the stored data + */ +static void +histload(Source *s, unsigned char *base, int bytes) +{ + State state; + int lno = 0; + unsigned char *line = NULL; + + for (state = shdr; bytes-- > 0; base++) { + switch (state) { + case shdr: + if (*base == COMMAND) + state = sn1; + break; + case sn1: + lno = (((*base)&0xff)<<24); + state = sn2; + break; + case sn2: + lno |= (((*base)&0xff)<<16); + state = sn3; + break; + case sn3: + lno |= (((*base)&0xff)<<8); + state = sn4; + break; + case sn4: + lno |= (*base)&0xff; + line = base+1; + state = sline; + break; + case sline: + if (*base == '\0') { + /* worry about line numbers */ + if (histptr >= history && lno-1 != s->line) { + /* a replacement ? */ + histinsert(s, lno, line); + } + else { + s->line = lno; + s->cmd_offset = lno; + histsave(lno, (char *)line, 0); + } + state = shdr; + } + } + } +} + +/* + * Insert a line into the history at a specified number + */ +static void +histinsert(Source *s, int lno, unsigned char *line) +{ + char **hp; + + if (lno >= s->line-(histptr-history) && lno <= s->line) { + hp = &histptr[lno-s->line]; + afree(*hp, APERM); + *hp = str_save((char *)line, APERM); + } +} + +/* + * write a command to the end of the history file + * This *MAY* seem easy but it's also necessary to check + * that the history file has not changed in size. + * If it has - then some other shell has written to it + * and we should read those commands to update our history + */ +static void +writehistfile(int lno, char *cmd) +{ + int sizenow; + unsigned char *base; + unsigned char *new; + int bytes; + unsigned char hdr[5]; + + (void) flock(histfd, LOCK_EX); + sizenow = lseek(histfd, 0L, SEEK_END); + if (sizenow != hsize) { + /* + * Things have changed + */ + if (sizenow > hsize) { + /* someone has added some lines */ + bytes = sizenow - hsize; + base = mmap(0, sizenow, + PROT_READ, MAP_FILE|MAP_PRIVATE, histfd, 0); + if (base == MAP_FAILED) + goto bad; + new = base + hsize; + if (*new != COMMAND) { + munmap((caddr_t)base, sizenow); + goto bad; + } + hist_source->line--; + histload(hist_source, new, bytes); + hist_source->line++; + lno = hist_source->line; + munmap((caddr_t)base, sizenow); + hsize = sizenow; + } else { + /* it has shrunk */ + /* but to what? */ + /* we'll give up for now */ + goto bad; + } + } + /* + * we can write our bit now + */ + hdr[0] = COMMAND; + hdr[1] = (lno>>24)&0xff; + hdr[2] = (lno>>16)&0xff; + hdr[3] = (lno>>8)&0xff; + hdr[4] = lno&0xff; + (void) write(histfd, hdr, 5); + (void) write(histfd, cmd, strlen(cmd)+1); + hsize = lseek(histfd, 0L, SEEK_END); + (void) flock(histfd, LOCK_UN); + return; +bad: + hist_finish(); +} + +void +hist_finish(void) +{ + (void) flock(histfd, LOCK_UN); + (void) close(histfd); + histfd = 0; +} + +/* + * add magic to the history file + */ +static int +sprinkle(int fd) +{ + static unsigned char mag[] = { HMAGIC1, HMAGIC2 }; + + return(write(fd, mag, 2) != 2); +} + +#else /* HISTORY */ + +/* No history to be compiled in: dummy routines to avoid lots more ifdefs */ +void +init_histvec(void) +{ +} +void +hist_init(Source *s) +{ +} +void +hist_finish(void) +{ +} +void +histsave(int lno, const char *cmd, int dowrite) +{ + errorf("history not enabled"); +} +#endif /* HISTORY */ diff --git a/bin/ksh/io.c b/bin/ksh/io.c @@ -0,0 +1,444 @@ +/* $OpenBSD: io.c,v 1.35 2016/03/20 00:01:21 krw Exp $ */ + +/* + * shell buffered IO and formatted output + */ + +#include <sys/stat.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> + +#include "sh.h" + +static int initio_done; + +/* + * formatted output functions + */ + + +/* A shell error occurred (eg, syntax error, etc.) */ +void +errorf(const char *fmt, ...) +{ + va_list va; + + shl_stdout_ok = 0; /* debugging: note that stdout not valid */ + exstat = 1; + if (fmt != NULL && *fmt != '\0') { + error_prefix(true); + va_start(va, fmt); + shf_vfprintf(shl_out, fmt, va); + va_end(va); + shf_putchar('\n', shl_out); + } + shf_flush(shl_out); + unwind(LERROR); +} + +/* like errorf(), but no unwind is done */ +void +warningf(bool show_lineno, const char *fmt, ...) +{ + va_list va; + + error_prefix(show_lineno); + va_start(va, fmt); + shf_vfprintf(shl_out, fmt, va); + va_end(va); + shf_putchar('\n', shl_out); + shf_flush(shl_out); +} + +/* Used by built-in utilities to prefix shell and utility name to message + * (also unwinds environments for special builtins). + */ +void +bi_errorf(const char *fmt, ...) +{ + va_list va; + + shl_stdout_ok = 0; /* debugging: note that stdout not valid */ + exstat = 1; + if (fmt != NULL && *fmt != '\0') { + error_prefix(true); + /* not set when main() calls parse_args() */ + if (builtin_argv0) + shf_fprintf(shl_out, "%s: ", builtin_argv0); + va_start(va, fmt); + shf_vfprintf(shl_out, fmt, va); + va_end(va); + shf_putchar('\n', shl_out); + } + shf_flush(shl_out); + /* POSIX special builtins and ksh special builtins cause + * non-interactive shells to exit. + * XXX odd use of KEEPASN; also may not want LERROR here + */ + if ((builtin_flag & SPEC_BI) || + (Flag(FPOSIX) && (builtin_flag & KEEPASN))) { + builtin_argv0 = NULL; + unwind(LERROR); + } +} + +/* Called when something that shouldn't happen does */ +void +internal_errorf(int jump, const char *fmt, ...) +{ + va_list va; + + error_prefix(true); + shf_fprintf(shl_out, "internal error: "); + va_start(va, fmt); + shf_vfprintf(shl_out, fmt, va); + va_end(va); + shf_putchar('\n', shl_out); + shf_flush(shl_out); + if (jump) + unwind(LERROR); +} + +/* used by error reporting functions to print "ksh: .kshrc[25]: " */ +void +error_prefix(int fileline) +{ + /* Avoid foo: foo[2]: ... */ + if (!fileline || !source || !source->file || + strcmp(source->file, kshname) != 0) + shf_fprintf(shl_out, "%s: ", kshname + (*kshname == '-')); + if (fileline && source && source->file != NULL) { + shf_fprintf(shl_out, "%s[%d]: ", source->file, + source->errline > 0 ? source->errline : source->line); + source->errline = 0; + } +} + +/* printf to shl_out (stderr) with flush */ +void +shellf(const char *fmt, ...) +{ + va_list va; + + if (!initio_done) /* shl_out may not be set up yet... */ + return; + va_start(va, fmt); + shf_vfprintf(shl_out, fmt, va); + va_end(va); + shf_flush(shl_out); +} + +/* printf to shl_stdout (stdout) */ +void +shprintf(const char *fmt, ...) +{ + va_list va; + + if (!shl_stdout_ok) + internal_errorf(1, "shl_stdout not valid"); + va_start(va, fmt); + shf_vfprintf(shl_stdout, fmt, va); + va_end(va); +} + +#ifdef KSH_DEBUG +static struct shf *kshdebug_shf; + +void +kshdebug_init_(void) +{ + if (kshdebug_shf) + shf_close(kshdebug_shf); + kshdebug_shf = shf_open("/tmp/ksh-debug.log", + O_WRONLY|O_APPEND|O_CREAT, 0600, SHF_WR|SHF_MAPHI); + if (kshdebug_shf) { + shf_fprintf(kshdebug_shf, "\nNew shell[pid %d]\n", getpid()); + shf_flush(kshdebug_shf); + } +} + +/* print to debugging log */ +void +kshdebug_printf_(const char *fmt, ...) +{ + va_list va; + + if (!kshdebug_shf) + return; + va_start(va, fmt); + shf_fprintf(kshdebug_shf, "[%d] ", getpid()); + shf_vfprintf(kshdebug_shf, fmt, va); + va_end(va); + shf_flush(kshdebug_shf); +} + +void +kshdebug_dump_(const char *str, const void *mem, int nbytes) +{ + int i, j; + int nprow = 16; + + if (!kshdebug_shf) + return; + shf_fprintf(kshdebug_shf, "[%d] %s:\n", getpid(), str); + for (i = 0; i < nbytes; i += nprow) { + char c = '\t'; + + for (j = 0; j < nprow && i + j < nbytes; j++) { + shf_fprintf(kshdebug_shf, "%c%02x", c, + ((const unsigned char *) mem)[i + j]); + c = ' '; + } + shf_fprintf(kshdebug_shf, "\n"); + } + shf_flush(kshdebug_shf); +} +#endif /* KSH_DEBUG */ + +/* test if we can seek backwards fd (returns 0 or SHF_UNBUF) */ +int +can_seek(int fd) +{ + struct stat statb; + + return fstat(fd, &statb) == 0 && !S_ISREG(statb.st_mode) ? + SHF_UNBUF : 0; +} + +struct shf shf_iob[3]; + +void +initio(void) +{ + shf_fdopen(1, SHF_WR, shl_stdout); /* force buffer allocation */ + shf_fdopen(2, SHF_WR, shl_out); + shf_fdopen(2, SHF_WR, shl_spare); /* force buffer allocation */ + initio_done = 1; + kshdebug_init(); +} + +/* A dup2() with error checking */ +int +ksh_dup2(int ofd, int nfd, int errok) +{ + int ret = dup2(ofd, nfd); + + if (ret < 0 && errno != EBADF && !errok) + errorf("too many files open in shell"); + + return ret; +} + +/* + * move fd from user space (0<=fd<10) to shell space (fd>=10), + * set close-on-exec flag. + */ +int +savefd(int fd) +{ + int nfd; + + if (fd < FDBASE) { + nfd = fcntl(fd, F_DUPFD_CLOEXEC, FDBASE); + if (nfd < 0) { + if (errno == EBADF) + return -1; + else + errorf("too many files open in shell"); + } + } else { + nfd = fd; + fcntl(nfd, F_SETFD, FD_CLOEXEC); + } + return nfd; +} + +void +restfd(int fd, int ofd) +{ + if (fd == 2) + shf_flush(&shf_iob[fd]); + if (ofd < 0) /* original fd closed */ + close(fd); + else if (fd != ofd) { + ksh_dup2(ofd, fd, true); /* XXX: what to do if this fails? */ + close(ofd); + } +} + +void +openpipe(int *pv) +{ + int lpv[2]; + + if (pipe(lpv) < 0) + errorf("can't create pipe - try again"); + pv[0] = savefd(lpv[0]); + if (pv[0] != lpv[0]) + close(lpv[0]); + pv[1] = savefd(lpv[1]); + if (pv[1] != lpv[1]) + close(lpv[1]); +} + +void +closepipe(int *pv) +{ + close(pv[0]); + close(pv[1]); +} + +/* Called by iosetup() (deals with 2>&4, etc.), c_read, c_print to turn + * a string (the X in 2>&X, read -uX, print -uX) into a file descriptor. + */ +int +check_fd(char *name, int mode, const char **emsgp) +{ + int fd, fl; + + if (isdigit((unsigned char)name[0]) && !name[1]) { + fd = name[0] - '0'; + if ((fl = fcntl(fd, F_GETFL)) < 0) { + if (emsgp) + *emsgp = "bad file descriptor"; + return -1; + } + fl &= O_ACCMODE; + /* X_OK is a kludge to disable this check for dups (x<&1): + * historical shells never did this check (XXX don't know what + * posix has to say). + */ + if (!(mode & X_OK) && fl != O_RDWR && + (((mode & R_OK) && fl != O_RDONLY) || + ((mode & W_OK) && fl != O_WRONLY))) { + if (emsgp) + *emsgp = (fl == O_WRONLY) ? + "fd not open for reading" : + "fd not open for writing"; + return -1; + } + return fd; + } else if (name[0] == 'p' && !name[1]) + return coproc_getfd(mode, emsgp); + if (emsgp) + *emsgp = "illegal file descriptor name"; + return -1; +} + +/* Called once from main */ +void +coproc_init(void) +{ + coproc.read = coproc.readw = coproc.write = -1; + coproc.njobs = 0; + coproc.id = 0; +} + +/* Called by c_read() when eof is read - close fd if it is the co-process fd */ +void +coproc_read_close(int fd) +{ + if (coproc.read >= 0 && fd == coproc.read) { + coproc_readw_close(fd); + close(coproc.read); + coproc.read = -1; + } +} + +/* Called by c_read() and by iosetup() to close the other side of the + * read pipe, so reads will actually terminate. + */ +void +coproc_readw_close(int fd) +{ + if (coproc.readw >= 0 && coproc.read >= 0 && fd == coproc.read) { + close(coproc.readw); + coproc.readw = -1; + } +} + +/* Called by c_print when a write to a fd fails with EPIPE and by iosetup + * when co-process input is dup'd + */ +void +coproc_write_close(int fd) +{ + if (coproc.write >= 0 && fd == coproc.write) { + close(coproc.write); + coproc.write = -1; + } +} + +/* Called to check for existence of/value of the co-process file descriptor. + * (Used by check_fd() and by c_read/c_print to deal with -p option). + */ +int +coproc_getfd(int mode, const char **emsgp) +{ + int fd = (mode & R_OK) ? coproc.read : coproc.write; + + if (fd >= 0) + return fd; + if (emsgp) + *emsgp = "no coprocess"; + return -1; +} + +/* called to close file descriptors related to the coprocess (if any) + * Should be called with SIGCHLD blocked. + */ +void +coproc_cleanup(int reuse) +{ + /* This to allow co-processes to share output pipe */ + if (!reuse || coproc.readw < 0 || coproc.read < 0) { + if (coproc.read >= 0) { + close(coproc.read); + coproc.read = -1; + } + if (coproc.readw >= 0) { + close(coproc.readw); + coproc.readw = -1; + } + } + if (coproc.write >= 0) { + close(coproc.write); + coproc.write = -1; + } +} + + +/* + * temporary files + */ + +struct temp * +maketemp(Area *ap, Temp_type type, struct temp **tlist) +{ + struct temp *tp; + int len; + int fd; + char *path; + const char *dir; + + dir = tmpdir ? tmpdir : "/tmp"; + /* The 20 + 20 is a paranoid worst case for pid/inc */ + len = strlen(dir) + 3 + 20 + 20 + 1; + tp = alloc(sizeof(struct temp) + len, ap); + tp->name = path = (char *) &tp[1]; + tp->shf = NULL; + tp->type = type; + shf_snprintf(path, len, "%s/shXXXXXXXX", dir); + fd = mkstemp(path); + if (fd >= 0) + tp->shf = shf_fdopen(fd, SHF_WR, NULL); + tp->pid = procpid; + + tp->next = *tlist; + *tlist = tp; + return tp; +} diff --git a/bin/ksh/jobs.c b/bin/ksh/jobs.c @@ -0,0 +1,1668 @@ +/* $OpenBSD: jobs.c,v 1.55 2016/03/17 23:33:23 mmcc Exp $ */ + +/* + * Process and job control + */ + +/* + * Reworked/Rewritten version of Eric Gisin's/Ron Natalie's code by + * Larry Bouzane (larry@cs.mun.ca) and hacked again by + * Michael Rendell (michael@cs.mun.ca) + * + * The interface to the rest of the shell should probably be changed + * to allow use of vfork() when available but that would be way too much + * work :) + * + */ + +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/wait.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "sh.h" +#include "tty.h" + +/* Order important! */ +#define PRUNNING 0 +#define PEXITED 1 +#define PSIGNALLED 2 +#define PSTOPPED 3 + +typedef struct proc Proc; +struct proc { + Proc *next; /* next process in pipeline (if any) */ + int state; + int status; /* wait status */ + pid_t pid; /* process id */ + char command[48]; /* process command string */ +}; + +/* Notify/print flag - j_print() argument */ +#define JP_NONE 0 /* don't print anything */ +#define JP_SHORT 1 /* print signals processes were killed by */ +#define JP_MEDIUM 2 /* print [job-num] -/+ command */ +#define JP_LONG 3 /* print [job-num] -/+ pid command */ +#define JP_PGRP 4 /* print pgrp */ + +/* put_job() flags */ +#define PJ_ON_FRONT 0 /* at very front */ +#define PJ_PAST_STOPPED 1 /* just past any stopped jobs */ + +/* Job.flags values */ +#define JF_STARTED 0x001 /* set when all processes in job are started */ +#define JF_WAITING 0x002 /* set if j_waitj() is waiting on job */ +#define JF_W_ASYNCNOTIFY 0x004 /* set if waiting and async notification ok */ +#define JF_XXCOM 0x008 /* set for `command` jobs */ +#define JF_FG 0x010 /* running in foreground (also has tty pgrp) */ +#define JF_SAVEDTTY 0x020 /* j->ttystate is valid */ +#define JF_CHANGED 0x040 /* process has changed state */ +#define JF_KNOWN 0x080 /* $! referenced */ +#define JF_ZOMBIE 0x100 /* known, unwaited process */ +#define JF_REMOVE 0x200 /* flagged for removal (j_jobs()/j_noityf()) */ +#define JF_USETTYMODE 0x400 /* tty mode saved if process exits normally */ +#define JF_SAVEDTTYPGRP 0x800 /* j->saved_ttypgrp is valid */ + +typedef struct job Job; +struct job { + Job *next; /* next job in list */ + int job; /* job number: %n */ + int flags; /* see JF_* */ + int state; /* job state */ + int status; /* exit status of last process */ + pid_t pgrp; /* process group of job */ + pid_t ppid; /* pid of process that forked job */ + int age; /* number of jobs started */ + struct timeval systime; /* system time used by job */ + struct timeval usrtime; /* user time used by job */ + Proc *proc_list; /* process list */ + Proc *last_proc; /* last process in list */ + Coproc_id coproc_id; /* 0 or id of coprocess output pipe */ +#ifdef JOBS + struct termios ttystate;/* saved tty state for stopped jobs */ + pid_t saved_ttypgrp; /* saved tty process group for stopped jobs */ +#endif /* JOBS */ +}; + +/* Flags for j_waitj() */ +#define JW_NONE 0x00 +#define JW_INTERRUPT 0x01 /* ^C will stop the wait */ +#define JW_ASYNCNOTIFY 0x02 /* asynchronous notification during wait ok */ +#define JW_STOPPEDWAIT 0x04 /* wait even if job stopped */ + +/* Error codes for j_lookup() */ +#define JL_OK 0 +#define JL_NOSUCH 1 /* no such job */ +#define JL_AMBIG 2 /* %foo or %?foo is ambiguous */ +#define JL_INVALID 3 /* non-pid, non-% job id */ + +static const char *const lookup_msgs[] = { + null, + "no such job", + "ambiguous", + "argument must be %job or process id", + NULL +}; + +struct timeval j_systime, j_usrtime; /* user and system time of last j_waitjed job */ + +static Job *job_list; /* job list */ +static Job *last_job; +static Job *async_job; +static pid_t async_pid; + +static int nzombie; /* # of zombies owned by this process */ +int njobs; /* # of jobs started */ +static int child_max; /* CHILD_MAX */ + + +/* held_sigchld is set if sigchld occurs before a job is completely started */ +static volatile sig_atomic_t held_sigchld; + +#ifdef JOBS +static struct shf *shl_j; +static int ttypgrp_ok; /* set if can use tty pgrps */ +static pid_t restore_ttypgrp = -1; +static pid_t our_pgrp; +static int const tt_sigs[] = { SIGTSTP, SIGTTIN, SIGTTOU }; +#endif /* JOBS */ + +static void j_set_async(Job *); +static void j_startjob(Job *); +static int j_waitj(Job *, int, const char *); +static void j_sigchld(int); +static void j_print(Job *, int, struct shf *); +static Job *j_lookup(const char *, int *); +static Job *new_job(void); +static Proc *new_proc(void); +static void check_job(Job *); +static void put_job(Job *, int); +static void remove_job(Job *, const char *); +static int kill_job(Job *, int); + +/* initialize job control */ +void +j_init(int mflagset) +{ + child_max = CHILD_MAX; /* so syscon() isn't always being called */ + + sigemptyset(&sm_default); + sigprocmask(SIG_SETMASK, &sm_default, NULL); + + sigemptyset(&sm_sigchld); + sigaddset(&sm_sigchld, SIGCHLD); + + setsig(&sigtraps[SIGCHLD], j_sigchld, + SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP); + +#ifdef JOBS + if (!mflagset && Flag(FTALKING)) + Flag(FMONITOR) = 1; + + /* shl_j is used to do asynchronous notification (used in + * an interrupt handler, so need a distinct shf) + */ + shl_j = shf_fdopen(2, SHF_WR, NULL); + + if (Flag(FMONITOR) || Flag(FTALKING)) { + int i; + + /* the TF_SHELL_USES test is a kludge that lets us know if + * if the signals have been changed by the shell. + */ + for (i = NELEM(tt_sigs); --i >= 0; ) { + sigtraps[tt_sigs[i]].flags |= TF_SHELL_USES; + /* j_change() sets this to SS_RESTORE_DFL if FMONITOR */ + setsig(&sigtraps[tt_sigs[i]], SIG_IGN, + SS_RESTORE_IGN|SS_FORCE); + } + } + + /* j_change() calls tty_init() */ + if (Flag(FMONITOR)) + j_change(); + else +#endif /* JOBS */ + if (Flag(FTALKING)) + tty_init(true); +} + +/* suspend the shell */ +void +j_suspend(void) +{ + struct sigaction sa, osa; + + /* Restore tty and pgrp. */ + if (ttypgrp_ok) { + tcsetattr(tty_fd, TCSADRAIN, &tty_state); + if (restore_ttypgrp >= 0) { + if (tcsetpgrp(tty_fd, restore_ttypgrp) < 0) { + warningf(false, + "j_suspend: tcsetpgrp() failed: %s", + strerror(errno)); + } else { + if (setpgid(0, restore_ttypgrp) < 0) { + warningf(false, + "j_suspend: setpgid() failed: %s", + strerror(errno)); + } + } + } + } + + /* Suspend the shell. */ + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_DFL; + sigaction(SIGTSTP, &sa, &osa); + kill(0, SIGTSTP); + + /* Back from suspend, reset signals, pgrp and tty. */ + sigaction(SIGTSTP, &osa, NULL); + if (ttypgrp_ok) { + if (restore_ttypgrp >= 0) { + if (setpgid(0, kshpid) < 0) { + warningf(false, + "j_suspend: setpgid() failed: %s", + strerror(errno)); + ttypgrp_ok = 0; + } else { + if (tcsetpgrp(tty_fd, kshpid) < 0) { + warningf(false, + "j_suspend: tcsetpgrp() failed: %s", + strerror(errno)); + ttypgrp_ok = 0; + } + } + } + tty_init(true); + } +} + +/* job cleanup before shell exit */ +void +j_exit(void) +{ + /* kill stopped, and possibly running, jobs */ + Job *j; + int killed = 0; + + for (j = job_list; j != NULL; j = j->next) { + if (j->ppid == procpid && + (j->state == PSTOPPED || + (j->state == PRUNNING && + ((j->flags & JF_FG) || + (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid))))) { + killed = 1; + if (j->pgrp == 0) + kill_job(j, SIGHUP); + else + killpg(j->pgrp, SIGHUP); +#ifdef JOBS + if (j->state == PSTOPPED) { + if (j->pgrp == 0) + kill_job(j, SIGCONT); + else + killpg(j->pgrp, SIGCONT); + } +#endif /* JOBS */ + } + } + if (killed) + sleep(1); + j_notify(); + +#ifdef JOBS + if (kshpid == procpid && restore_ttypgrp >= 0) { + /* Need to restore the tty pgrp to what it was when the + * shell started up, so that the process that started us + * will be able to access the tty when we are done. + * Also need to restore our process group in case we are + * about to do an exec so that both our parent and the + * process we are to become will be able to access the tty. + */ + tcsetpgrp(tty_fd, restore_ttypgrp); + setpgid(0, restore_ttypgrp); + } + if (Flag(FMONITOR)) { + Flag(FMONITOR) = 0; + j_change(); + } +#endif /* JOBS */ +} + +#ifdef JOBS +/* turn job control on or off according to Flag(FMONITOR) */ +void +j_change(void) +{ + int i; + + if (Flag(FMONITOR)) { + int use_tty; + + if (Flag(FTALKING)) { + /* Don't call tcgetattr() 'til we own the tty process group */ + use_tty = 1; + tty_init(false); + } else + use_tty = 0; + + /* no controlling tty, no SIGT* */ + ttypgrp_ok = use_tty && tty_fd >= 0 && tty_devtty; + + if (ttypgrp_ok && (our_pgrp = getpgrp()) < 0) { + warningf(false, "j_init: getpgrp() failed: %s", + strerror(errno)); + ttypgrp_ok = 0; + } + if (ttypgrp_ok) { + setsig(&sigtraps[SIGTTIN], SIG_DFL, + SS_RESTORE_ORIG|SS_FORCE); + /* wait to be given tty (POSIX.1, B.2, job control) */ + while (1) { + pid_t ttypgrp; + + if ((ttypgrp = tcgetpgrp(tty_fd)) < 0) { + warningf(false, + "j_init: tcgetpgrp() failed: %s", + strerror(errno)); + ttypgrp_ok = 0; + break; + } + if (ttypgrp == our_pgrp) + break; + kill(0, SIGTTIN); + } + } + for (i = NELEM(tt_sigs); --i >= 0; ) + setsig(&sigtraps[tt_sigs[i]], SIG_IGN, + SS_RESTORE_DFL|SS_FORCE); + if (ttypgrp_ok && our_pgrp != kshpid) { + if (setpgid(0, kshpid) < 0) { + warningf(false, + "j_init: setpgid() failed: %s", + strerror(errno)); + ttypgrp_ok = 0; + } else { + if (tcsetpgrp(tty_fd, kshpid) < 0) { + warningf(false, + "j_init: tcsetpgrp() failed: %s", + strerror(errno)); + ttypgrp_ok = 0; + } else + restore_ttypgrp = our_pgrp; + our_pgrp = kshpid; + } + } + if (use_tty) { + if (!ttypgrp_ok) + warningf(false, "warning: won't have full job control"); + } + if (tty_fd >= 0) + tcgetattr(tty_fd, &tty_state); + } else { + ttypgrp_ok = 0; + if (Flag(FTALKING)) + for (i = NELEM(tt_sigs); --i >= 0; ) + setsig(&sigtraps[tt_sigs[i]], SIG_IGN, + SS_RESTORE_IGN|SS_FORCE); + else + for (i = NELEM(tt_sigs); --i >= 0; ) { + if (sigtraps[tt_sigs[i]].flags & + (TF_ORIG_IGN | TF_ORIG_DFL)) + setsig(&sigtraps[tt_sigs[i]], + (sigtraps[tt_sigs[i]].flags & TF_ORIG_IGN) ? + SIG_IGN : SIG_DFL, + SS_RESTORE_ORIG|SS_FORCE); + } + if (!Flag(FTALKING)) + tty_close(); + } +} +#endif /* JOBS */ + +/* execute tree in child subprocess */ +int +exchild(struct op *t, int flags, volatile int *xerrok, + int close_fd) /* used if XPCLOSE or XCCLOSE */ +{ + static Proc *last_proc; /* for pipelines */ + + int i; + sigset_t omask; + Proc *p; + Job *j; + int rv = 0; + int forksleep; + int ischild; + + if (flags & XEXEC) + /* Clear XFORK|XPCLOSE|XCCLOSE|XCOPROC|XPIPEO|XPIPEI|XXCOM|XBGND + * (also done in another execute() below) + */ + return execute(t, flags & (XEXEC | XERROK), xerrok); + + /* no SIGCHLD's while messing with job and process lists */ + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + p = new_proc(); + p->next = NULL; + p->state = PRUNNING; + p->status = 0; + p->pid = 0; + + /* link process into jobs list */ + if (flags&XPIPEI) { /* continuing with a pipe */ + if (!last_job) + internal_errorf(1, + "exchild: XPIPEI and no last_job - pid %d", + (int) procpid); + j = last_job; + last_proc->next = p; + last_proc = p; + } else { + j = new_job(); /* fills in j->job */ + /* we don't consider XXCOM's foreground since they don't get + * tty process group and we don't save or restore tty modes. + */ + j->flags = (flags & XXCOM) ? JF_XXCOM : + ((flags & XBGND) ? 0 : (JF_FG|JF_USETTYMODE)); + timerclear(&j->usrtime); + timerclear(&j->systime); + j->state = PRUNNING; + j->pgrp = 0; + j->ppid = procpid; + j->age = ++njobs; + j->proc_list = p; + j->coproc_id = 0; + last_job = j; + last_proc = p; + put_job(j, PJ_PAST_STOPPED); + } + + snptreef(p->command, sizeof(p->command), "%T", t); + + /* create child process */ + forksleep = 1; + while ((i = fork()) < 0 && errno == EAGAIN && forksleep < 32) { + if (intrsig) /* allow user to ^C out... */ + break; + sleep(forksleep); + forksleep <<= 1; + } + if (i < 0) { + kill_job(j, SIGKILL); + remove_job(j, "fork failed"); + sigprocmask(SIG_SETMASK, &omask, NULL); + errorf("cannot fork - try again"); + } + ischild = i == 0; + if (ischild) + p->pid = procpid = getpid(); + else + p->pid = i; + +#ifdef JOBS + /* job control set up */ + if (Flag(FMONITOR) && !(flags&XXCOM)) { + int dotty = 0; + if (j->pgrp == 0) { /* First process */ + j->pgrp = p->pid; + dotty = 1; + } + + /* set pgrp in both parent and child to deal with race + * condition + */ + setpgid(p->pid, j->pgrp); + /* YYY: should this be + if (ttypgrp_ok && ischild && !(flags&XBGND)) + tcsetpgrp(tty_fd, j->pgrp); + instead? (see also YYY below) + */ + if (ttypgrp_ok && dotty && !(flags & XBGND)) + tcsetpgrp(tty_fd, j->pgrp); + } +#endif /* JOBS */ + + /* used to close pipe input fd */ + if (close_fd >= 0 && (((flags & XPCLOSE) && !ischild) || + ((flags & XCCLOSE) && ischild))) + close(close_fd); + if (ischild) { /* child */ + /* Do this before restoring signal */ + if (flags & XCOPROC) + coproc_cleanup(false); + sigprocmask(SIG_SETMASK, &omask, NULL); + cleanup_parents_env(); +#ifdef JOBS + /* If FMONITOR or FTALKING is set, these signals are ignored, + * if neither FMONITOR nor FTALKING are set, the signals have + * their inherited values. + */ + if (Flag(FMONITOR) && !(flags & XXCOM)) { + for (i = NELEM(tt_sigs); --i >= 0; ) + setsig(&sigtraps[tt_sigs[i]], SIG_DFL, + SS_RESTORE_DFL|SS_FORCE); + } +#endif /* JOBS */ + if (Flag(FBGNICE) && (flags & XBGND)) + nice(4); + if ((flags & XBGND) && !Flag(FMONITOR)) { + setsig(&sigtraps[SIGINT], SIG_IGN, + SS_RESTORE_IGN|SS_FORCE); + setsig(&sigtraps[SIGQUIT], SIG_IGN, + SS_RESTORE_IGN|SS_FORCE); + if (!(flags & (XPIPEI | XCOPROC))) { + int fd = open("/dev/null", O_RDONLY); + if (fd != 0) { + (void) ksh_dup2(fd, 0, true); + close(fd); + } + } + } + remove_job(j, "child"); /* in case of `jobs` command */ + nzombie = 0; +#ifdef JOBS + ttypgrp_ok = 0; + Flag(FMONITOR) = 0; +#endif /* JOBS */ + Flag(FTALKING) = 0; + tty_close(); + cleartraps(); + execute(t, (flags & XERROK) | XEXEC, NULL); /* no return */ + internal_errorf(0, "exchild: execute() returned"); + unwind(LLEAVE); + /* NOTREACHED */ + } + + /* shell (parent) stuff */ + /* Ensure next child gets a (slightly) different $RANDOM sequence */ + change_random(); + if (!(flags & XPIPEO)) { /* last process in a job */ +#ifdef JOBS + /* YYY: Is this needed? (see also YYY above) + if (Flag(FMONITOR) && !(flags&(XXCOM|XBGND))) + tcsetpgrp(tty_fd, j->pgrp); + */ +#endif /* JOBS */ + j_startjob(j); + if (flags & XCOPROC) { + j->coproc_id = coproc.id; + coproc.njobs++; /* n jobs using co-process output */ + coproc.job = (void *) j; /* j using co-process input */ + } + if (flags & XBGND) { + j_set_async(j); + if (Flag(FTALKING)) { + shf_fprintf(shl_out, "[%d]", j->job); + for (p = j->proc_list; p; p = p->next) + shf_fprintf(shl_out, " %d", p->pid); + shf_putchar('\n', shl_out); + shf_flush(shl_out); + } + } else + rv = j_waitj(j, JW_NONE, "jw:last proc"); + } + + sigprocmask(SIG_SETMASK, &omask, NULL); + + return rv; +} + +/* start the last job: only used for `command` jobs */ +void +startlast(void) +{ + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + if (last_job) { /* no need to report error - waitlast() will do it */ + /* ensure it isn't removed by check_job() */ + last_job->flags |= JF_WAITING; + j_startjob(last_job); + } + sigprocmask(SIG_SETMASK, &omask, NULL); +} + +/* wait for last job: only used for `command` jobs */ +int +waitlast(void) +{ + int rv; + Job *j; + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + j = last_job; + if (!j || !(j->flags & JF_STARTED)) { + if (!j) + warningf(true, "waitlast: no last job"); + else + internal_errorf(0, "waitlast: not started"); + sigprocmask(SIG_SETMASK, &omask, NULL); + return 125; /* not so arbitrary, non-zero value */ + } + + rv = j_waitj(j, JW_NONE, "jw:waitlast"); + + sigprocmask(SIG_SETMASK, &omask, NULL); + + return rv; +} + +/* wait for child, interruptable. */ +int +waitfor(const char *cp, int *sigp) +{ + int rv; + Job *j; + int ecode; + int flags = JW_INTERRUPT|JW_ASYNCNOTIFY; + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + *sigp = 0; + + if (cp == NULL) { + /* wait for an unspecified job - always returns 0, so + * don't have to worry about exited/signaled jobs + */ + for (j = job_list; j; j = j->next) + /* at&t ksh will wait for stopped jobs - we don't */ + if (j->ppid == procpid && j->state == PRUNNING) + break; + if (!j) { + sigprocmask(SIG_SETMASK, &omask, NULL); + return -1; + } + } else if ((j = j_lookup(cp, &ecode))) { + /* don't report normal job completion */ + flags &= ~JW_ASYNCNOTIFY; + if (j->ppid != procpid) { + sigprocmask(SIG_SETMASK, &omask, NULL); + return -1; + } + } else { + sigprocmask(SIG_SETMASK, &omask, NULL); + if (ecode != JL_NOSUCH) + bi_errorf("%s: %s", cp, lookup_msgs[ecode]); + return -1; + } + + /* at&t ksh will wait for stopped jobs - we don't */ + rv = j_waitj(j, flags, "jw:waitfor"); + + sigprocmask(SIG_SETMASK, &omask, NULL); + + if (rv < 0) /* we were interrupted */ + *sigp = 128 + -rv; + + return rv; +} + +/* kill (built-in) a job */ +int +j_kill(const char *cp, int sig) +{ + Job *j; + int rv = 0; + int ecode; + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + if ((j = j_lookup(cp, &ecode)) == NULL) { + sigprocmask(SIG_SETMASK, &omask, NULL); + bi_errorf("%s: %s", cp, lookup_msgs[ecode]); + return 1; + } + + if (j->pgrp == 0) { /* started when !Flag(FMONITOR) */ + if (kill_job(j, sig) < 0) { + bi_errorf("%s: %s", cp, strerror(errno)); + rv = 1; + } + } else { +#ifdef JOBS + if (j->state == PSTOPPED && (sig == SIGTERM || sig == SIGHUP)) + (void) killpg(j->pgrp, SIGCONT); +#endif /* JOBS */ + if (killpg(j->pgrp, sig) < 0) { + bi_errorf("%s: %s", cp, strerror(errno)); + rv = 1; + } + } + + sigprocmask(SIG_SETMASK, &omask, NULL); + + return rv; +} + +#ifdef JOBS +/* fg and bg built-ins: called only if Flag(FMONITOR) set */ +int +j_resume(const char *cp, int bg) +{ + Job *j; + Proc *p; + int ecode; + int running; + int rv = 0; + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + if ((j = j_lookup(cp, &ecode)) == NULL) { + sigprocmask(SIG_SETMASK, &omask, NULL); + bi_errorf("%s: %s", cp, lookup_msgs[ecode]); + return 1; + } + + if (j->pgrp == 0) { + sigprocmask(SIG_SETMASK, &omask, NULL); + bi_errorf("job not job-controlled"); + return 1; + } + + if (bg) + shprintf("[%d] ", j->job); + + running = 0; + for (p = j->proc_list; p != NULL; p = p->next) { + if (p->state == PSTOPPED) { + p->state = PRUNNING; + p->status = 0; + running = 1; + } + shprintf("%s%s", p->command, p->next ? "| " : ""); + } + shprintf("\n"); + shf_flush(shl_stdout); + if (running) + j->state = PRUNNING; + + put_job(j, PJ_PAST_STOPPED); + if (bg) + j_set_async(j); + else { +# ifdef JOBS + /* attach tty to job */ + if (j->state == PRUNNING) { + if (ttypgrp_ok && (j->flags & JF_SAVEDTTY)) + tcsetattr(tty_fd, TCSADRAIN, &j->ttystate); + /* See comment in j_waitj regarding saved_ttypgrp. */ + if (ttypgrp_ok && + tcsetpgrp(tty_fd, (j->flags & JF_SAVEDTTYPGRP) ? + j->saved_ttypgrp : j->pgrp) < 0) { + if (j->flags & JF_SAVEDTTY) + tcsetattr(tty_fd, TCSADRAIN, &tty_state); + sigprocmask(SIG_SETMASK, &omask, NULL); + bi_errorf("1st tcsetpgrp(%d, %d) failed: %s", + tty_fd, + (int) ((j->flags & JF_SAVEDTTYPGRP) ? + j->saved_ttypgrp : j->pgrp), + strerror(errno)); + return 1; + } + } +# endif /* JOBS */ + j->flags |= JF_FG; + j->flags &= ~JF_KNOWN; + if (j == async_job) + async_job = NULL; + } + + if (j->state == PRUNNING && killpg(j->pgrp, SIGCONT) < 0) { + int err = errno; + + if (!bg) { + j->flags &= ~JF_FG; +# ifdef JOBS + if (ttypgrp_ok && (j->flags & JF_SAVEDTTY)) + tcsetattr(tty_fd, TCSADRAIN, &tty_state); + if (ttypgrp_ok && tcsetpgrp(tty_fd, our_pgrp) < 0) { + warningf(true, + "fg: 2nd tcsetpgrp(%d, %d) failed: %s", + tty_fd, (int) our_pgrp, + strerror(errno)); + } +# endif /* JOBS */ + } + sigprocmask(SIG_SETMASK, &omask, NULL); + bi_errorf("cannot continue job %s: %s", + cp, strerror(err)); + return 1; + } + if (!bg) { +# ifdef JOBS + if (ttypgrp_ok) { + j->flags &= ~(JF_SAVEDTTY | JF_SAVEDTTYPGRP); + } +# endif /* JOBS */ + rv = j_waitj(j, JW_NONE, "jw:resume"); + } + sigprocmask(SIG_SETMASK, &omask, NULL); + return rv; +} +#endif /* JOBS */ + +/* are there any running or stopped jobs ? */ +int +j_stopped_running(void) +{ + Job *j; + int which = 0; + + for (j = job_list; j != NULL; j = j->next) { +#ifdef JOBS + if (j->ppid == procpid && j->state == PSTOPPED) + which |= 1; +#endif /* JOBS */ + if (Flag(FLOGIN) && !Flag(FNOHUP) && procpid == kshpid && + j->ppid == procpid && j->state == PRUNNING) + which |= 2; + } + if (which) { + shellf("You have %s%s%s jobs\n", + which & 1 ? "stopped" : "", + which == 3 ? " and " : "", + which & 2 ? "running" : ""); + return 1; + } + + return 0; +} + +int +j_njobs(void) +{ + Job *j; + int nj = 0; + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + for (j = job_list; j; j = j->next) + nj++; + + sigprocmask(SIG_SETMASK, &omask, NULL); + return nj; +} + + +/* list jobs for jobs built-in */ +int +j_jobs(const char *cp, int slp, + int nflag) /* 0: short, 1: long, 2: pgrp */ +{ + Job *j, *tmp; + int how; + int zflag = 0; + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + if (nflag < 0) { /* kludge: print zombies */ + nflag = 0; + zflag = 1; + } + if (cp) { + int ecode; + + if ((j = j_lookup(cp, &ecode)) == NULL) { + sigprocmask(SIG_SETMASK, &omask, NULL); + bi_errorf("%s: %s", cp, lookup_msgs[ecode]); + return 1; + } + } else + j = job_list; + how = slp == 0 ? JP_MEDIUM : (slp == 1 ? JP_LONG : JP_PGRP); + for (; j; j = j->next) { + if ((!(j->flags & JF_ZOMBIE) || zflag) && + (!nflag || (j->flags & JF_CHANGED))) { + j_print(j, how, shl_stdout); + if (j->state == PEXITED || j->state == PSIGNALLED) + j->flags |= JF_REMOVE; + } + if (cp) + break; + } + /* Remove jobs after printing so there won't be multiple + or - jobs */ + for (j = job_list; j; j = tmp) { + tmp = j->next; + if (j->flags & JF_REMOVE) + remove_job(j, "jobs"); + } + sigprocmask(SIG_SETMASK, &omask, NULL); + return 0; +} + +/* list jobs for top-level notification */ +void +j_notify(void) +{ + Job *j, *tmp; + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + for (j = job_list; j; j = j->next) { +#ifdef JOBS + if (Flag(FMONITOR) && (j->flags & JF_CHANGED)) + j_print(j, JP_MEDIUM, shl_out); +#endif /* JOBS */ + /* Remove job after doing reports so there aren't + * multiple +/- jobs. + */ + if (j->state == PEXITED || j->state == PSIGNALLED) + j->flags |= JF_REMOVE; + } + for (j = job_list; j; j = tmp) { + tmp = j->next; + if (j->flags & JF_REMOVE) + remove_job(j, "notify"); + } + shf_flush(shl_out); + sigprocmask(SIG_SETMASK, &omask, NULL); +} + +/* Return pid of last process in last asynchronous job */ +pid_t +j_async(void) +{ + sigset_t omask; + + sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); + + if (async_job) + async_job->flags |= JF_KNOWN; + + sigprocmask(SIG_SETMASK, &omask, NULL); + + return async_pid; +} + +/* Make j the last async process + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static void +j_set_async(Job *j) +{ + Job *jl, *oldest; + + if (async_job && (async_job->flags & (JF_KNOWN|JF_ZOMBIE)) == JF_ZOMBIE) + remove_job(async_job, "async"); + if (!(j->flags & JF_STARTED)) { + internal_errorf(0, "j_async: job not started"); + return; + } + async_job = j; + async_pid = j->last_proc->pid; + while (nzombie > child_max) { + oldest = NULL; + for (jl = job_list; jl; jl = jl->next) + if (jl != async_job && (jl->flags & JF_ZOMBIE) && + (!oldest || jl->age < oldest->age)) + oldest = jl; + if (!oldest) { + /* XXX debugging */ + if (!(async_job->flags & JF_ZOMBIE) || nzombie != 1) { + internal_errorf(0, + "j_async: bad nzombie (%d)", nzombie); + nzombie = 0; + } + break; + } + remove_job(oldest, "zombie"); + } +} + +/* Start a job: set STARTED, check for held signals and set j->last_proc + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static void +j_startjob(Job *j) +{ + Proc *p; + + j->flags |= JF_STARTED; + for (p = j->proc_list; p->next; p = p->next) + ; + j->last_proc = p; + + if (held_sigchld) { + held_sigchld = 0; + /* Don't call j_sigchld() as it may remove job... */ + kill(procpid, SIGCHLD); + } +} + +/* + * wait for job to complete or change state + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static int +j_waitj(Job *j, + int flags, /* see JW_* */ + const char *where) +{ + int rv; + + /* + * No auto-notify on the job we are waiting on. + */ + j->flags |= JF_WAITING; + if (flags & JW_ASYNCNOTIFY) + j->flags |= JF_W_ASYNCNOTIFY; + + if (!Flag(FMONITOR)) + flags |= JW_STOPPEDWAIT; + + while ((volatile int) j->state == PRUNNING || + ((flags & JW_STOPPEDWAIT) && (volatile int) j->state == PSTOPPED)) { + sigsuspend(&sm_default); + if (fatal_trap) { + int oldf = j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY); + j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); + runtraps(TF_FATAL); + j->flags |= oldf; /* not reached... */ + } + if ((flags & JW_INTERRUPT) && (rv = trap_pending())) { + j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); + return -rv; + } + } + j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); + + if (j->flags & JF_FG) { + int status; + + j->flags &= ~JF_FG; +#ifdef JOBS + if (Flag(FMONITOR) && ttypgrp_ok && j->pgrp) { + /* + * Save the tty's current pgrp so it can be restored + * when the job is foregrounded. This is to + * deal with things like the GNU su which does + * a fork/exec instead of an exec (the fork means + * the execed shell gets a different pid from its + * pgrp, so naturally it sets its pgrp and gets hosed + * when it gets foregrounded by the parent shell, which + * has restored the tty's pgrp to that of the su + * process). + */ + if (j->state == PSTOPPED && + (j->saved_ttypgrp = tcgetpgrp(tty_fd)) >= 0) + j->flags |= JF_SAVEDTTYPGRP; + if (tcsetpgrp(tty_fd, our_pgrp) < 0) { + warningf(true, + "j_waitj: tcsetpgrp(%d, %d) failed: %s", + tty_fd, (int) our_pgrp, + strerror(errno)); + } + if (j->state == PSTOPPED) { + j->flags |= JF_SAVEDTTY; + tcgetattr(tty_fd, &j->ttystate); + } + } +#endif /* JOBS */ + if (tty_fd >= 0) { + /* Only restore tty settings if job was originally + * started in the foreground. Problems can be + * caused by things like `more foobar &' which will + * typically get and save the shell's vi/emacs tty + * settings before setting up the tty for itself; + * when more exits, it restores the `original' + * settings, and things go down hill from there... + */ + if (j->state == PEXITED && j->status == 0 && + (j->flags & JF_USETTYMODE)) { + tcgetattr(tty_fd, &tty_state); + } else { + tcsetattr(tty_fd, TCSADRAIN, &tty_state); + /* Don't use tty mode if job is stopped and + * later restarted and exits. Consider + * the sequence: + * vi foo (stopped) + * ... + * stty something + * ... + * fg (vi; ZZ) + * mode should be that of the stty, not what + * was before the vi started. + */ + if (j->state == PSTOPPED) + j->flags &= ~JF_USETTYMODE; + } + } +#ifdef JOBS + /* If it looks like user hit ^C to kill a job, pretend we got + * one too to break out of for loops, etc. (at&t ksh does this + * even when not monitoring, but this doesn't make sense since + * a tty generated ^C goes to the whole process group) + */ + status = j->last_proc->status; + if (Flag(FMONITOR) && j->state == PSIGNALLED && + WIFSIGNALED(status) && + (sigtraps[WTERMSIG(status)].flags & TF_TTY_INTR)) + trapsig(WTERMSIG(status)); +#endif /* JOBS */ + } + + j_usrtime = j->usrtime; + j_systime = j->systime; + rv = j->status; + + if (!(flags & JW_ASYNCNOTIFY) && + (!Flag(FMONITOR) || j->state != PSTOPPED)) { + j_print(j, JP_SHORT, shl_out); + shf_flush(shl_out); + } + if (j->state != PSTOPPED && + (!Flag(FMONITOR) || !(flags & JW_ASYNCNOTIFY))) + remove_job(j, where); + + return rv; +} + +/* SIGCHLD handler to reap children and update job states + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static void +j_sigchld(int sig) +{ + int errno_ = errno; + Job *j; + Proc *p = NULL; + int pid; + int status; + struct rusage ru0, ru1; + + /* Don't wait for any processes if a job is partially started. + * This is so we don't do away with the process group leader + * before all the processes in a pipe line are started (so the + * setpgid() won't fail) + */ + for (j = job_list; j; j = j->next) + if (j->ppid == procpid && !(j->flags & JF_STARTED)) { + held_sigchld = 1; + goto finished; + } + + getrusage(RUSAGE_CHILDREN, &ru0); + do { + pid = waitpid(-1, &status, (WNOHANG|WUNTRACED)); + + if (pid <= 0) /* return if would block (0) ... */ + break; /* ... or no children or interrupted (-1) */ + + getrusage(RUSAGE_CHILDREN, &ru1); + + /* find job and process structures for this pid */ + for (j = job_list; j != NULL; j = j->next) + for (p = j->proc_list; p != NULL; p = p->next) + if (p->pid == pid) + goto found; +found: + if (j == NULL) { + /* Can occur if process has kids, then execs shell + warningf(true, "bad process waited for (pid = %d)", + pid); + */ + ru0 = ru1; + continue; + } + + timeradd(&j->usrtime, &ru1.ru_utime, &j->usrtime); + timersub(&j->usrtime, &ru0.ru_utime, &j->usrtime); + timeradd(&j->systime, &ru1.ru_stime, &j->systime); + timersub(&j->systime, &ru0.ru_stime, &j->systime); + ru0 = ru1; + p->status = status; +#ifdef JOBS + if (WIFSTOPPED(status)) + p->state = PSTOPPED; + else +#endif /* JOBS */ + if (WIFSIGNALED(status)) + p->state = PSIGNALLED; + else + p->state = PEXITED; + + check_job(j); /* check to see if entire job is done */ + } while (1); + +finished: + errno = errno_; +} + +/* + * Called only when a process in j has exited/stopped (ie, called only + * from j_sigchld()). If no processes are running, the job status + * and state are updated, asynchronous job notification is done and, + * if unneeded, the job is removed. + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static void +check_job(Job *j) +{ + int jstate; + Proc *p; + + /* XXX debugging (nasty - interrupt routine using shl_out) */ + if (!(j->flags & JF_STARTED)) { + internal_errorf(0, "check_job: job started (flags 0x%x)", + j->flags); + return; + } + + jstate = PRUNNING; + for (p=j->proc_list; p != NULL; p = p->next) { + if (p->state == PRUNNING) + return; /* some processes still running */ + if (p->state > jstate) + jstate = p->state; + } + j->state = jstate; + + switch (j->last_proc->state) { + case PEXITED: + j->status = WEXITSTATUS(j->last_proc->status); + break; + case PSIGNALLED: + j->status = 128 + WTERMSIG(j->last_proc->status); + break; + default: + j->status = 0; + break; + } + + /* Note when co-process dies: can't be done in j_wait() nor + * remove_job() since neither may be called for non-interactive + * shells. + */ + if (j->state == PEXITED || j->state == PSIGNALLED) { + /* No need to keep co-process input any more + * (at least, this is what ksh93d thinks) + */ + if (coproc.job == j) { + coproc.job = NULL; + /* XXX would be nice to get the closes out of here + * so they aren't done in the signal handler. + * Would mean a check in coproc_getfd() to + * do "if job == 0 && write >= 0, close write". + */ + coproc_write_close(coproc.write); + } + /* Do we need to keep the output? */ + if (j->coproc_id && j->coproc_id == coproc.id && + --coproc.njobs == 0) + coproc_readw_close(coproc.read); + } + + j->flags |= JF_CHANGED; +#ifdef JOBS + if (Flag(FMONITOR) && !(j->flags & JF_XXCOM)) { + /* Only put stopped jobs at the front to avoid confusing + * the user (don't want finished jobs effecting %+ or %-) + */ + if (j->state == PSTOPPED) + put_job(j, PJ_ON_FRONT); + if (Flag(FNOTIFY) && + (j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY)) != JF_WAITING) { + /* Look for the real file descriptor 2 */ + { + struct env *ep; + int fd = 2; + + for (ep = genv; ep; ep = ep->oenv) + if (ep->savefd && ep->savefd[2]) + fd = ep->savefd[2]; + shf_reopen(fd, SHF_WR, shl_j); + } + /* Can't call j_notify() as it removes jobs. The job + * must stay in the job list as j_waitj() may be + * running with this job. + */ + j_print(j, JP_MEDIUM, shl_j); + shf_flush(shl_j); + if (!(j->flags & JF_WAITING) && j->state != PSTOPPED) + remove_job(j, "notify"); + } + } +#endif /* JOBS */ + if (!Flag(FMONITOR) && !(j->flags & (JF_WAITING|JF_FG)) && + j->state != PSTOPPED) { + if (j == async_job || (j->flags & JF_KNOWN)) { + j->flags |= JF_ZOMBIE; + j->job = -1; + nzombie++; + } else + remove_job(j, "checkjob"); + } +} + +/* + * Print job status in either short, medium or long format. + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static void +j_print(Job *j, int how, struct shf *shf) +{ + Proc *p; + int state; + int status; + int coredumped; + char jobchar = ' '; + char buf[64]; + const char *filler; + int output = 0; + + if (how == JP_PGRP) { + /* POSIX doesn't say what to do it there is no process + * group leader (ie, !FMONITOR). We arbitrarily return + * last pid (which is what $! returns). + */ + shf_fprintf(shf, "%d\n", j->pgrp ? j->pgrp : + (j->last_proc ? j->last_proc->pid : 0)); + return; + } + j->flags &= ~JF_CHANGED; + filler = j->job > 10 ? "\n " : "\n "; + if (j == job_list) + jobchar = '+'; + else if (j == job_list->next) + jobchar = '-'; + + for (p = j->proc_list; p != NULL;) { + coredumped = 0; + switch (p->state) { + case PRUNNING: + strlcpy(buf, "Running", sizeof buf); + break; + case PSTOPPED: + strlcpy(buf, sigtraps[WSTOPSIG(p->status)].mess, + sizeof buf); + break; + case PEXITED: + if (how == JP_SHORT) + buf[0] = '\0'; + else if (WEXITSTATUS(p->status) == 0) + strlcpy(buf, "Done", sizeof buf); + else + shf_snprintf(buf, sizeof(buf), "Done (%d)", + WEXITSTATUS(p->status)); + break; + case PSIGNALLED: + if (WCOREDUMP(p->status)) + coredumped = 1; + /* kludge for not reporting `normal termination signals' + * (ie, SIGINT, SIGPIPE) + */ + if (how == JP_SHORT && !coredumped && + (WTERMSIG(p->status) == SIGINT || + WTERMSIG(p->status) == SIGPIPE)) { + buf[0] = '\0'; + } else + strlcpy(buf, sigtraps[WTERMSIG(p->status)].mess, + sizeof buf); + break; + } + + if (how != JP_SHORT) { + if (p == j->proc_list) + shf_fprintf(shf, "[%d] %c ", j->job, jobchar); + else + shf_fprintf(shf, "%s", filler); + } + + if (how == JP_LONG) + shf_fprintf(shf, "%5d ", p->pid); + + if (how == JP_SHORT) { + if (buf[0]) { + output = 1; + shf_fprintf(shf, "%s%s ", + buf, coredumped ? " (core dumped)" : ""); + } + } else { + output = 1; + shf_fprintf(shf, "%-20s %s%s%s", buf, p->command, + p->next ? "|" : "", + coredumped ? " (core dumped)" : ""); + } + + state = p->state; + status = p->status; + p = p->next; + while (p && p->state == state && p->status == status) { + if (how == JP_LONG) + shf_fprintf(shf, "%s%5d %-20s %s%s", filler, p->pid, + " ", p->command, p->next ? "|" : ""); + else if (how == JP_MEDIUM) + shf_fprintf(shf, " %s%s", p->command, + p->next ? "|" : ""); + p = p->next; + } + } + if (output) + shf_fprintf(shf, "\n"); +} + +/* Convert % sequence to job + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static Job * +j_lookup(const char *cp, int *ecodep) +{ + Job *j, *last_match; + const char *errstr; + Proc *p; + int len, job = 0; + + if (digit(*cp)) { + job = strtonum(cp, 1, INT_MAX, &errstr); + if (errstr) { + if (ecodep) + *ecodep = JL_NOSUCH; + return NULL; + } + /* Look for last_proc->pid (what $! returns) first... */ + for (j = job_list; j != NULL; 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 != NULL; j = j->next) + if (j->pgrp && j->pgrp == job) + return j; + if (ecodep) + *ecodep = JL_NOSUCH; + return NULL; + } + if (*cp != '%') { + if (ecodep) + *ecodep = JL_INVALID; + return NULL; + } + switch (*++cp) { + case '\0': /* non-standard */ + case '+': + case '%': + if (job_list != NULL) + return job_list; + break; + + case '-': + if (job_list != NULL && 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 = strtonum(cp, 1, INT_MAX, &errstr); + if (errstr) + break; + for (j = job_list; j != NULL; j = j->next) + if (j->job == job) + return j; + break; + + case '?': /* %?string */ + last_match = NULL; + for (j = job_list; j != NULL; j = j->next) + for (p = j->proc_list; p != NULL; p = p->next) + if (strstr(p->command, cp+1) != NULL) { + if (last_match) { + if (ecodep) + *ecodep = JL_AMBIG; + return NULL; + } + last_match = j; + } + if (last_match) + return last_match; + break; + + default: /* %string */ + len = strlen(cp); + last_match = NULL; + for (j = job_list; j != NULL; j = j->next) + if (strncmp(cp, j->proc_list->command, len) == 0) { + if (last_match) { + if (ecodep) + *ecodep = JL_AMBIG; + return NULL; + } + last_match = j; + } + if (last_match) + return last_match; + break; + } + if (ecodep) + *ecodep = JL_NOSUCH; + return NULL; +} + +static Job *free_jobs; +static Proc *free_procs; + +/* allocate a new job and fill in the job number. + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static Job * +new_job(void) +{ + int i; + Job *newj, *j; + + if (free_jobs != NULL) { + newj = free_jobs; + free_jobs = free_jobs->next; + } else + newj = alloc(sizeof(Job), APERM); + + /* brute force method */ + for (i = 1; ; i++) { + for (j = job_list; j && j->job != i; j = j->next) + ; + if (j == NULL) + break; + } + newj->job = i; + + return newj; +} + +/* Allocate new process struct + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static Proc * +new_proc(void) +{ + Proc *p; + + if (free_procs != NULL) { + p = free_procs; + free_procs = free_procs->next; + } else + p = alloc(sizeof(Proc), APERM); + + return p; +} + +/* Take job out of job_list and put old structures into free list. + * Keeps nzombies, last_job and async_job up to date. + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static void +remove_job(Job *j, const char *where) +{ + Proc *p, *tmp; + Job **prev, *curr; + + prev = &job_list; + curr = *prev; + for (; curr != NULL && 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 != NULL; ) { + 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 = NULL; + if (j == async_job) + async_job = NULL; +} + +/* put j in a particular location (taking it out job_list if it is there + * already) + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static void +put_job(Job *j, int where) +{ + Job **prev, *curr; + + /* Remove job from list (if there) */ + prev = &job_list; + curr = job_list; + for (; curr && curr != j; prev = &curr->next, curr = *prev) + ; + if (curr == j) + *prev = curr->next; + + switch (where) { + case PJ_ON_FRONT: + j->next = job_list; + job_list = j; + break; + + case PJ_PAST_STOPPED: + prev = &job_list; + curr = job_list; + for (; curr && curr->state == PSTOPPED; prev = &curr->next, + curr = *prev) + ; + j->next = curr; + *prev = j; + break; + } +} + +/* nuke a job (called when unable to start full job). + * + * If jobs are compiled in then this routine expects sigchld to be blocked. + */ +static int +kill_job(Job *j, int sig) +{ + Proc *p; + int rval = 0; + + for (p = j->proc_list; p != NULL; p = p->next) + if (p->pid != 0) + if (kill(p->pid, sig) < 0) + rval = -1; + return rval; +} diff --git a/bin/ksh/ksh.1 b/bin/ksh/ksh.1 @@ -0,0 +1,5563 @@ +.\" $OpenBSD: ksh.1,v 1.179 2016/04/27 12:46:23 naddy Exp $ +.\" +.\" Public Domain +.\" +.Dd $Mdocdate: April 27 2016 $ +.Dt KSH 1 +.Os +.Sh NAME +.Nm ksh , +.Nm rksh +.Nd public domain Korn shell +.Sh SYNOPSIS +.Nm ksh +.Bk -words +.Op Fl +abCefhiklmnpruvXx +.Op Fl +o Ar option +.Op Fl c Ar string | Fl s | Ar file Op Ar argument ... +.Ek +.Sh DESCRIPTION +.Nm +is a command interpreter intended for both interactive and shell +script use. +Its command language is a superset of the +.Xr sh 1 +shell language. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl c Ar string +.Nm +will execute the command(s) contained in +.Ar string . +.It Fl i +Interactive shell. +A shell is +.Dq interactive +if this +option is used or if both standard input and standard error are attached +to a +.Xr tty 4 . +An interactive shell has job control enabled, ignores the +.Dv SIGINT , +.Dv SIGQUIT , +and +.Dv SIGTERM +signals, and prints prompts before reading input (see the +.Ev PS1 +and +.Ev PS2 +parameters). +For non-interactive shells, the +.Ic trackall +option is on by default (see the +.Ic set +command below). +.It Fl l +Login shell. +If the basename the shell is called with (i.e. argv[0]) +starts with +.Ql - +or if this option is used, +the shell is assumed to be a login shell and the shell reads and executes +the contents of +.Pa /etc/profile +and +.Pa $HOME/.profile +if they exist and are readable. +.It Fl p +Privileged shell. +A shell is +.Dq privileged +if this option is used +or if the real user ID or group ID does not match the +effective user ID or group ID (see +.Xr getuid 2 +and +.Xr getgid 2 ) . +A privileged shell does not process +.Pa $HOME/.profile +nor the +.Ev ENV +parameter (see below). +Instead, the file +.Pa /etc/suid_profile +is processed. +Clearing the privileged option causes the shell to set +its effective user ID (group ID) to its real user ID (group ID). +.It Fl r +Restricted shell. +A shell is +.Dq restricted +if this +option is used; +if the basename the shell was invoked with was +.Dq rksh ; +or if the