From d8d708aa45bb282965c4fc8c8438c29340cd1e2c Mon Sep 17 00:00:00 2001 From: tg Date: Fri, 21 Oct 2005 11:33:15 +0000 Subject: [PATCH] * un-hook bin/ksh, usr.bin/xmlwf and lib/libexpat from the build - expat as discussed with bsiegert@ today on the phone - ksh as announced earlier on the lists * un-hook lib/libexpat from make includes * remove /usr/include/{,open}ssl upgrade workaround from includes/Makefile * nuke old bin/ksh * nuke libexpat and xmlwf --- CONTRIBUTORS | 133 -- Makefile | 27 - NOTES | 305 --- README | 78 - alloc.c | 122 -- c_ksh.c | 1406 ------------ c_sh.c | 861 -------- c_test.c | 626 ------ c_test.h | 61 - c_ulimit.c | 284 --- conf-end.h | 82 - config.h | 322 --- edit.c | 955 --------- edit.h | 74 - emacs-gen.sh | 45 - emacs.c | 2026 ----------------- eval.c | 1310 ----------- exec.c | 1497 ------------- expand.h | 113 - expr.c | 580 ----- history.c | 1113 ---------- io.c | 476 ---- jobs.c | 1816 ---------------- ksh.1 | 5367 ---------------------------------------------- ksh_stat.h | 71 - ksh_wait.h | 57 - lex.c | 1301 ----------- lex.h | 131 -- main.c | 751 ------- misc.c | 1329 ------------ missing.c | 148 -- path.c | 309 --- proto.h | 297 --- sh.h | 660 ------ shf.c | 1226 ----------- shf.h | 87 - syn.c | 910 -------- table.c | 231 -- table.h | 182 -- tests/alias.t | 111 - tests/arith.t | 78 - tests/bksl-nl.t | 340 --- tests/brkcont.t | 193 -- tests/cdhist.t | 162 -- tests/eglob.t | 144 -- tests/glob.t | 98 - tests/heredoc.t | 330 --- tests/history.t | 560 ----- tests/ifs.t | 175 -- tests/integer.t | 217 -- tests/lineno.t | 110 - tests/read.t | 57 - tests/regress.t | 1091 ---------- tests/syntax.t | 9 - tests/th | 1205 ----------- tests/unclass1.t | 99 - tests/unclass2.t | 162 -- tests/version.t | 9 - trap.c | 426 ---- tree.c | 695 ------ tree.h | 146 -- tty.c | 63 - tty.h | 44 - var.c | 1163 ---------- vi.c | 2114 ------------------ 65 files changed, 37170 deletions(-) delete mode 100644 CONTRIBUTORS delete mode 100644 Makefile delete mode 100644 NOTES delete mode 100644 README delete mode 100644 alloc.c delete mode 100644 c_ksh.c delete mode 100644 c_sh.c delete mode 100644 c_test.c delete mode 100644 c_test.h delete mode 100644 c_ulimit.c delete mode 100644 conf-end.h delete mode 100644 config.h delete mode 100644 edit.c delete mode 100644 edit.h delete mode 100644 emacs-gen.sh delete mode 100644 emacs.c delete mode 100644 eval.c delete mode 100644 exec.c delete mode 100644 expand.h delete mode 100644 expr.c delete mode 100644 history.c delete mode 100644 io.c delete mode 100644 jobs.c delete mode 100644 ksh.1 delete mode 100644 ksh_stat.h delete mode 100644 ksh_wait.h delete mode 100644 lex.c delete mode 100644 lex.h delete mode 100644 main.c delete mode 100644 misc.c delete mode 100644 missing.c delete mode 100644 path.c delete mode 100644 proto.h delete mode 100644 sh.h delete mode 100644 shf.c delete mode 100644 shf.h delete mode 100644 syn.c delete mode 100644 table.c delete mode 100644 table.h delete mode 100644 tests/alias.t delete mode 100644 tests/arith.t delete mode 100644 tests/bksl-nl.t delete mode 100644 tests/brkcont.t delete mode 100644 tests/cdhist.t delete mode 100644 tests/eglob.t delete mode 100644 tests/glob.t delete mode 100644 tests/heredoc.t delete mode 100644 tests/history.t delete mode 100644 tests/ifs.t delete mode 100644 tests/integer.t delete mode 100644 tests/lineno.t delete mode 100644 tests/read.t delete mode 100644 tests/regress.t delete mode 100644 tests/syntax.t delete mode 100644 tests/th delete mode 100644 tests/unclass1.t delete mode 100644 tests/unclass2.t delete mode 100644 tests/version.t delete mode 100644 trap.c delete mode 100644 tree.c delete mode 100644 tree.h delete mode 100644 tty.c delete mode 100644 tty.h delete mode 100644 var.c delete mode 100644 vi.c diff --git a/CONTRIBUTORS b/CONTRIBUTORS deleted file mode 100644 index d83c839..0000000 --- a/CONTRIBUTORS +++ /dev/null @@ -1,133 +0,0 @@ -$MirOS$ -$OpenBSD: CONTRIBUTORS,v 1.9 1999/07/14 13:37:23 millert Exp $ - -This is a partial history of this shell gleened from old change logs and -readmes and 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 - * Thorsten Glaser (tg@66h.42h.de) releases mksh and is the guy - to blame for all mirbsdksh-specific or MirOS-specific bugs. - Released versions: 5.2.14 MirOS 1.1-1.x - -Major contributions: - * John R MacMillan (@yonge.csri.toronto.edu:chance!john@sq.sq.com), ?: - cleaned up configuration, many bug fixes (see misc/Changes.jrm). - * Simon Gerraty, (sjg@zen.void.oz.au), Nov '91..?: much improved emacs mode - ala at&t ksh, 386bsd port, sigaction routines for non-POSIX systems - (see misc/ChangeLog.sjg and misc/ReadME.sjg). - * Peter Collinson (pc@hillside.co.uk), July '92: added select, at&t ksh - style history file, original csh-style {} globbing, BSD/386 port, - misc bug fixes. - * Larry Bouzane (larry@compusult.nf.ca), Mar '89..'93: re-wrote job control, - added async job notification, added CDPATH and other cd fixes, misc bug - fixes. - * John Rochester (jr@cs.mun.ca), '87: wrote vi command line editor; various - bug fixes/enhancements. - * Jeff Sparkes (jsparkes@bnr.ca), Mar '89..Mar '90: added arrays, - merged John Rochester's vi code into pdksh, misc bug fixes. - * Michael Haardt (u31b3hs@POOL.Informatik.RWTH-Aachen.DE), Sept '94: - organized man page, filled in many of its copious blank spots; added - KSH ifdefs. - * Dale DePriest (daled@cadence.com): ported to OS/2 (initially based on - port of pdksh4.9 to OS/2 by Kai Rommel (rommel@ars.muc.de)); maintains - OS/2 port; misc bug fixes. - -Other contributors: - * Piercarlo Grandi (pcg@aber.ac.uk), Dec '93: fixes for linux port - * Neil Smithline (Neil.Smithline@eng.sun.com), Aug '92: emacs-style - filename completion. - * Mike Jetzer [mlj] (jetzer@studsys.mscs.mu.edu), ?;Nov '94: fixes for vi - mode (see misc/Changes.mlj), added v to vi, fixes for history; fixed - command redoing in vi; fixes to vi globbing. - * Robert J Gibson: mailbox checking code that was adapted for pdksh by - John R. MacMillan. - * ? (guy@demon.co.uk), ?: promptlen() function. - * J.T. Conklin (jtc@cygnus.com): POSIXized test builtin; miscellaneous - fixes/enhancements. - * Sean Hogan (sean@neweast.ca): fixes for ICS 3.0 Unix, found and helped - fix numerous problems. - * Gordan Larson (hoh@approve.se): fix to compile sans VI, ksh.1 typo. - * Thomas Gellekum (thomas@ghpc8.ihf.rwth-aachen.de): fixes for Makefile - typos, fixed CLK_TCK for FreeBSD, man page fixes. - * Ed Ferguson (Ed.Ferguson@dseg.ti.com): fix to compile sans VI. - * Brian Campbell (brianc@qnx.com): fixes to compile under QNX and - to compile with dmake. - * (guy@netapp.com), Oct '94: patch to use gmacs flag. - * Andrew Moore (alm@netcom.com): reported many bugs, fixes. - * William Bader (wbader@CSEE.Lehigh.Edu): fix to compile on SCO Unix - (strut winsize). - * Mike Long (mike.long@analog.com): makefile fix - use $manext, not 1. - * Art Mills (aem@hpbs9162.bio.hp.com): bug fix for vi file completion in - command mode. - * Tory Bollinger (tboll@authstin.ibm.com): allow ~ in vi mode to take - a count. - * Frank Edwards (): added macros to vi (@char). - * Fritz Heinrichmeyer (): fixes - to allow compile under Linux 1.4.3. - * Gabor Zahemszky (): SVR3_PGRP vs SYSV_PGRP, many - bug reports and man page fixes. - * Dave Kinchlea (): DEFAULT_ENV patches. - * Paul Borman (): j_exit: send HUP, then CONT; HUP fg process. - * DaviD W. Sanderson (): patches to allow { .. } instead - of in .. esac in case statements. - * ? (): partial patches to handle SIGWINCH for command line - editing. - * Jason Tyler (): fixes for bugs in fc. - * Stefan Dalibor (): fix for - COLUMNS never being set in x_init(). - * Arnon Kanfi (): fix for prompt. - * Marc Olzheim (): patches to ifdef KSH the mail check - code and aliases; enum patches for old K&R compilers; handle missing dup2. - * Lars Hecking (): fixes so shell compiles as sh - again. - * Bill Kish (): added prompt delimiter hack for - hidden characters (eg, escape codes). - * Andrew S. Townley (): fixes for NeXT machines: - get a controlling if one needed, use correct profile. - * Eric J. Chet (): fix for core dump in . (quitenv() called - too soon). - * Greg A. Woods : fix to make ^[_ in emacs work - as in at&t ksh. - * George Robbins : fix for sh mode to - keep exec'd file descriptors open. - * George White : fix here-doc problem under OS/2 - (memory allocated incorrectly). - * David E. Wexelblat : fix to avoid memory overrun - in aresize(); fix to not print un-named options. - * Clifford Wolf (): fix memory overrun in aresize(); - fixed sys_siglist[] problem. - * Theo de Raadt (): allow ". /dev/null". - * Eric Youngdale (): flag field incorrectly changed - in exec.c(flushcom). - * Todd. C Miller (Todd C. Miller ): fix - for coredump in jobs. - * Kevin Schoedel : fix for word location in file - completion. - * Martin Lucina : fix for argument parsing in exit command, - fix for KSH_CHECK_H_TYPE. - * Mark Funkenhauser : added $LINENO support. - * Corinna Vinschen and Steven Hein : - port to cyngin environment on win95/winnt. - * Martin Dalecki : changes for 8 bit emacs mode. - * Dave Hillman : patch for bug in test -nt. diff --git a/Makefile b/Makefile deleted file mode 100644 index fde5f7d..0000000 --- a/Makefile +++ /dev/null @@ -1,27 +0,0 @@ -# $MirOS: src/bin/ksh/Makefile,v 1.4 2005/08/29 20:54:39 tg Exp $ -# $OpenBSD: Makefile,v 1.18 2004/02/16 19:07:19 deraadt Exp $ - -PROG= ksh -SRCS= alloc.c c_ksh.c c_sh.c c_test.c c_ulimit.c edit.c emacs.c eval.c \ - exec.c expr.c history.c io.c jobs.c lex.c main.c misc.c missing.c \ - path.c shf.c syn.c table.c trap.c tree.c tty.c var.c vi.c -MAN= ksh.1 - -.include -COPTS+= -Wall -Wextra -pedantic -CPPFLAGS+= -DHAVE_CONFIG_H -I. -DMIRBSD_NATIVE -CLEANFILES+= emacs.out - -LINKS= ${BINDIR}/ksh ${BINDIR}/rksh -MLINKS= ksh.1 rksh.1 - -beforedepend: emacs.out - -emacs.out: emacs.c - ${SHELL} ${.CURDIR}/emacs-gen.sh ${.CURDIR}/emacs.c >emacs.out - -check: - /usr/bin/perl ${.CURDIR}/tests/th -s ${.CURDIR}/tests \ - -p ./ksh -C pdksh,sh,ksh,posix,posix-upu - -.include diff --git a/NOTES b/NOTES deleted file mode 100644 index db18e2e..0000000 --- a/NOTES +++ /dev/null @@ -1,305 +0,0 @@ -$MirOS: src/bin/ksh/NOTES,v 1.1.7.1 2005/03/06 15:42:53 tg Exp $ -$OpenBSD: NOTES,v 1.9 2003/10/26 15:07:25 jmc Exp $ - - -* http://www.research.att.com/~gsf/public/ifs.sh - # tests 6856 passed 5192 failed 1664 - - -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 - - MAIL, MAILPATH, MAILCHECK have been removed in mirbsdksh. - -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+ - in future, pdksh's read will - do this for all signals that are normally fatal as required by POSIX. - (POSIX does not require the setting of variables to null so applications - shouldn't rely on this). - - in pdksh, ! substitution done before variable substitution; in at&t ksh - it is done after substitution (and therefor may do ! substitutions on - the result of variable substitutions). POSIX doesn't say which is to be - done. - - pwd: in at&t ksh, it ignores arguments; in pdksh, it complains when given - arguments. - - the at&t ksh does not do command substition on PS1, pdksh does. - - ksh93 allows ". foo" to run the function foo if there is no file - called foo (go figure). - - field splitting (IFS): ksh88/ksh93 strip leading non-white space IFS - chars, pdksh (and POSIX, I think) leave them intact. e.g. - $ IFS="$IFS:"; read x; echo "<$x>" - :: - prints "<>" in at&t ksh, "<::>" in pdksh. - - command completion: at&t ksh will do completion on a blank line (matching - all commands), pdksh does not (as this isn't very useful - use * if - you really want the list). - - co-processes: if ksh93, the write portion of the co-process output is - closed when the most recently started co-process exits. pdksh closes - it when all the co-processes using it have exited. - - pdksh accepts empty command lists for while and for statements, while - at&t ksh (and sh) don't. Eg., pdksh likes - while false ; do done - but ksh88 doesn't like it. - - pdksh bumps RANDOM in parent after a fork, at&t ksh bumps it in both - parent and child: - RANDOM=1 - echo child: $(echo $RANDOM) - echo parent: $RANDOM - will produce "child: 16838 parent: 5758" in pdksh, while at&t ksh - will produce "child: 5758 parent: 5758". - -Oddities in ksh (pd & at&t): - - array references inside (())/$(()) are strange: - $(( x[2] )) does the expected, $(( $x[2] )) doesn't. - - 'typeset -R3 X='x '; echo "($X)"' produces ( x) - trailing - spaces are stripped. - - typeset -R turns off Z flag. - - both shells have the following mis-feature: - $ x='function xx { - cat -n <<- EOF - here we are in xx - EOF - }' - $ (eval "$x"; (sleep 2; xx) & echo bye) - [1] 1234 - bye - $ xx: /tmp/sh1234.1: cannot open - - bizarre special handling of alias/export/readonly/typeset arguments - $ touch a=a; typeset a=[ab]; echo "$a" - a=[ab] - $ x=typeset; $x a=[ab]; echo "$a" - a=a - $ - - both ignore SIGTSTP,SIGTTIN,SIGTTOU in exec'd processes when talking - and not monitoring (at&t ksh kind of does this). Doesn't really make - sense. - (Note that ksh.att -ic 'set +m; check-sigs' shows TSTP et al aren't - ignored, while ksh.att -ic 'set +m^J check-sigs' does... very strange) - - when tracing (set -x), and a command's stderr is redirected, the trace - output is also redirected. so "set -x; echo foo 2> /tmp/O > /dev/null" - will create /tmp/foo with the lines "+ > /dev/null" and "+ echo foo". - - undocumented at&t ksh feature: FPATH is searched after PATH if no - executable is found, even if typeset -uf wasn't used. - -[...] - -POSIX sh questions (references are to POSIX 1003.2-1992) - - arithmetic expressions: how are empty expressions treated? - (eg, echo $(( ))). at&t ksh (and now pdksh) echo 0. - Same question goes for 'test "" -eq 0' - does this generate an error - or, if not, what is the exit code? - - should tilde expansion occur after :'s in the word part of ${..=..}? - (me thinks it should) - - if a signal is received during the execution of a built-in, - does the builtin command exit or the whole shell? - - is it legal to execute last command of pipeline in current - execution environment (eg, can "echo foo | read bar" set - bar?) - - what action should be taken if there is an error doing a dup due - to system limits (eg, not enough feil destriptors): is this - a "redirection error" (in which case a script will exit iff the - error occured while executing a special built-in)? - IMHO, shell should exit script. Couldn't find a blanket statement - like "if shell encounters an unexpected system error, it shall - exit non-interactive scripts"... - -POSIX sh bugs (references are to POSIX 1003.2-1992) - - in vi insert mode, ^W deletes to beginning of line or to the first - blank/punct character (para at line 9124, section 3). This means - "foo ^W" will do nothing. This is inconsistent with the vi - spec, which says delete preceding word including and interceding - blanks (para at line 5189, section 5). - - parameter expansion, section 3.6.2, line 391: 'in each case that a - value of word is needed (..), word shall be subjected to tilde - expansion, parameter expansion, ...'. Various expansions should not - be performed if parameter is in double quotes. - - the getopts description says assigning OPTIND a value other than 1 - produces undefined results, while the rationale for getopts suggests - saving/restoring the OPTIND value inside functions (since POSIX - functions don't do the save/restore automatically). Restoring - OPTIND is kind of dumb since getopts may have been in the middle - of parsing a group of flags (eg, -abc). - - unclear whether arithmetic expressions (eg, $((..))) should - understand C integer constants (ie, 0x123, 0177). at&t ksh doesn't - and neither does pdksh. - - `...` definition (3.6.3) says nothing about backslash followed by - a newline, which sh and at&t ksh strip out completely. e.g., - $ show-args `echo 'X - Y'` - Number of args: 1 - 1: - $ - POSIX would indicate the backslash-newline would be preserved. - - does not say how "cat << ''" is to be treated (illegal, read 'til - blank line, or read 'til eof). at&t ksh reads til eof, bourne shell - reads 'til blank line. pdksh reads 'til blank line. diff --git a/README b/README deleted file mode 100644 index 43c350f..0000000 --- a/README +++ /dev/null @@ -1,78 +0,0 @@ -$MirOS$ - -This is the README for mirbsdksh, developed as part of the MirBSD -operating system at The MirOS Project, and produced portably. -Legal information is provided about a screenpage farther down. - -To build mksh on MirOS, issue 'make obj && make depend && make'. - -To build mksh on other operating systems, read Builsd.sh first -or consult http://wiki.mirbsd.de/MirbsdKsh for further infos. - -Set the CC, CFLAGS, CPPFLAGS and LDFLAGS environment variables -to your likes. Don't complain if anything doesn't work. - - -LEGAL INFORMATION - -This package as a whole is placed under the MirOS licence template -as contained in the "Build.sh" source file in this distribution, -by having the project leader asserting a collective copyright. - -The file alloc.c by Marc Espie is provided under a 2-clause BSD licence. - -All files from the original pdksh distribution which were not public -domain have been removed from the source tree, with the exception of -aclocal.m4, from which only the functions which required the file to -be under the GNU GPL have been removed. - - -The information below is mostly unmaintained. -________________________________________________________________________ - -$OpenBSD: README,v 1.10 2003/03/10 03:48:16 david Exp $ - -Last updated Jul '99 for pdksh-5.2.14. - (check ftp://ftp.cs.mun.ca:/pub/pdksh/ or - http://www.cs.mun.ca/~michael/pdksh/ for new versions/patches) - -PD-ksh is a mostly complete AT&T ksh look-alike (see NOTES file for a list -of things not supported). Work is mostly finished to make it fully -compatible with both POSIX and AT&T ksh (when the two don't conflict). - -Since pdksh is free and compiles and runs on most common unix systems, it -is very useful in creating a consistent user interface across multiple -machines. For example, in the CS dept. of MUN, pdksh is installed on a -variety of machines including Suns, HPs, DecStations, pcs running Linux, -etc., and is the login shell of ~5200 users. - -If you would like to be notified via email of new releases as they become -available, send mail to pdksh-request@cs.mun.ca with subject -"send release notifications" (or "don't send release notifications" to stop -them). - -Newer versions of pdksh may be available from - ftp://ftp.cs.mun.ca:/pub/pdksh/ -[...] - -You can send bug reports, fixes, and enhancements to pdksh@cs.mun.ca (please -don't assume I will see bug reports that are posted to some newsgroup or -mailing list - I probably won't). -If you are reporting a bug (with or without a fix), please include - * the version of pdksh you are using (see version.c, or, if you are - running pdksh, try echo $KSH_VERSION), - * the machine, operating system and compiler you are using, - * and a description of how to repeat the bug (a small shell - script that demonstrates the bug is best). -as well as the following, if relevant (if you aren't sure, include them) - * what options you are using (both configure options and set -o options) - * the output of configure, with the verbose flag - (eg, make distclean; ./configure --verbose) - * the contents of config.log (this is created by the configure script) - * if you are using gcc (the GNU C compiler), which version it is. - -BTW, THE MOST FREQUENTLY REPORTED BUG IS - echo hi | read a; echo $a # Does not print hi -I'm aware of this and there is no need to report it. - -Michael Rendell, michael@cs.mun.ca diff --git a/alloc.c b/alloc.c deleted file mode 100644 index f349393..0000000 --- a/alloc.c +++ /dev/null @@ -1,122 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: alloc.c,v 1.7 2004/02/19 18:51:17 deraadt Exp $ */ - -/* - * Copyright (c) 2002 Marc Espie. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS - * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD - * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - * area-based allocation built on malloc/free - */ - -#include "sh.h" - -struct link { - struct link *prev; - struct link *next; -}; - -Area * -ainit(Area *ap) -{ - ap->freelist = NULL; - return ap; -} - -void -afreeall(Area *ap) -{ - struct link *l, *l2; - - for (l = ap->freelist; l != NULL; l = l2) { - l2 = l->next; - free(l); - } - ap->freelist = NULL; -} - -#define L2P(l) ( (void *)(((char *)(l)) + sizeof(struct link)) ) -#define P2L(p) ( (struct link *)(((char *)(p)) - sizeof(struct link)) ) - -void * -alloc(size_t size, Area *ap) -{ - struct link *l; - - l = malloc(sizeof(struct link) + size); - if (l == NULL) - internal_errorf(1, "unable to allocate memory"); - l->next = ap->freelist; - l->prev = NULL; - if (ap->freelist) - ap->freelist->prev = l; - ap->freelist = l; - - return L2P(l); -} - -void * -aresize(void *ptr, size_t size, Area *ap) -{ - struct link *l, *l2, *lprev, *lnext; - - if (ptr == NULL) - return alloc(size, ap); - - l = P2L(ptr); - lprev = l->prev; - lnext = l->next; - - l2 = realloc(l, sizeof(struct link) + size); - if (l2 == NULL) - internal_errorf(1, "unable to allocate memory"); - if (lprev) - lprev->next = l2; - else - ap->freelist = l2; - if (lnext) - lnext->prev = l2; - - return L2P(l2); -} - -void -afree(void *ptr, Area *ap) -{ - struct link *l; - - if (!ptr) - return; - - l = P2L(ptr); - - if (l->prev) - l->prev->next = l->next; - else - ap->freelist = l->next; - if (l->next) - l->next->prev = l->prev; - - free(l); -} diff --git a/c_ksh.c b/c_ksh.c deleted file mode 100644 index a4d084e..0000000 --- a/c_ksh.c +++ /dev/null @@ -1,1406 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: c_ksh.c,v 1.18 2004/02/10 13:03:36 jmc Exp $ */ - -/* - * built-in Korn commands: c_* - */ - -#include "sh.h" -#include "ksh_stat.h" -#include - -#ifdef __CYGWIN__ -#include -#endif /* __CYGWIN__ */ - -__RCSID("$MirOS$"); - -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; - - while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != EOF) - switch (optc) { - case 'L': - physical = 0; - break; - case 'P': - physical = 1; - break; - case '?': - return 1; - } - wp += builtin_opt.optind; - - if (Flag(FRESTRICTED)) { - bi_errorf("restricted shell - can't cd"); - return 1; - } - - pwd_s = global("PWD"); - oldpwd_s = global("OLDPWD"); - - if (!wp[0]) { - /* No arguments - go home */ - if ((dir = str_val(global("HOME"))) == null) { - bi_errorf("no home directory (HOME not set)"); - return 1; - } - } else if (!wp[1]) { - /* One argument: - or dir */ - dir = wp[0]; - if (strcmp(dir, "-") == 0) { - dir = str_val(oldpwd_s); - if (dir == null) { - bi_errorf("no OLDPWD"); - return 1; - } - printpath++; - } - } else if (!wp[2]) { - /* Two arguments - substitute arg1 in PWD for arg2 */ - int ilen, olen, nlen, elen; - char *cp; - - if (!current_wd[0]) { - bi_errorf("don't know current directory"); - return 1; - } - /* 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; - 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_MAX, 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); -#ifdef S_ISLNK - if (physical) - rval = chdir(try = Xstring(xs, xp) + phys_path); - else -#endif /* S_ISLNK */ - { - simplify_path(Xstring(xs, xp)); - rval = chdir(try = Xstring(xs, xp)); - } - } while (rval < 0 && cdpath != NULL); - - if (rval < 0) { - if (cdnode) - bi_errorf("%s: bad directory", dir); - else - bi_errorf("%s - %s", try, strerror(errno)); - return 1; - } - - /* Clear out tracked aliases with relative paths */ - flushcom(0); - - /* Set OLDPWD (note: unsetting OLDPWD does not disable this - * setting in at&t ksh) - */ - if (current_wd[0]) - /* Ignore failure (happens if readonly or integer) */ - setstr(oldpwd_s, current_wd, KSH_RETURN_ERROR); - - if (!ISABSPATH(Xstring(xs, xp))) { - pwd = NULL; - } else -#ifdef S_ISLNK - if (!physical || !(pwd = get_phys_path(Xstring(xs, xp)))) -#endif /* S_ISLNK */ - pwd = Xstring(xs, xp); - - /* Set PWD */ - if (pwd) { -#ifdef __CYGWIN__ - char ptmp[PATH_MAX]; /* larger than MAX_PATH */ - cygwin_conv_to_full_posix_path(pwd, ptmp); -#else /* __CYGWIN__ */ - char *ptmp = pwd; -#endif /* __CYGWIN__ */ - set_current_wd(ptmp); - /* Ignore failure (happens if readonly or integer) */ - setstr(pwd_s, ptmp, KSH_RETURN_ERROR); - } else { - set_current_wd(null); - pwd = Xstring(xs, xp); - /* XXX unset $PWD? */ - } - if (printpath || cdnode) - shprintf("%s\n", pwd); - - return 0; -} - -int -c_pwd(char **wp) -{ - int optc; - int physical = Flag(FPHYSICAL); - char *p; - - while ((optc = ksh_getopt(wp, &builtin_opt, "LP")) != EOF) - switch (optc) { - case 'L': - physical = 0; - break; - case 'P': - physical = 1; - break; - case '?': - return 1; - } - wp += builtin_opt.optind; - - if (wp[0]) { - bi_errorf("too many arguments"); - return 1; - } -#ifdef S_ISLNK - p = current_wd[0] ? (physical ? get_phys_path(current_wd) : current_wd) - : NULL; -#else /* S_ISLNK */ - p = current_wd[0] ? current_wd : NULL; -#endif /* S_ISLNK */ - if (p && eaccess(p, R_OK) < 0) - p = NULL; - if (!p) { - p = ksh_get_wd(NULL, 0); - if (!p) { - bi_errorf("can't get current directory - %s", - strerror(errno)); - return 1; - } - } - shprintf("%s\n", p); - 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; - 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)) != EOF) - 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 UNINITIALIZED(opipe); - - /* 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; - } - 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)) != EOF) - switch (optc) { - case 'p': - pflag = 1; - break; - case 'v': - vflag = 1; - break; - case 'V': - Vflag = 1; - break; - case '?': - return 1; - } - wp += builtin_opt.optind; - - - fcflags = FC_BI | FC_PATH | FC_FUNC; - if (!iam_whence) { - /* Note that -p on its own is deal with in comexec() */ - if (pflag) - fcflags |= FC_DEFPATH; - /* Convert command options to whence options - note that - * command -pV uses a different path search than whence -v - * or whence -pv. This should be considered a feature. - */ - vflag = Vflag; - } - if (pflag) - fcflags &= ~(FC_BI | FC_FUNC); - - while ((vflag || ret == 0) && (id = *wp++) != NULL) { - tp = NULL; - if ((iam_whence || vflag) && !pflag) - tp = tsearch(&keywords, id, hash(id)); - if (!tp && !pflag) { - tp = tsearch(&aliases, id, hash(id)); - if (tp && !(tp->flag & ISSET)) - tp = NULL; - } - if (!tp) - tp = findcom(id, fcflags); - if (vflag || (tp->type != CALIAS && tp->type != CEXEC - && tp->type != CTALIAS)) - shprintf("%s", id); - switch (tp->type) { - case CKEYWD: - if (vflag) - shprintf(" is a reserved word"); - break; - case CALIAS: - if (vflag) - shprintf(" is an %salias for ", - (tp->flag & EXPORT) ? "exported " - : null); - if (!iam_whence && !vflag) - shprintf("alias %s=", id); - print_value_quoted(tp->val.s); - break; - case CFUNC: - if (vflag) { - shprintf(" is a"); - if (tp->flag & EXPORT) - shprintf("n exported"); - if (tp->flag & TRACE) - shprintf(" traced"); - if (!(tp->flag & ISSET)) { - shprintf(" undefined"); - if (tp->u.fpath) - shprintf(" (autoload from %s)", - tp->u.fpath); - } - shprintf(" function"); - } - break; - case CSHELL: - if (vflag) - shprintf(" is a%s shell builtin", - (tp->flag & SPEC_BI) ? " special" : null); - break; - case CTALIAS: - case CEXEC: - if (tp->flag & ISSET) { - if (vflag) { - shprintf(" is "); - if (tp->type == CTALIAS) - shprintf( - "a tracked %salias for ", - (tp->flag & EXPORT) ? - "exported " - : null); - } - shprintf("%s", tp->val.s); - } else { - if (vflag) - shprintf(" not found"); - ret = 1; - } - break; - default: - shprintf("%s is *GOK*", id); - break; - } - if (vflag || !ret) - shprintf(newline); - } - return ret; -} - -/* Deal with command -vV - command -p dealt with in comexec() */ -int -c_command(char **wp) -{ - /* Let c_whence do the work. Note that c_command() must be - * a distinct function from c_whence() (tested in comexec()). - */ - return c_whence(wp); -} - -/* typeset, export, and readonly */ -int -c_typeset(char **wp) -{ - struct block *l = e->loc; - struct tbl *vp, **p; - Tflag fset = 0, fclr = 0; - int thing = 0, func = 0, local = 0; - const char *options = "L#R#UZ#fi#lprtux"; /* see comment below */ - char *fieldstr, *basestr; - int field, base; - int optc; - Tflag flag; - int pflag = 0; - - switch (**wp) { - case 'e': /* export */ - fset |= EXPORT; - options = "p"; - break; - case 'r': /* readonly */ - fset |= RDONLY; - options = "p"; - break; - case 's': /* set */ - /* called with 'typeset -' */ - break; - case 't': /* typeset */ - local = 1; - break; - } - - fieldstr = basestr = 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)) != EOF) { - flag = 0; - switch (optc) { - case 'L': - flag = LJUST; - fieldstr = builtin_opt.optarg; - break; - case 'R': - flag = RJUST; - fieldstr = builtin_opt.optarg; - break; - case 'U': - /* at&t ksh uses u, but this conflicts with - * upper/lower case. If this option is changed, - * need to change the -U below as well - */ - flag = INT_U; - break; - case 'Z': - flag = ZEROFIL; - fieldstr = builtin_opt.optarg; - break; - case 'f': - func = 1; - break; - case 'i': - flag = INTEGER; - basestr = builtin_opt.optarg; - break; - case 'l': - flag = LCASEV; - break; - case 'p': /* posix export/readonly -p flag. - * typeset -p is the same as typeset (in pdksh); - * here for compatibility with ksh93. - */ - pflag = 1; - break; - case 'r': - flag = RDONLY; - break; - case 't': - flag = TRACE; - break; - case 'u': - flag = UCASEV_AL; /* upper case / autoload */ - break; - case 'x': - flag = EXPORT; - break; - case '?': - return 1; - } - if (builtin_opt.info & GI_PLUS) { - fclr |= flag; - fset &= ~flag; - thing = '+'; - } else { - fset |= flag; - fclr &= ~flag; - thing = '-'; - } - } - - field = 0; - if (fieldstr && !bi_getn(fieldstr, &field)) - return 1; - base = 0; - if (basestr && !bi_getn(basestr, &base)) - return 1; - - if (!(builtin_opt.info & GI_MINUSMINUS) && wp[builtin_opt.optind] - && (wp[builtin_opt.optind][0] == '-' - || wp[builtin_opt.optind][0] == '+') - && wp[builtin_opt.optind][1] == '\0') - { - thing = wp[builtin_opt.optind][0]; - builtin_opt.optind++; - } - - if (func && ((fset|fclr) & ~(TRACE|UCASEV_AL|EXPORT))) { - bi_errorf("only -t, -u and -x options may be used with -f"); - return 1; - } - if (wp[builtin_opt.optind]) { - /* Take care of exclusions. - * At this point, flags in fset are cleared in fclr and vise - * versa. This property should be preserved. - */ - if (fset & LCASEV) /* LCASEV has priority over UCASEV_AL */ - fset &= ~UCASEV_AL; - if (fset & LJUST) /* LJUST has priority over RJUST */ - fset &= ~RJUST; - if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { /* -Z implies -ZR */ - fset |= RJUST; - fclr &= ~RJUST; - } - /* Setting these attributes clears the others, unless they - * are also set in this command - */ - if (fset & (LJUST|RJUST|ZEROFIL|UCASEV_AL|LCASEV|INTEGER - |INT_U|INT_L)) - fclr |= ~fset & - (LJUST|RJUST|ZEROFIL|UCASEV_AL|LCASEV|INTEGER - |INT_U|INT_L); - } - - /* set variables and attributes */ - if (wp[builtin_opt.optind]) { - int i; - int rval = 0; - struct tbl *f; - - if (local && !func) - fset |= LOCAL; - for (i = builtin_opt.optind; wp[i]; i++) { - if (func) { - f = findfunc(wp[i], hash(wp[i]), - (fset&UCASEV_AL) ? true : false); - if (!f) { - /* at&t ksh does ++rval: bogus */ - rval = 1; - continue; - } - if (fset | fclr) { - f->flag |= fset; - f->flag &= ~fclr; - } else - fptreef(shl_stdout, 0, - f->flag & FKSH ? - "function %s %T\n" - : "%s() %T\n" - , - wp[i], f->val.t); - } else if (!typeset(wp[i], fset, fclr, field, base)) { - bi_errorf("%s: not identifier", wp[i]); - return 1; - } - } - return rval; - } - - /* list variables and attributes */ - flag = fset | fclr; /* no difference at this point.. */ - if (func) { - for (l = e->loc; l; l = l->next) { - for (p = tsort(&l->funs); (vp = *p++); ) { - if (flag && (vp->flag & flag) == 0) - continue; - if (thing == '-') - fptreef(shl_stdout, 0, vp->flag & FKSH ? - "function %s %T\n" - : "%s() %T\n", - vp->name, vp->val.t); - else - shprintf("%s\n", vp->name); - } - } - } else { - for (l = e->loc; l; l = l->next) { - for (p = tsort(&l->vars); (vp = *p++); ) { - struct tbl *tvp; - int any_set = 0; - /* - * See if the parameter is set (for arrays, if any - * element is set). - */ - for (tvp = vp; tvp; tvp = tvp->u.array) - if (tvp->flag & ISSET) { - any_set = 1; - break; - } - /* - * Check attributes - note that all array elements - * have (should have?) the same attributes, so checking - * the first is sufficient. - * - * Report an unset param only if the user has - * explicitly given it some attribute (like export); - * otherwise, after "echo $FOO", we would report FOO... - */ - if (!any_set && !(vp->flag & USERATTRIB)) - continue; - if (flag && (vp->flag & flag) == 0) - continue; - for (; vp; vp = vp->u.array) { - /* Ignore array elements that aren't set unless there - * are no set elements, in which case the first is - * reported on - */ - if ((vp->flag&ARRAY) && any_set && !(vp->flag & ISSET)) - continue; - /* no arguments */ - if (thing == 0 && flag == 0) { - /* at&t ksh prints things like export, integer, - * leftadj, zerofill, etc., but POSIX says must - * be suitable for re-entry... - */ - shprintf("typeset "); - if ((vp->flag&INTEGER)) - shprintf("-i "); - if ((vp->flag&EXPORT)) - shprintf("-x "); - if ((vp->flag&RDONLY)) - shprintf("-r "); - if ((vp->flag&TRACE)) - shprintf("-t "); - if ((vp->flag&LJUST)) - shprintf("-L%d ", vp->u2.field); - if ((vp->flag&RJUST)) - shprintf("-R%d ", vp->u2.field); - if ((vp->flag&ZEROFIL)) - shprintf("-Z "); - if ((vp->flag&LCASEV)) - shprintf("-l "); - if ((vp->flag&UCASEV_AL)) - shprintf("-u "); - if ((vp->flag&INT_U)) - shprintf("-U "); - shprintf("%s\n", vp->name); - if (vp->flag&ARRAY) - break; - } else { - if (pflag) - shprintf("%s ", - (flag & EXPORT) ? "export" : "readonly"); - if ((vp->flag&ARRAY) && any_set) - shprintf("%s[%d]", vp->name, vp->index); - else - shprintf("%s", vp->name); - if (thing == '-' && (vp->flag&ISSET)) { - char *s = str_val(vp); - - shprintf("="); - /* at&t ksh can't have justified integers.. */ - if ((vp->flag & (INTEGER|LJUST|RJUST)) - == INTEGER) - shprintf("%s", s); - else - print_value_quoted(s); - } - shprintf(newline); - } - /* Only report first 'element' of an array with - * no set elements. - */ - if (!any_set) - break; - } - } - } - } - return 0; -} - -int -c_alias(char **wp) -{ - struct table *t = &aliases; - int rv = 0, rflag = 0, tflag, Uflag = 0, pflag = 0; - int prefix = 0; - Tflag xflag = 0; - int optc; - - builtin_opt.flags |= GF_PLUSOPT; - while ((optc = ksh_getopt(wp, &builtin_opt, "dprtUx")) != EOF) { - prefix = builtin_opt.info & GI_PLUS ? '+' : '-'; - switch (optc) { - case 'd': - t = &homedirs; - break; - case 'p': - pflag = 1; - break; - case 'r': - rflag = 1; - break; - case 't': - t = &taliases; - break; - case 'U': /* kludge for tracked alias initialization - * (don't do a path search, just make an entry) - */ - Uflag = 1; - break; - case 'x': - xflag = EXPORT; - break; - case '?': - return 1; - } - } - wp += builtin_opt.optind; - - if (!(builtin_opt.info & GI_MINUSMINUS) && *wp - && (wp[0][0] == '-' || wp[0][0] == '+') && wp[0][1] == '\0') - { - prefix = wp[0][0]; - wp++; - } - - tflag = t == &taliases; - - /* "hash -r" means reset all the tracked aliases.. */ - if (rflag) { - static const char *const args[] = { - "unalias", "-ta", 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 = tsort(t); (ap = *p++) != NULL; ) - if ((ap->flag & (ISSET|xflag)) == (ISSET|xflag)) { - if (pflag) - shf_puts("alias ", shl_stdout); - shf_puts(ap->name, shl_stdout); - if (prefix != '+') { - shf_putc('=', shl_stdout); - print_value_quoted(ap->val.s); - } - shprintf(newline); - } - } - - for (; *wp != NULL; wp++) { - char *alias = *wp; - char *val = strchr(alias, '='); - char *newval; - struct tbl *ap; - int h; - - if (val) - alias = str_nsave(alias, val++ - alias, ATEMP); - h = hash(alias); - if (val == NULL && !tflag && !xflag) { - ap = tsearch(t, alias, h); - if (ap != NULL && (ap->flag&ISSET)) { - if (pflag) - shf_puts("alias ", shl_stdout); - shf_puts(ap->name, shl_stdout); - if (prefix != '+') { - shf_putc('=', shl_stdout); - print_value_quoted(ap->val.s); - } - shprintf(newline); - } else { - shprintf("%s alias not found\n", alias); - rv = 1; - } - continue; - } - ap = tenter(t, alias, h); - ap->type = tflag ? CTALIAS : CALIAS; - /* Are we setting the value or just some flags? */ - if ((val && !tflag) || (!val && tflag && !Uflag)) { - if (ap->flag&ALLOC) { - ap->flag &= ~(ALLOC|ISSET); - afree((void*)ap->val.s, APERM); - } - /* ignore values for -t (at&t ksh does this) */ - newval = tflag ? search(alias, path, X_OK, 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")) != EOF) - switch (optc) { - case 'a': - all = 1; - break; - case 'd': - t = &homedirs; - break; - case 't': - t = &taliases; - break; - case '?': - return 1; - } - wp += builtin_opt.optind; - - for (; *wp != NULL; wp++) { - ap = tsearch(t, *wp, hash(*wp)); - if (ap == NULL) { - rv = 1; /* POSIX */ - continue; - } - if (ap->flag&ALLOC) { - ap->flag &= ~(ALLOC|ISSET); - afree((void*)ap->val.s, APERM); - } - ap->flag &= ~(DEFINED|ISSET|EXPORT); - } - - if (all) { - struct tstate ts; - - for (twalk(&ts, t); (ap = tnext(&ts)); ) { - if (ap->flag&ALLOC) { - ap->flag &= ~(ALLOC|ISSET); - afree((void*)ap->val.s, APERM); - } - ap->flag &= ~(DEFINED|ISSET|EXPORT); - } - } - - return rv; -} - -int -c_let(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)) { - 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")) != EOF) - switch (optc) { - case 'l': - flag = 1; - break; - case 'p': - flag = 2; - break; - case 'n': - nflag = 1; - break; - case 'z': /* debugging: print zombies */ - nflag = -1; - break; - case '?': - return 1; - } - wp += builtin_opt.optind; - if (!*wp) { - if (j_jobs(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 UNINITIALIZED(rv); - - if (!Flag(FMONITOR)) { - bi_errorf("job control not enabled"); - return 1; - } - if (ksh_getopt(wp, &builtin_opt, null) == '?') - return 1; - wp += builtin_opt.optind; - if (*wp) - for (; *wp; wp++) - rv = j_resume(*wp, bg); - else - rv = j_resume("%%", bg); - /* POSIX says fg shall return 0 (unless an error occurs). - * at&t ksh returns the exit value of the job... - */ - return (bg || Flag(FPOSIX)) ? 0 : rv; -} -#endif - -struct kill_info { - size_t num_width; - size_t 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(p[1]))) { - if (!(t = gettrap(p + 1, true))) { - bi_errorf("bad signal '%s'", p + 1); - return 1; - } - i = (wp[2] && strcmp(wp[2], "--") == 0) ? 3 : 2; - } else { - int optc; - - while ((optc = ksh_getopt(wp, &builtin_opt, "ls:")) != EOF) - switch (optc) { - case 'l': - lflag = 1; - break; - case 's': - if (!(t = gettrap(builtin_opt.optarg, true))) { - bi_errorf("bad signal '%s'", - builtin_opt.optarg); - return 1; - } - break; - case '?': - return 1; - } - i = builtin_opt.optind; - } - if ((lflag && t) || (!wp[i] && !lflag)) { - shf_fprintf(shl_out, -"Usage: kill [ -s signame | -signum | -signame ] {pid|job}...\n\ - kill -l [exit_status]\n" - ); - bi_errorf(null); - return 1; - } - - if (lflag) { - if (wp[i]) { - for (; wp[i]; i++) { - if (!bi_getn(wp[i], &n)) - return 1; - if (n > 128 && n < 128 + NSIG) - n -= 128; - if (n > 0 && n < NSIG && sigtraps[n].name) - shprintf("%s\n", sigtraps[n].name); - else - shprintf("%d\n", n); - } - } else if (Flag(FPOSIX)) { - p = null; - for (i = 1; i < NSIG; i++, p = space) - if (sigtraps[i].name) - shprintf("%s%s", p, sigtraps[i].name); - shprintf(newline); - } else { - int i; - size_t w, mess_width; - struct kill_info ki; - - for (i = NSIG, ki.num_width = 1; i >= 10; i /= 10) - ki.num_width++; - ki.name_width = mess_width = 0; - for (i = 0; i < NSIG; i++) { - w = sigtraps[i].name ? strlen(sigtraps[i].name) - : ki.num_width; - if (w > ki.name_width) - ki.name_width = w; - w = strlen(sigtraps[i].mess); - if (w > mess_width) - mess_width = w; - } - - print_columns(shl_stdout, NSIG - 1, - kill_fmt_entry, (void *) &ki, - ki.num_width + ki.name_width + mess_width + 3, 1); - } - return 0; - } - rv = 0; - sig = t ? t->signal : SIGTERM; - for (; (p = wp[i]); i++) { - if (*p == '%') { - if (j_kill(p, sig)) - rv = 1; - } else if (!getn(p, &n)) { - bi_errorf("%s: arguments must be jobs or process IDs", - p); - rv = 1; - } else { - /* use killpg if < -1 since -1 does special things for - * some non-killpg-endowed kills - */ - if ((n < -1 ? killpg(-n, sig) : kill(n, sig)) < 0) { - bi_errorf("%s: %s", p, strerror(errno)); - rv = 1; - } - } - } - return rv; -} - -void -getopts_reset(int val) -{ - if (val >= 1) { - ksh_getopt_reset(&user_opt, - GF_NONAME | (Flag(FPOSIX) ? 0 : GF_PLUSOPT)); - user_opt.optind = user_opt.uoptind = val; - } -} - -int -c_getopts(char **wp) -{ - int argc; - const char *options; - const char *var; - int optc; - int ret; - char buf[3]; - struct tbl *vq, *voptarg; - - if (ksh_getopt(wp, &builtin_opt, null) == '?') - return 1; - wp += builtin_opt.optind; - - options = *wp++; - if (!options) { - bi_errorf("missing options argument"); - return 1; - } - - var = *wp++; - if (!var) { - bi_errorf("missing name argument"); - return 1; - } - if (!*var || *skip_varname(var, true)) { - bi_errorf("%s: is not an identifier", var); - return 1; - } - - if (e->loc->next == NULL) { - internal_errorf(0, "c_getopts: no argv"); - return 1; - } - /* Which arguments are we parsing... */ - if (*wp == NULL) - wp = e->loc->next->argv; - else - *--wp = e->loc->next->argv[0]; - - /* Check that our saved state won't cause a core dump... */ - for (argc = 0; wp[argc]; argc++) - ; - if (user_opt.optind > argc - || (user_opt.p != 0 - && user_opt.p > strlen(wp[user_opt.optind - 1]))) - { - bi_errorf("arguments changed since last call"); - return 1; - } - - user_opt.optarg = 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; -} - -int -c_bind(char **wp) -{ - int rv = 0, macro = 0, list = 0; - char *cp; - int optc; - - while ((optc = ksh_getopt(wp, &builtin_opt, "lm")) != EOF) - switch (optc) { - case 'l': - list = 1; - break; - case 'm': - macro = 1; - break; - case '?': - return 1; - } - wp += builtin_opt.optind; - - if (*wp == NULL) /* list all */ - rv = x_bind((char*)NULL, (char*)NULL, 0, list); - - for (; *wp != NULL; wp++) { - cp = strchr(*wp, '='); - if (cp != NULL) - *cp++ = '\0'; - if (x_bind(*wp, cp, macro, 0)) - rv = 1; - } - - return rv; -} - -/* 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}, - {"+fc", c_fc}, - {"+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 - {"bind", c_bind}, - {NULL, NULL} -}; diff --git a/c_sh.c b/c_sh.c deleted file mode 100644 index daf54a3..0000000 --- a/c_sh.c +++ /dev/null @@ -1,861 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: c_sh.c,v 1.25 2004/12/22 18:48:56 millert Exp $ */ - -/* - * built-in Bourne commands - */ - -#include "sh.h" -#include -#include -#include - -__RCSID("$MirOS$"); - -static void p_time(struct shf *, int, struct timeval *, int, char *, char *); - -/* :, false and true */ -int -c_label(char **wp) -{ - return wp[0][0] == 'f' ? 1 : 0; -} - -int -c_shift(char **wp) -{ - struct block *l = e->loc; - int n; - long val; - char *arg; - - if (ksh_getopt(wp, &builtin_opt, null) == '?') - return 1; - arg = wp[builtin_opt.optind]; - - if (arg) { - evaluate(arg, &val, KSH_UNWIND_ERROR); - 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")) != EOF) - switch (optc) { - case 'S': - symbolic = 1; - break; - case '?': - return 1; - } - cp = wp[builtin_opt.optind]; - if (cp == NULL) { - old_umask = umask(0); - umask(old_umask); - if (symbolic) { - char buf[18]; - int j; - - old_umask = ~old_umask; - cp = buf; - for (i = 0; i < 3; i++) { - *cp++ = "ugo"[i]; - *cp++ = '='; - for (j = 0; j < 3; j++) - if (old_umask & (1 << (8 - (3*i + j)))) - *cp++ = "rwx"[j]; - *cp++ = ','; - } - cp[-1] = '\0'; - shprintf("%s\n", buf); - } else - shprintf("%#3.3o\n", old_umask); - } else { - mode_t new_umask; - - if (digit(*cp)) { - for (new_umask = 0; *cp >= '0' && *cp <= '7'; cp++) - new_umask = new_umask * 8 + (*cp - '0'); - if (*cp) { - bi_errorf("bad number"); - return 1; - } - } else { - /* symbolic format */ - int positions, new_val; - char op; - - old_umask = umask(0); - umask(old_umask); /* in case of error */ - old_umask = ~old_umask; - new_umask = old_umask; - positions = 0; - while (*cp) { - while (*cp && strchr("augo", *cp)) - switch (*cp++) { - case 'a': positions |= 0111; break; - case 'u': positions |= 0100; break; - case 'g': positions |= 0010; break; - case 'o': positions |= 0001; break; - } - if (!positions) - positions = 0111; /* default is a */ - if (!strchr("=+-", op = *cp)) - break; - cp++; - new_val = 0; - while (*cp && strchr("rwxugoXs", *cp)) - switch (*cp++) { - case 'r': new_val |= 04; break; - case 'w': new_val |= 02; break; - case 'x': new_val |= 01; break; - case 'u': new_val |= old_umask >> 6; - break; - case 'g': new_val |= old_umask >> 3; - break; - case 'o': new_val |= old_umask >> 0; - break; - case 'X': if (old_umask & 0111) - new_val |= 01; - break; - case 's': /* ignored */ - break; - } - new_val = (new_val & 07) * positions; - switch (op) { - case '-': - new_umask &= ~new_val; - break; - case '=': - new_umask = new_val - | (new_umask & ~(positions * 07)); - break; - case '+': - new_umask |= new_val; - } - if (*cp == ',') { - positions = 0; - cp++; - } else if (!strchr("=+-", *cp)) - break; - } - if (*cp) { - bi_errorf("bad mask"); - return 1; - } - new_umask = ~new_umask; - } - umask(new_umask); - } - return 0; -} - -int -c_dot(char **wp) -{ - char *file, *cp; - char **argv; - int argc; - int i; - int err; - - if (ksh_getopt(wp, &builtin_opt, null) == '?') - return 1; - - if ((cp = wp[builtin_opt.optind]) == NULL) - return 0; - file = search(cp, path, R_OK, &err); - if (file == NULL) { - bi_errorf("%s: %s", cp, err ? strerror(err) : "not found"); - return 1; - } - - /* Set positional parameters? */ - if (wp[builtin_opt.optind + 1]) { - argv = wp + builtin_opt.optind; - argv[0] = e->loc->argv[0]; /* preserve $0 */ - for (argc = 0; argv[argc + 1]; argc++) - ; - } else { - argc = 0; - argv = 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 UNINITIALIZED(rv); - 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 UNINITIALIZED(*xp); - - while ((optc = ksh_getopt(wp, &builtin_opt, "prsu,")) != EOF) - switch (optc) { - case 'p': - if ((fd = coproc_getfd(R_OK, &emsg)) < 0) { - bi_errorf("-p: %s", emsg); - return 1; - } - break; - case 'r': - expand = 0; - break; - case 's': - history = 1; - break; - case 'u': - if (!*(cp = builtin_opt.optarg)) - fd = 0; - else if ((fd = check_fd(cp, R_OK, &emsg)) < 0) { - bi_errorf("-u: %s: %s", cp, emsg); - return 1; - } - break; - case '?': - return 1; - } - wp += builtin_opt.optind; - - if (*wp == NULL) - *--wp = "REPLY"; - - /* Since we can't necessarily seek backwards on non-regular files, - * don't buffer them so we can't read too much. - */ - shf = shf_reopen(fd, SHF_RD | SHF_INTERRUPT | can_seek(fd), shl_spare); - - if ((cp = strchr(*wp, '?')) != NULL) { - *cp = 0; - if (isatty(fd)) { - /* at&t ksh says it prints prompt on fd if it's open - * for writing and is a tty, but it doesn't do it - * (it also doesn't check the interactive flag, - * as is indicated in the Kornshell book). - */ - shellf("%s", cp+1); - } - } - - /* If we are reading from the co-process for the first time, - * make sure the other side of the pipe is closed first. This allows - * the detection of eof. - * - * This is not compatible with at&t ksh... the fd is kept so another - * coproc can be started with same output, however, this means eof - * can't be detected... This is why it is closed here. - * If this call is removed, remove the eof check below, too. - * coproc_readw_close(fd); - */ - - if (history) - Xinit(xs, xp, 128, ATEMP); - expanding = 0; - Xinit(cs, cp, 128, ATEMP); - for (; *wp != NULL; wp++) { - for (cp = Xstring(cs, cp); ; ) { - if (c == '\n' || c == EOF) - break; - while (1) { - c = shf_getc(shf); - if (c == '\0') - continue; - if (c == EOF && shf_error(shf) - && shf_errno(shf) == EINTR) - { - /* Was the offending signal one that - * would normally kill a process? - * If so, pretend the read was killed. - */ - ecode = fatal_trap_check(); - - /* non fatal (eg, CHLD), carry on */ - if (!ecode) { - shf_clearerr(shf); - continue; - } - } - break; - } - if (history) { - Xcheck(xs, xp); - Xput(xs, xp, c); - } - Xcheck(cs, cp); - if (expanding) { - expanding = 0; - if (c == '\n') { - c = 0; - if (Flag(FTALKING_I) && isatty(fd)) { - /* set prompt in case this is - * called from .profile or $ENV - */ - set_prompt(PS2, 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; - - if (ksh_getopt(wp, &builtin_opt, null) == '?') - return 1; - s = pushs(SWORDS, ATEMP); - s->u.strv = wp + builtin_opt.optind; - if (!Flag(FPOSIX)) { - /* - * Handle case where the command is empty due to failed - * command substitution, eg, eval "$(false)". - * In this case, shell() will not set/change exstat (because - * compiled tree is empty), so will use this value. - * subst_exstat is cleared in execute(), so should be 0 if - * there were no substitutions. - * - * A strict reading of POSIX says we don't do this (though - * it is traditionally done). [from 1003.2-1992] - * 3.9.1: Simple Commands - * ... If there is a command name, execution shall - * continue as described in 3.9.1.1. If there - * is no command name, but the command contained a command - * substitution, the command shall complete with the exit - * status of the last command substitution - * 3.9.1.1: Command Search and Execution - * ...(1)...(a) If the command name matches the name of - * a special built-in utility, that special built-in - * utility shall be invoked. - * 3.14.5: Eval - * ... If there are no arguments, or only null arguments, - * eval shall return an exit status of zero. - */ - exstat = subst_exstat; - } - - return shell(s, false); -} - -int -c_trap(char **wp) -{ - int i; - char *s; - Trap *p; - - if (ksh_getopt(wp, &builtin_opt, null) == '?') - return 1; - wp += builtin_opt.optind; - - if (*wp == NULL) { - int anydfl = 0; - - for (p = sigtraps, i = NSIG + 1; --i >= 0; p++) { - if (p->trap == NULL) - anydfl = 1; - else { - shprintf("trap -- "); - print_value_quoted(p->trap); - shprintf(" %s\n", p->name); - } - } - return 0; - } - - /* - * Use case sensitive lookup for first arg so the - * command 'exit' isn't confused with the pseudo-signal - * 'EXIT'. - */ - s = (gettrap(*wp, false) == NULL) ? *wp++ : NULL; /* get command */ - if (s != NULL && s[0] == '-' && s[1] == '\0') - s = NULL; - - /* set/clear traps */ - while (*wp != NULL) { - p = gettrap(*wp++, true); - if (p == NULL) { - bi_errorf("bad signal %s", wp[-1]); - return 1; - } - settrap(p, s); - } - return 0; -} - -int -c_exitreturn(char **wp) -{ - int how = LEXIT; - int n; - char *arg; - - if (ksh_getopt(wp, &builtin_opt, null) == '?') - return 1; - arg = wp[builtin_opt.optind]; - - if (arg) { - if (!getn(arg, &n)) { - exstat = 1; - warningf(true, "%s: bad number", arg); - } else - exstat = n; - } - if (wp[0][0] == 'r') { /* return */ - struct env *ep; - - /* need to tell if this is exit or return so trap exit will - * work right (POSIX) - */ - for (ep = e; ep; ep = ep->oenv) - if (STOP_RETURN(ep->type)) { - how = LRETURN; - break; - } - } - - if (how == LEXIT && !really_exit && j_stopped_running()) { - really_exit = 1; - how = LSHELL; - } - - quitenv(NULL); /* get rid of any i/o redirections */ - unwind(how); - /*NOTREACHED*/ - return 0; -} - -int -c_brkcont(char **wp) -{ - int n, quit; - struct env *ep, *last_ep = 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 = e; ep && !STOP_BRKCONT(ep->type); ep = ep->oenv) - if (ep->type == E_LOOP) { - if (--quit == 0) - break; - ep->flags |= EF_BRKCONT_PASS; - last_ep = ep; - } - - if (quit) { - /* at&t ksh doesn't print a message - just does what it - * can. We print a message 'cause it helps in debugging - * scripts, but don't generate an error (ie, keep going). - */ - if (n == quit) { - warningf(true, "%s: cannot %s", wp[0], wp[0]); - return 0; - } - /* POSIX says if n is too big, the last enclosing loop - * shall be used. Doesn't say to print an error but we - * do anyway 'cause the user messed up. - */ - last_ep->flags &= ~EF_BRKCONT_PASS; - warningf(true, "%s: can only %s %d level(s)", - wp[0], wp[0], n - quit); - } - - unwind(*wp[0] == 'b' ? LBREAK : LCONTIN); - /*NOTREACHED*/ -} - -int -c_set(char **wp) -{ - int argi, setargs; - struct block *l = e->loc; - char **owp = wp; - - if (wp[1] == NULL) { - static const char *const args [] = { "set", "-", NULL }; - return c_typeset((char **) args); - } - - argi = parse_args(wp, OF_SET, &setargs); - if (argi < 0) - return 1; - /* set $# and $* */ - if (setargs) { - owp = wp += argi - 1; - wp[0] = l->argv[0]; /* save $0 */ - while (*++wp != NULL) - *wp = str_save(*wp, &l->area); - l->argc = wp - owp - 1; - l->argv = (char **) alloc(sizeofN(char *, l->argc+2), &l->area); - for (wp = l->argv; (*wp++ = *owp++) != NULL; ) - ; - } - /* POSIX says set exit status is 0, but old scripts that use - * getopt(1), use the construct: set -- $(getopt ab:c "$@") - * which assumes the exit value set will be that of the $() - * (subst_exstat is cleared in execute() so that it will be 0 - * if there are no command substitutions). - */ - return Flag(FPOSIX) ? 0 : subst_exstat; -} - -int -c_unset(char **wp) -{ - char *id; - int optc, unset_var = 1; - int ret = 0; - - while ((optc = ksh_getopt(wp, &builtin_opt, "fv")) != EOF) - switch (optc) { - case 'f': - unset_var = 0; - break; - case 'v': - unset_var = 1; - break; - case '?': - return 1; - } - wp += builtin_opt.optind; - for (; (id = *wp) != NULL; wp++) - if (unset_var) { /* unset variable */ - struct tbl *vp = global(id); - - if (!(vp->flag & ISSET)) - ret = 1; - if ((vp->flag&RDONLY)) { - bi_errorf("%s is read only", vp->name); - return 1; - } - unset(vp, strchr(id, '[') ? 1 : 0); - } else { /* unset function */ - if (define(id, (struct op *) NULL)) - ret = 1; - } - return ret; -} - -static void -p_time(struct shf *shf, int posix, struct timeval *tv, int width, - char *prefix, char *suffix) -{ - if (posix) - shf_fprintf(shf, "%s%*ld.%02ld%s", prefix ? prefix : "", - width, (long)tv->tv_sec, tv->tv_usec / 10000, suffix); - else - shf_fprintf(shf, "%s%*ldm%ld.%02lds%s", prefix ? prefix : "", - width, (long)(tv->tv_sec / 60), (long)(tv->tv_sec % 60), - tv->tv_usec / 10000, suffix); -} - -int -c_times(char **wp GCC_FUNC_ATTR(unused)) -{ - 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) -{ -#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 */ - char opts[1]; - - 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); - if (t->left->type == TCOM) - t->left->str = opts; - opts[0] = 0; - rv = execute(t->left, f | XTIME); - tf |= opts[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")) != EOF) - switch (optc) { - case 'p': - t->str[0] |= TF_POSIX; - break; - case '?': - errorf("time: -%s unknown option", opt.optarg); - case ':': - errorf("time: -%s requires an argument", - opt.optarg); - } - /* Copy command words down over options. */ - if (opt.optind != 0) { - for (i = 0; i < opt.optind; i++) - afree(wp[i], ATEMP); - for (i = 0, j = opt.optind; (wp[i] = wp[j]); i++, j++) - ; - } - if (!wp[0]) - t->str[0] |= TF_NOARGS; - *app = wp; -} - -/* exec with no args - args case is taken care of in comexec() */ -int -c_exec(char **wp GCC_FUNC_ATTR(unused)) -{ - int i; - - /* make sure redirects stay in place */ - if (e->savefd != NULL) { - for (i = 0; i < NUFILE; i++) { - if (e->savefd[i] > 0) - close(e->savefd[i]); - /* - * For ksh keep anything > 2 private, - * for sh, let them be (POSIX says what - * happens is unspecified and the bourne shell - * keeps them open). - */ - if (!Flag(FSH) && i > 2 && e->savefd[i]) - fcntl(i, F_SETFD, FD_CLOEXEC); - } - e->savefd = NULL; - } - return 0; -} - -/* dummy function, special case in comexec() */ -int -c_builtin(char **wp GCC_FUNC_ATTR(unused)) -{ - 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}, - {NULL, NULL} -}; diff --git a/c_test.c b/c_test.c deleted file mode 100644 index f452610..0000000 --- a/c_test.c +++ /dev/null @@ -1,626 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: c_test.c,v 1.10 2003/10/10 19:09:07 millert Exp $ */ - -/* - * test(1); version 7-like -- author Erik Baalbergen - * modified by Eric Gisin to be used as built-in. - * modified by Arnold Robbins to add SVR3 compatibility - * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). - * modified by Michael Rendell to add Korn's [[ .. ]] expressions. - * modified by J.T. Conklin to add POSIX compatibility. - */ - -#include "sh.h" -#include "ksh_stat.h" -#include "c_test.h" - -__RCSID("$MirOS$"); - -/* test(1) accepts the following grammar: - oexpr ::= aexpr | aexpr "-o" oexpr ; - aexpr ::= nexpr | nexpr "-a" aexpr ; - nexpr ::= primary | "!" nexpr ; - primary ::= unary-operator operand - | operand binary-operator operand - | operand - | "(" oexpr ")" - ; - - unary-operator ::= "-a"|"-r"|"-w"|"-x"|"-e"|"-f"|"-d"|"-c"|"-b"|"-p"| - "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"| - "-L"|"-h"|"-S"|"-H"; - - binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| - "-nt"|"-ot"|"-ef"| - "<"|">" # rules used for [[ .. ]] expressions - ; - operand ::= -*/ - -#define T_ERR_EXIT 2 /* POSIX says > 1 for errors */ - -struct t_op { - char op_text[4]; - Test_op op_num; -}; -static const struct t_op u_ops [] = { - {"-a", TO_FILAXST }, - {"-b", TO_FILBDEV }, - {"-c", TO_FILCDEV }, - {"-d", TO_FILID }, - {"-e", TO_FILEXST }, - {"-f", TO_FILREG }, - {"-G", TO_FILGID }, - {"-g", TO_FILSETG }, - {"-h", TO_FILSYM }, - {"-H", TO_FILCDF }, - {"-k", TO_FILSTCK }, - {"-L", TO_FILSYM }, - {"-n", TO_STNZE }, - {"-O", TO_FILUID }, - {"-o", TO_OPTION }, - {"-p", TO_FILFIFO }, - {"-r", TO_FILRD }, - {"-s", TO_FILGZ }, - {"-S", TO_FILSOCK }, - {"-t", TO_FILTT }, - {"-u", TO_FILSETU }, - {"-w", TO_FILWR }, - {"-x", TO_FILEX }, - {"-z", TO_STZER }, - {"", TO_NONOP } - }; -static const struct t_op b_ops [] = { - {"=", TO_STEQL }, - {"==", 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 *path, struct stat *statb); -static int test_eaccess(const char *path, int mode); -static int test_oexpr(Test_env *te, int do_eval); -static int test_aexpr(Test_env *te, int do_eval); -static int test_nexpr(Test_env *te, int do_eval); -static int test_primary(Test_env *te, int do_eval); -static int ptest_isa(Test_env *te, Test_meta meta); -static const char *ptest_getopnd(Test_env *te, Test_op op, int do_eval); -static int ptest_eval(Test_env *te, Test_op op, const char *opnd1, - const char *opnd2, int do_eval); -static void ptest_error(Test_env *te, int offset, const char *msg); - -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 */ -#ifdef S_ISCHR - return test_stat(opnd1, &b1) == 0 && S_ISCHR(b1.st_mode); -#else - return 0; -#endif - case TO_FILBDEV: /* -b */ -#ifdef S_ISBLK - return test_stat(opnd1, &b1) == 0 && S_ISBLK(b1.st_mode); -#else - return 0; -#endif - case TO_FILFIFO: /* -p */ -#ifdef S_ISFIFO - return test_stat(opnd1, &b1) == 0 && S_ISFIFO(b1.st_mode); -#else - return 0; -#endif - case TO_FILSYM: /* -h -L */ -#ifdef S_ISLNK - return lstat(opnd1, &b1) == 0 && S_ISLNK(b1.st_mode); -#else - return 0; -#endif - case TO_FILSOCK: /* -S */ -#ifdef S_ISSOCK - return test_stat(opnd1, &b1) == 0 && S_ISSOCK(b1.st_mode); -#else - return 0; -#endif - case TO_FILCDF:/* -H HP context dependent files (directories) */ -#ifdef S_ISCDF - { - /* Append a + to filename and check to see if result is a - * setuid directory. CDF stuff in general is hookey, since - * it breaks for the following sequence: echo hi > foo+; - * mkdir foo; echo bye > foo/default; chmod u+s foo - * (foo+ refers to the file with hi in it, there is no way - * to get at the file with bye in it - please correct me if - * I'm wrong about this). - */ - int len = strlen(opnd1); - char *p = str_nsave(opnd1, len + 1, ATEMP); - - p[len++] = '+'; - p[len] = '\0'; - return stat(p, &b1) == 0 && S_ISCDF(b1.st_mode); - } -#else - return 0; -#endif - case TO_FILSETU: /* -u */ -#ifdef S_ISUID - return test_stat(opnd1, &b1) == 0 - && (b1.st_mode & S_ISUID) == S_ISUID; -#else - return 0; -#endif - case TO_FILSETG: /* -g */ -#ifdef S_ISGID - return test_stat(opnd1, &b1) == 0 - && (b1.st_mode & S_ISGID) == S_ISGID; -#else - return 0; -#endif - case TO_FILSTCK: /* -k */ - return test_stat(opnd1, &b1) == 0 - && (b1.st_mode & S_ISVTX) == S_ISVTX; - case TO_FILGZ: /* -s */ - return test_stat(opnd1, &b1) == 0 && b1.st_size > 0L; - case TO_FILTT: /* -t */ - if (opnd1 && !bi_getn(opnd1, &res)) { - te->flags |= TEF_ERROR; - res = 0; - } else { - /* generate error if in FPOSIX mode? */ - res = isatty(opnd1 ? res : 0); - } - return res; - case TO_FILUID: /* -O */ - return test_stat(opnd1, &b1) == 0 && b1.st_uid == ksheuid; - case TO_FILGID: /* -G */ - return test_stat(opnd1, &b1) == 0 && b1.st_gid == getegid(); - /* - * Binary Operators - */ - case TO_STEQL: /* = */ - if (te->flags & TEF_DBRACKET) - return gmatch(opnd1, opnd2, false); - return strcmp(opnd1, opnd2) == 0; - case TO_STNEQ: /* != */ - if (te->flags & TEF_DBRACKET) - return !gmatch(opnd1, opnd2, false); - return strcmp(opnd1, opnd2) != 0; - case TO_STLT: /* < */ - return strcmp(opnd1, opnd2) < 0; - case TO_STGT: /* > */ - return strcmp(opnd1, opnd2) > 0; - case TO_INTEQ: /* -eq */ - case TO_INTNE: /* -ne */ - case TO_INTGE: /* -ge */ - case TO_INTGT: /* -gt */ - case TO_INTLE: /* -le */ - case TO_INTLT: /* -lt */ - { - long v1, v2; - - if (!evaluate(opnd1, &v1, KSH_RETURN_ERROR) - || !evaluate(opnd2, &v2, KSH_RETURN_ERROR)) - { - /* error already printed.. */ - te->flags |= TEF_ERROR; - return 1; - } - switch ((int) op) { - case TO_INTEQ: - return v1 == v2; - case TO_INTNE: - return v1 != v2; - case TO_INTGE: - return v1 >= v2; - case TO_INTGT: - return v1 > v2; - case TO_INTLE: - return v1 <= v2; - case TO_INTLT: - return v1 < v2; - } - } - case TO_FILNT: /* -nt */ - { - int s2; - /* ksh88/ksh93 succeed if file2 can't be stated - * (subtly different from 'does not exist'). - */ - return stat(opnd1, &b1) == 0 - && (((s2 = stat(opnd2, &b2)) == 0 - && b1.st_mtime > b2.st_mtime) || s2 < 0); - } - case TO_FILOT: /* -ot */ - { - int s1; - /* ksh88/ksh93 succeed if file1 can't be stated - * (subtly different from 'does not exist'). - */ - return stat(opnd2, &b2) == 0 - && (((s1 = stat(opnd1, &b1)) == 0 - && b1.st_mtime < b2.st_mtime) || s1 < 0); - } - case TO_FILEQ: /* -ef */ - return stat (opnd1, &b1) == 0 && stat (opnd2, &b2) == 0 - && b1.st_dev == b2.st_dev - && b1.st_ino == b2.st_ino; - } - (*te->error)(te, 0, "internal error: unknown op"); - return 1; -} - -/* Nasty kludge to handle Korn's bizarre /dev/fd hack */ -static int -test_stat(const char *path, struct stat *statb) -{ -#if !defined(HAVE_DEV_FD) - int fd; - - if (strncmp(path, "/dev/fd/", 8) == 0 && getn(path + 8, &fd)) - return fstat(fd, statb); -#endif /* !HAVE_DEV_FD */ - - return stat(path, statb); -} - -/* Routine to handle Korn's /dev/fd hack, and to deal with X_OK on - * non-directories when running as root. - */ -static int -test_eaccess(const char *path, int mode) -{ - int res; - -#if !defined(HAVE_DEV_FD) - int fd; - - /* Note: doesn't handle //dev/fd, etc.. (this is ok) */ - if (strncmp(path, "/dev/fd/", 8) == 0 && getn(path + 8, &fd)) { - int flags; - - if ((flags = fcntl(fd, F_GETFL, 0)) < 0 - || (mode & X_OK) - || ((mode & W_OK) && (flags & O_ACCMODE) == O_RDONLY) - || ((mode & R_OK) && (flags & O_ACCMODE) == O_WRONLY)) - return -1; - return 0; - } -#endif /* !HAVE_DEV_FD */ - - res = eaccess(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; - } - 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 GCC_FUNC_ATTR(unused)) -{ - 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/c_test.h b/c_test.h deleted file mode 100644 index 1ab7a54..0000000 --- a/c_test.h +++ /dev/null @@ -1,61 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: c_test.h,v 1.2 2003/10/22 07:40:38 jmc Exp $ */ - -#ifndef C_TEST_H -#define C_TEST_H - -/* 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 *te, Test_meta meta); - const char *(*getopnd)(Test_env *te, Test_op op, int do_eval); - int (*eval)(Test_env *te, Test_op op, const char *opnd1, - const char *opnd2, int do_eval); - void (*error)(Test_env *te, int offset, const char *msg); -}; - -Test_op test_isop(Test_env *te, Test_meta meta, const char *s); -int test_eval(Test_env *te, Test_op op, const char *opnd1, - const char *opnd2, int do_eval); -int test_parse(Test_env *te); - -#endif /* ndef C_TEST_H */ diff --git a/c_ulimit.c b/c_ulimit.c deleted file mode 100644 index e2db217..0000000 --- a/c_ulimit.c +++ /dev/null @@ -1,284 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: c_ulimit.c,v 1.10 2003/10/22 07:40:38 jmc Exp $ */ - -/* - ulimit -- handle "ulimit" builtin - - Reworked to use getrusage() and ulimit() at once (as needed on - some schizophrenic systems, eg, HP-UX 9.01), made argument parsing - conform to at&t ksh, added autoconf support. Michael Rendell, May, '94 - - Eric Gisin, September 1988 - Adapted to PD KornShell. Removed AT&T code. - - last edit: 06-Jun-1987 D A Gwyn - - This started out as the BRL UNIX System V system call emulation - for 4.nBSD, and was later extended by Doug Kingston to handle - the extended 4.nBSD resource limits. It now includes the code - that was originally under case SYSULIMIT in source file "xec.c". -*/ - -#include "sh.h" -#include -#ifdef HAVE_SYS_RESOURCE_H -# include -#endif /* HAVE_SYS_RESOURCE_H */ -#ifdef HAVE_ULIMIT_H -# include -#else /* HAVE_ULIMIT_H */ -# ifdef HAVE_ULIMIT -extern long ulimit(); -# endif /* HAVE_ULIMIT */ -#endif /* HAVE_ULIMIT_H */ - -__RCSID("$MirOS$"); - -#define SOFT 0x1 -#define HARD 0x2 - -#ifdef RLIM_INFINITY -# define KSH_RLIM_INFINITY RLIM_INFINITY -#else -# define KSH_RLIM_INFINITY ((rlim_t) 1 << (sizeof(rlim_t) * 8 - 1) - 1) -#endif /* RLIM_INFINITY */ - -int -c_ulimit(char **wp) -{ - static const struct limits { - const char *name; - enum { RLIMIT, ULIMIT } which; - int gcmd; /* get command */ - int scmd; /* set command (or -1, if no set command) */ - int factor; /* multiply by to get rlim_{cur,max} values */ - char option; - } limits[] = { - /* Do not use options -H, -S or -a */ -#ifdef RLIMIT_CPU - { "time(cpu-seconds)", RLIMIT, RLIMIT_CPU, RLIMIT_CPU, 1, 't' }, -#endif -#ifdef RLIMIT_TIME - { "humantime(seconds)", RLIMIT, RLIMIT_TIME, RLIMIT_TIME, 1, 'T' }, -#endif -#ifdef RLIMIT_FSIZE - { "file(blocks)", RLIMIT, RLIMIT_FSIZE, RLIMIT_FSIZE, 512, 'f' }, -#else /* RLIMIT_FSIZE */ -# ifdef UL_GETFSIZE /* x/open */ - { "file(blocks)", ULIMIT, UL_GETFSIZE, UL_SETFSIZE, 1, 'f' }, -# else /* UL_GETFSIZE */ -# ifdef UL_GFILLIM /* svr4/xenix */ - { "file(blocks)", ULIMIT, UL_GFILLIM, UL_SFILLIM, 1, 'f' }, -# else /* UL_GFILLIM */ - { "file(blocks)", ULIMIT, 1, 2, 1, 'f' }, -# endif /* UL_GFILLIM */ -# endif /* UL_GETFSIZE */ -#endif /* RLIMIT_FSIZE */ -#ifdef RLIMIT_CORE - { "coredump(blocks)", RLIMIT, RLIMIT_CORE, RLIMIT_CORE, 512, 'c' }, -#endif -#ifdef RLIMIT_DATA - { "data(KiB)", RLIMIT, RLIMIT_DATA, RLIMIT_DATA, 1024, 'd' }, -#endif -#ifdef RLIMIT_STACK - { "stack(KiB)", RLIMIT, RLIMIT_STACK, RLIMIT_STACK, 1024, 's' }, -#endif -#ifdef RLIMIT_MEMLOCK - { "lockedmem(KiB)", RLIMIT, RLIMIT_MEMLOCK, RLIMIT_MEMLOCK, 1024, 'l' }, -#endif -#ifdef RLIMIT_RSS - { "memory(KiB)", RLIMIT, RLIMIT_RSS, RLIMIT_RSS, 1024, 'm' }, -#endif -#ifdef RLIMIT_NOFILE - { "nofiles(descriptors)", RLIMIT, RLIMIT_NOFILE, RLIMIT_NOFILE, 1, 'n' }, -#else /* RLIMIT_NOFILE */ -# ifdef UL_GDESLIM /* svr4/xenix */ - { "nofiles(descriptors)", ULIMIT, UL_GDESLIM, -1, 1, 'n' }, -# endif /* UL_GDESLIM */ -#endif /* RLIMIT_NOFILE */ -#ifdef RLIMIT_NPROC - { "processes", RLIMIT, RLIMIT_NPROC, RLIMIT_NPROC, 1, 'p' }, -#endif -#ifdef RLIMIT_VMEM - { "vmemory(KiB)", RLIMIT, RLIMIT_VMEM, RLIMIT_VMEM, 1024, 'v' }, -#else /* RLIMIT_VMEM */ - /* These are not quite right - really should subtract etext or something */ -# ifdef UL_GMEMLIM /* svr4/xenix */ - { "vmemory(maxaddr)", ULIMIT, UL_GMEMLIM, -1, 1, 'v' }, -# else /* UL_GMEMLIM */ -# ifdef UL_GETBREAK /* osf/1 */ - { "vmemory(maxaddr)", ULIMIT, UL_GETBREAK, -1, 1, 'v' }, -# else /* UL_GETBREAK */ -# ifdef UL_GETMAXBRK /* hpux */ - { "vmemory(maxaddr)", ULIMIT, UL_GETMAXBRK, -1, 1, 'v' }, -# endif /* UL_GETMAXBRK */ -# endif /* UL_GETBREAK */ -# endif /* UL_GMEMLIM */ -#endif /* RLIMIT_VMEM */ -#ifdef RLIMIT_SWAP - { "swap(KiB)", RLIMIT, RLIMIT_SWAP, RLIMIT_SWAP, 1024, 'w' }, -#endif - { NULL, RLIMIT, 0, 0, 0, 0 } - }; - static char options[3 + NELEM(limits)]; - rlim_t UNINITIALIZED(val); - int how = SOFT | HARD; - const struct limits *l; - int set, all = 0; - int optc, what; -#ifdef HAVE_SETRLIMIT - struct rlimit limit; -#endif /* HAVE_SETRLIMIT */ - - if (!options[0]) { - /* build options string on first call - yuck */ - char *p = options; - - *p++ = 'H'; *p++ = 'S'; *p++ = 'a'; - for (l = limits; l->name; l++) - *p++ = l->option; - *p = '\0'; - } - what = 'f'; - while ((optc = ksh_getopt(wp, &builtin_opt, options)) != EOF) - switch (optc) { - case 'H': - how = HARD; - break; - case 'S': - how = SOFT; - break; - case 'a': - all = 1; - break; - case '?': - return 1; - default: - what = optc; - } - - for (l = limits; l->name && l->option != what; l++) - ; - if (!l->name) { - internal_errorf(0, "ulimit: %c", what); - return 1; - } - - wp += builtin_opt.optind; - set = *wp ? 1 : 0; - if (set) { - if (all || wp[1]) { - bi_errorf("too many arguments"); - return 1; - } - if (strcmp(wp[0], "unlimited") == 0) - val = KSH_RLIM_INFINITY; - else { - long rval; - - if (!evaluate(wp[0], &rval, KSH_RETURN_ERROR)) - return 1; - /* Avoid problems caused by typos that - * evaluate misses due to evaluating unset - * parameters to 0... - * If this causes problems, will have to - * add parameter to evaluate() to control - * if unset params are 0 or an error. - */ - if (!rval && !digit(wp[0][0])) { - bi_errorf("invalid limit: %s", wp[0]); - return 1; - } - val = rval * l->factor; - } - } - if (all) { - for (l = limits; l->name; l++) { -#ifdef HAVE_SETRLIMIT - if (l->which == RLIMIT) { - getrlimit(l->gcmd, &limit); - if (how & SOFT) - val = limit.rlim_cur; - else if (how & HARD) - val = limit.rlim_max; - } -#ifdef HAVE_ULIMIT - else -#endif -#endif /* HAVE_SETRLIMIT */ -#ifdef HAVE_ULIMIT - { - val = ulimit(l->gcmd, (rlim_t) 0); - } -#else /* HAVE_ULIMIT */ - ; -#endif /* HAVE_ULIMIT */ - shprintf("%-20s ", l->name); -#ifdef RLIM_INFINITY - if (val == RLIM_INFINITY) - shprintf("unlimited\n"); - else -#endif /* RLIM_INFINITY */ - { - val /= l->factor; - shprintf("%ld\n", (long) val); - } - } - return 0; - } -#ifdef HAVE_SETRLIMIT - if (l->which == RLIMIT) { - getrlimit(l->gcmd, &limit); - if (set) { - if (how & SOFT) - limit.rlim_cur = val; - if (how & HARD) - limit.rlim_max = val; - if (setrlimit(l->scmd, &limit) < 0) { - if (errno == EPERM) - bi_errorf("exceeds allowable limit"); - else - bi_errorf("bad limit: %s", - strerror(errno)); - return 1; - } - } else { - if (how & SOFT) - val = limit.rlim_cur; - else if (how & HARD) - val = limit.rlim_max; - } - } -#ifdef HAVE_ULIMIT - else -#endif -#endif /* HAVE_SETRLIMIT */ -#ifdef HAVE_ULIMIT - { - if (set) { - if (l->scmd == -1) { - bi_errorf("can't change limit"); - return 1; - } else if (ulimit(l->scmd, val) < 0) { - bi_errorf("bad limit: %s", strerror(errno)); - return 1; - } - } else - val = ulimit(l->gcmd, (rlim_t) 0); - } -#else /* HAVE_ULIMIT */ - ; -#endif /* HAVE_ULIMIT */ - if (!set) { -#ifdef RLIM_INFINITY - if (val == RLIM_INFINITY) - shprintf("unlimited\n"); - else -#endif /* RLIM_INFINITY */ - { - val /= l->factor; - shprintf("%ld\n", (long) val); - } - } - return 0; -} diff --git a/conf-end.h b/conf-end.h deleted file mode 100644 index 969adf8..0000000 --- a/conf-end.h +++ /dev/null @@ -1,82 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: conf-end.h,v 1.2 1996/08/25 12:37:58 downsj Exp $ */ - -#ifndef CONF_END_H -#define CONF_END_H - -/* Include job control? */ -#define JOBS 1 - -/* Include complex history? */ -#define COMPLEX_HISTORY - -/* Specify default $ENV? */ -/* #undef DEFAULT_ENV */ - -/* - * The above are defined for mirbsdksh via external - * means, such as this header ;-) - * End of configuration stuff for PD ksh. - */ - -#if SIZEOF_INT < 4 -# error "int cannot hold 32 bit" -#endif - -/* - * if you don't have mmap() you can't use Peter Collinson's history - * mechanism. If that is the case, then define EASY_HISTORY - */ -#if !defined(COMPLEX_HISTORY) || !defined(HAVE_MMAP) || !defined(HAVE_FLOCK) -# undef COMPLEX_HISTORY -# define EASY_HISTORY /* sjg's trivial history file */ -#endif - -/* Can we safely catch sigchld and wait for processes? */ -#if defined(HAVE_WAITPID) || defined(HAVE_WAIT3) -# define JOB_SIGS -#endif - -#if !defined(JOB_SIGS) || !(defined(POSIX_PGRP) || defined(BSD_PGRP)) -# undef JOBS /* if no JOB_SIGS, no job control support */ -#endif - -#ifdef HAVE_SYS_PARAM_H -# include -#else -# ifdef HAVE_SYS_TYPES_H -# include -# endif /* HAVE_SYS_TYPES_H */ -#endif /* HAVE_SYS_PARAM_H */ - -#ifdef HAVE_STDINT_H -# include -#endif - -#ifdef HAVE_GCC_FUNC_ATTR -# define GCC_FUNC_ATTR(x) __attribute__((x)) -# define GCC_FUNC_ATTR2(x,y) __attribute__((x,y)) -#else -# define GCC_FUNC_ATTR(x) -# define GCC_FUNC_ATTR2(x,y) -#endif /* HAVE_GCC_FUNC_ATTR */ - -#ifdef HAVE_STDBOOL_H -#include -#else -#if defined(__GNUC__) && __GNUC__ >= 3 -/* Support for _C99: type _Bool is already built-in. */ -#define false 0 -#define true 1 -#else -typedef enum { - false = 0, - true = 1 -} _Bool; -#define false false -#define true true -#endif -#define bool _Bool -#endif - -#endif /* ndef CONF_END_H */ diff --git a/config.h b/config.h deleted file mode 100644 index 6c16fd1..0000000 --- a/config.h +++ /dev/null @@ -1,322 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: config.h,v 1.9 2003/10/22 07:40:38 jmc Exp $ */ - -/* - * This is mirbsdksh "config.h" for both MirOS systems, on which it - * is statically generated by the operating system maintainers, and - * for the portable mirbsdksh, where it is being fed to and genera- - * ted by the configure script. - */ - -#ifndef CONFIG_H -#define CONFIG_H - -/* Define if on AIX 3. - System headers sometimes define this. - We just want to avoid a redefinition error message. */ -#ifndef _ALL_SOURCE -/* #undef _ALL_SOURCE */ -#endif - -/* Define if the closedir function returns void instead of int. */ -/* #undef CLOSEDIR_VOID */ - -/* Define to empty if the keyword does not work. */ -/* #undef const */ - -/* Define to 'int' if doesn't define. */ -/* #undef gid_t */ - -/* Define if you have a working 'mmap' system call. */ -#define HAVE_MMAP 1 - -/* Define if your struct stat has st_rdev. */ -#define HAVE_ST_RDEV 1 - -/* Define if you have . */ -#define HAVE_UNISTD_H 1 - -/* Define if on MINIX. */ -/* #undef _MINIX */ - -/* Define to 'int' if doesn't define. */ -/* #undef mode_t */ - -/* Define to 'long' if doesn't define. */ -/* #undef off_t */ - -/* Define to 'int' if doesn't define. */ -/* #undef pid_t */ - -/* Define if the system does not provide POSIX.1 features except - with this defined. */ -/* #undef _POSIX_1_SOURCE */ - -/* Define if you need to in order for stat and other things to work. */ -/* #undef _POSIX_SOURCE */ - -/* Define as the return type of signal handlers (int or void). */ -#define RETSIGTYPE void - -/* Define if the 'S_IS*' macros in do not work properly. */ -/* #undef STAT_MACROS_BROKEN */ - -/* Define to 'int' if doesn't define. */ -/* #undef uid_t */ - -/* Define if the closedir function returns void instead of int. */ -/* #undef VOID_CLOSEDIR */ - -/* Define if your kernel doesn't handle scripts starting with #! */ -/* #undef SHARPBANG */ - -/* Define if dup2() preserves the close-on-exec flag (ultrix does this) */ -/* #undef DUP2_BROKEN */ - -/* Define as the return value of signal handlers (0 or ). */ -#define RETSIGVAL - -/* Define if you have bsd versions of the setpgrp() and getpgrp() routines */ -/* #undef BSD_PGRP */ - -/* Define if you have POSIX versions of the setpgid() and getpgrp() routines */ -#define POSIX_PGRP 1 - -/* Define if you have sysV versions of the setpgrp() and getpgrp() routines */ -/* #undef SYSV_PGRP */ - -/* Define if you don't have setpgrp(), setpgid() or getpgrp() routines */ -/* #undef NO_PGRP */ - -/* Define to char if your compiler doesn't like the void keyword */ -/* #undef void */ - -/* Define to nothing if compiler doesn't like the volatile keyword */ -/* #undef volatile */ - -/* Define if C compiler groks __attribute__((...)) (const, noreturn, format) */ -#define HAVE_GCC_FUNC_ATTR 1 - -/* Define to 32-bit signed integer type if doesn't define */ -/* #undef clock_t */ - -/* Define to the type of struct rlimit fields if the rlim_t type is missing */ -/* #undef rlim_t */ - -/* Define to 'unsigned' if doesn't define */ -/* #undef sigset_t */ - -/* Define if you have a sane header file */ -#define HAVE_TERMIOS_H 1 - -/* Define if you have a memset() function in your C library */ -#define HAVE_MEMSET 1 - -/* Define if you have a memmove() function in your C library */ -#define HAVE_MEMMOVE 1 - -/* Define if you have a bcopy() function in your C library */ -/* #undef HAVE_BCOPY */ - -/* Define if you have a lstat() function in your C library */ -#define HAVE_LSTAT 1 - -/* Define if you have a sane header file */ -/* #undef HAVE_TERMIO_H */ - -/* Define if opendir() will open non-directory files */ -/* #undef OPENDIR_DOES_NONDIR */ - -/* Define if the pgrp of setpgrp() can't be the pid of a zombie process */ -/* #undef NEED_PGRP_SYNC */ - -/* Define if you arg running SCO unix */ -/* #undef OS_SCO */ - -/* Define if you arg running ISC unix */ -/* #undef OS_ISC */ - -/* Define if you have a POSIX.1 compatible */ -#define POSIX_SYS_WAIT 1 - -/* Define if your OS maps references to /dev/fd/n to file descriptor n */ -#define HAVE_DEV_FD 1 - -/* Default PATH */ -#define DEFAULT_PATH "/bin:/usr/bin:/sbin:/usr/sbin" - -/* Define if your C library's getwd/getcwd function dumps core in unreadable - * directories. */ -/* #undef HPUX_GETWD_BUG */ - -/* The number of bytes in a int. */ -#define SIZEOF_INT 4 - -/* The number of bytes in a long. */ -#define SIZEOF_LONG 4 - -/* Define if you have the _setjmp function. */ -/* #undef HAVE__SETJMP */ - -/* Define if you have the arc4random function. */ -#define HAVE_ARC4RANDOM 1 - -/* Define if you have the arc4random_addrandom function. */ -#define HAVE_ARC4RANDOM_ADDRANDOM 1 - -/* Define if you have the arc4random_push function. */ -#define HAVE_ARC4RANDOM_PUSH 1 - -/* Define if you have the break function. */ -/* #undef HAVE_BREAK */ - -/* Define if you have the confstr function. */ -#define HAVE_CONFSTR 1 - -/* Define if you have the dup2 function. */ -#define HAVE_DUP2 1 - -/* Define if you have the flock function. */ -#define HAVE_FLOCK 1 - -/* Define if you have the getcwd function. */ -#define HAVE_GETCWD 1 - -/* Define if you have the getgroups function. */ -#define HAVE_GETGROUPS 1 - -/* Define if you have the getpagesize function. */ -#define HAVE_GETPAGESIZE 1 - -/* Define if you have the getrusage function. */ -#define HAVE_GETRUSAGE 1 - -/* Define if you have the getwd function. */ -#define HAVE_GETWD 1 - -/* Define if you have the killpg function. */ -#define HAVE_KILLPG 1 - -/* Define if you have the mkstemp function. */ -#define HAVE_MKSTEMP 1 - -/* Define if you have the nice function. */ -#define HAVE_NICE 1 - -/* Define if you have the random function. */ -#define HAVE_RANDOM 1 - -/* Define if you have the revoke function. */ -#define HAVE_REVOKE 1 - -/* Define if you have the setrlimit function. */ -#define HAVE_SETRLIMIT 1 - -/* Define if you have the setsid function. */ -#define HAVE_SETSID 1 - -/* Define if you have the sigsetjmp function. */ -#define HAVE_SIGSETJMP 1 - -/* Define if you have the srandom function. */ -#define HAVE_SRANDOM 1 - -/* Define if you have the strcasecmp function. */ -#define HAVE_STRCASECMP 1 - -/* Define if you have the strerror function. */ -#define HAVE_STRERROR 1 - -/* Define if you have the strlcat function. */ -#define HAVE_STRLCAT 1 - -/* Define if you have the strlcpy function. */ -#define HAVE_STRLCPY 1 - -/* Define if you have the strstr function. */ -#define HAVE_STRSTR 1 - -/* Define if you have the sysconf function. */ -#define HAVE_SYSCONF 1 - -/* Define if you have the tcsetpgrp function. */ -#define HAVE_TCSETPGRP 1 - -/* Define if you have the ulimit function. */ -/* #undef HAVE_ULIMIT */ - -/* Define if you have the valloc function. */ -#define HAVE_VALLOC 1 - -/* Define if you have the wait3 function. */ -#define HAVE_WAIT3 1 - -/* Define if you have the waitpid function. */ -#define HAVE_WAITPID 1 - -/* Define if you have the header file. */ -#define HAVE_DIRENT_H 1 - -/* Define if you have the header file. */ -#define HAVE_FCNTL_H 1 - -/* Define if you have the header file. */ -#define HAVE_LIMITS_H 1 - -/* Define if you have the header file. */ -#define HAVE_MEMORY_H 1 - -/* Define if you have the header file. */ -/* #undef HAVE_NDIR_H */ - -/* Define if you have the header file. */ -#define HAVE_PATHS_H 1 - -/* Define if you have the header file. */ -#define HAVE_STDBOOL_H 1 - -/* Define if you have the header file. */ -#define HAVE_STDDEF_H 1 - -/* Define if you have the header file. */ -#define HAVE_STDINT_H 1 - -/* Define if you have the header file. */ -#define HAVE_STDLIB_H 1 - -/* Define if you have the header file. */ -#define HAVE_STRING_H 1 - -/* Define if you have the header file. */ -/* #undef HAVE_SYS_DIR_H */ - -/* Define if you have the header file. */ -/* #undef HAVE_SYS_NDIR_H */ - -/* Define if you have the header file. */ -#define HAVE_SYS_PARAM_H 1 - -/* Define if you have the header file. */ -#define HAVE_SYS_RESOURCE_H 1 - -/* Define if you have the header file. */ -#define HAVE_SYS_TIME_H 1 - -/* Define if you have the header file. */ -#define HAVE_SYS_TYPES_H 1 - -/* Define if you have the header file. */ -#define HAVE_SYS_WAIT_H 1 - -/* Define if you have the header file. */ -/* #undef HAVE_ULIMIT_H */ - -/* Define if you have the header file. */ -/* #undef HAVE_VALUES_H */ - -/* Need to use a separate file to keep the configure script from commenting - * out the undefs.... - */ -#include "conf-end.h" - -#endif /* CONFIG_H */ diff --git a/edit.c b/edit.c deleted file mode 100644 index 6eb04e4..0000000 --- a/edit.c +++ /dev/null @@ -1,955 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: edit.c,v 1.23 2004/12/18 22:12:23 millert Exp $ */ - -/* - * Command line editing - common code - * - */ - -#include "config.h" - -#include "sh.h" -#include "tty.h" -#define EXTERN -#include "edit.h" -#undef EXTERN -#ifdef OS_SCO /* SCO Unix 3.2v4.1 */ -# include /* needed for */ -# include /* needed for struct winsize */ -#endif /* OS_SCO */ -#include -#include -#include "ksh_stat.h" - -__RCSID("$MirOS$"); - -#if defined(TIOCGWINSZ) -static RETSIGTYPE x_sigwinch(int sig); -static volatile sig_atomic_t got_sigwinch; -static void check_sigwinch(void); -#endif /* TIOCGWINSZ */ - -#if !defined(_POSIX_VDISABLE) || (_POSIX_VDISABLE < 0) -#define _POSIX_VDISABLE 0377 -#endif - -static int x_file_glob(int flags, const char *str, int slen, - char ***wordsp); -static int x_command_glob(int flags, const char *str, int slen, - char ***wordsp); -static int x_locate_word(const char *buf, int buflen, int pos, - int *startp, int *is_command); - -/* 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 */ - -#ifdef TIOCGWINSZ -# ifdef SIGWINCH - if (setsig(&sigtraps[SIGWINCH], x_sigwinch, SS_RESTORE_ORIG|SS_SHTRAP)) - sigtraps[SIGWINCH].flags |= TF_SHELL_USES; -# endif /* SIGWINCH */ - got_sigwinch = 1; /* force initial check */ - check_sigwinch(); -#endif /* TIOCGWINSZ */ - - x_init_emacs(); -} - -#if defined(TIOCGWINSZ) -static RETSIGTYPE -x_sigwinch(int sig GCC_FUNC_ATTR(unused)) -{ - got_sigwinch = 1; - return RETSIGVAL; -} - -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); - } - } -} -#endif /* TIOCGWINSZ */ - -/* - * read an edited command line - */ -int -x_read(char *buf, size_t len) -{ - int i; - -#if defined(TIOCGWINSZ) - if (got_sigwinch) - check_sigwinch(); -#endif /* TIOCGWINSZ */ - - x_mode(true); - if (Flag(FEMACS) || Flag(FGMACS)) - i = x_emacs(buf, len); - else if (Flag(FVI)) - i = x_vi(buf, len); - else - i = -1; /* internal error */ - x_mode(false); - return i; -} - -/* tty I/O */ - -int -x_getc(void) -{ - char c; - int n; - - while ((n = blocking_read(0, &c, 1)) < 0 && errno == EINTR) - if (trap) { - x_mode(false); - runtraps(0); - x_mode(true); - } - if (n != 1) - return -1; - return (int) (unsigned char) c; -} - -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; - -#if defined(HAVE_TERMIOS_H) || defined(HAVE_TERMIO_H) - edchars.erase = cb.c_cc[VERASE]; - edchars.kill = cb.c_cc[VKILL]; - edchars.intr = cb.c_cc[VINTR]; - edchars.quit = cb.c_cc[VQUIT]; - edchars.eof = cb.c_cc[VEOF]; -# ifdef VWERASE - edchars.werase = cb.c_cc[VWERASE]; -# endif -# ifdef _CRAY2 /* brain-damaged terminal handler */ - cb.c_lflag &= ~(ICANON|ECHO); - /* rely on print routine to map '\n' to CR,LF */ -# else - cb.c_iflag &= ~(INLCR|ICRNL); -# ifdef _BSD_SYSV /* need to force CBREAK instead of RAW (need CRMOD on output) */ - cb.c_lflag &= ~(ICANON|ECHO); -# else - cb.c_lflag &= ~(ISIG|ICANON|ECHO); -# endif -# ifdef VLNEXT - /* osf/1 processes lnext when ~icanon */ - cb.c_cc[VLNEXT] = _POSIX_VDISABLE; -# endif /* VLNEXT */ -# ifdef VDISCARD - /* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */ - cb.c_cc[VDISCARD] = _POSIX_VDISABLE; -# endif /* VDISCARD */ - cb.c_cc[VTIME] = 0; - cb.c_cc[VMIN] = 1; -# endif /* _CRAY2 */ -#else - /* Assume BSD tty stuff. */ - edchars.erase = cb.sgttyb.sg_erase; - edchars.kill = cb.sgttyb.sg_kill; - cb.sgttyb.sg_flags &= ~ECHO; - cb.sgttyb.sg_flags |= CBREAK; -# ifdef TIOCGATC - edchars.intr = cb.lchars.tc_intrc; - edchars.quit = cb.lchars.tc_quitc; - edchars.eof = cb.lchars.tc_eofc; - edchars.werase = cb.lchars.tc_werasc; - cb.lchars.tc_suspc = -1; - cb.lchars.tc_dsuspc = -1; - cb.lchars.tc_lnextc = -1; - cb.lchars.tc_statc = -1; - cb.lchars.tc_intrc = -1; - cb.lchars.tc_quitc = -1; - cb.lchars.tc_rprntc = -1; -# else - edchars.intr = cb.tchars.t_intrc; - edchars.quit = cb.tchars.t_quitc; - edchars.eof = cb.tchars.t_eofc; - cb.tchars.t_intrc = -1; - cb.tchars.t_quitc = -1; -# ifdef TIOCGLTC - edchars.werase = cb.ltchars.t_werasc; - cb.ltchars.t_suspc = -1; - cb.ltchars.t_dsuspc = -1; - cb.ltchars.t_lnextc = -1; - cb.ltchars.t_rprntc = -1; -# endif -# endif /* TIOCGATC */ -#endif /* HAVE_TERMIOS_H || HAVE_TERMIO_H */ - - tcsetattr(tty_fd, TCSADRAIN, &cb); - -#ifdef __CYGWIN__ - if (edchars.eof == '\0') - edchars.eof = '\4'; -#endif /* __CYGWIN__ */ - - /* 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) - x_emacs_keys(&edchars); - } else { - tcsetattr(tty_fd, TCSADRAIN, &tty_state); - } - - return prev; -} - -/* NAME: - * promptlen - calculate the length of PS1 etc. - * - * DESCRIPTION: - * This function is based on a fix from guy@demon.co.uk - * It fixes a bug in that if PS1 contains '!', the length - * given by strlen() is probably wrong. - * - * RETURN VALUE: - * length - */ -int -promptlen(const char *cp, const char **spp) -{ - int count = 0; - const char *sp = cp; - char delimiter = 0; - int indelimit = 0; - - /* Undocumented AT&T ksh feature: - * If the second char in the prompt string is \r then the first char - * is taken to be a non-printing delimiter and any chars between two - * instances of the delimiter are not considered to be part of the - * prompt length - */ - if (*cp && cp[1] == '\r') { - delimiter = *cp; - cp += 2; - } - - for (; *cp; cp++) { - if (indelimit && *cp != delimiter) - ; - else if (*cp == '\n' || *cp == '\r') { - count = 0; - sp = cp + 1; - } else if (*cp == '\t') { - count = (count | 7) + 1; - } else if (*cp == '\b') { - if (count > 0) - count--; - } else if (*cp == delimiter) - indelimit = !indelimit; - else - count++; - } - if (spp) - *spp = sp; - return count; -} - -void -set_editmode(const char *ed) -{ - static const enum sh_flag edit_flags[] = { - FEMACS, FGMACS, - FVI, - }; - char *rcp; - unsigned i; - - if ((rcp = ksh_strrchr_dirsep(ed))) - ed = ++rcp; - for (i = 0; i < NELEM(edit_flags); i++) - if (strstr(ed, options[(int) edit_flags[i]].name)) { - change_flag(edit_flags[i], OF_SPECIAL, 1); - return; - } -} - -/* ------------------------------------------------------------------------- */ -/* Misc common code for vi/emacs */ - -/* Handle the commenting/uncommenting of a line. - * Returns: - * 1 if a carriage return is indicated (comment added) - * 0 if no return (comment removed) - * -1 if there is an error (not enough room for comment chars) - * If successful, *lenp contains the new length. Note: cursor should be - * moved to the start of the line after (un)commenting. - */ -int -x_do_comment(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 - && !ISDIRSEP(words[0][prefix_len - 1])) - prefix_len--; - use_copy = 1; - XPinit(l, nwords + 1); - for (i = 0; i < nwords; i++) - XPput(l, words[i] + prefix_len); - XPput(l, 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 GCC_FUNC_ATTR(unused), const char *str, - int slen, char ***wordsp) -{ - char *toglob; - char **words; - int nwords, i, idx, escaping; - XPtrV w; - struct source *s, *sold; - - if (slen < 0) - return 0; - - toglob = add_glob(str, slen); - - /* remove all escaping backward slashes */ - escaping = 0; - for(i = 0, idx = 0; toglob[i]; i++) { - if (toglob[i] == '\\' && !escaping) { - escaping = 1; - continue; - } - - toglob[idx] = toglob[i]; - idx++; - if (escaping) escaping = 0; - } - toglob[idx] = '\0'; - - /* - * Convert "foo*" (toglob) to an array of strings (words) - */ - sold = source; - s = pushs(SWSTR, ATEMP); - s->start = s->str = toglob; - source = s; - if (yylex(ONEWORD) != LWORD) { - source = sold; - internal_errorf(0, "fileglob: substitute error"); - return 0; - } - source = sold; - XPinit(w, 32); - expand(yylval.cp, &w, DOGLOB|DOTILDE|DOMARKDIRS); - XPput(w, NULL); - words = (char **) XPclose(w); - - for (nwords = 0; words[nwords]; nwords++) - ; - if (nwords == 1) { - struct stat statb; - - /* Check if globbing failed (returned glob pattern), - * but be careful (E.g. toglob == "ab*" when the file - * "ab*" exists is not an error). - * Also, check for empty result - happens if we tried - * to glob something which evaluated to an empty - * string (e.g., "$FOO" when there is no FOO, etc). - */ - if ((strcmp(words[0], toglob) == 0 - && stat(words[0], &statb) < 0) - || words[0][0] == '\0') - { - x_free_words(nwords, words); - nwords = 0; - } - } - afree(toglob, ATEMP); - - *wordsp = nwords ? words : 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 = FILECMP(a->word + a->base, b->word + b->base); - return t ? t : a->path_order - b->path_order; -} - -static int -x_command_glob(int flags, const char *str, int slen, char ***wordsp) -{ - char *toglob; - char *pat; - char *fpath; - int nwords; - XPtrV w; - struct block *l; - - if (slen < 0) - return 0; - - toglob = add_glob(str, slen); - - /* Convert "foo*" (toglob) to a pattern for future use */ - pat = evalstr(toglob, DOPAT|DOTILDE); - afree(toglob, ATEMP); - - XPinit(w, 32); - - glob_table(pat, &w, &keywords); - glob_table(pat, &w, &aliases); - glob_table(pat, &w, &builtins); - for (l = e->loc; l; l = l->next) - glob_table(pat, &w, &l->funs); - - glob_path(flags, pat, &w, path); - if ((fpath = str_val(global("FPATH"))) != null) - glob_path(flags, pat, &w, fpath); - - nwords = XPsize(w); - - if (!nwords) { - *wordsp = 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 = 0; - char **words = (char **) XPptrv(w); - int path_order = 0; - int i; - - info = (struct path_order_info *) - alloc(sizeof(struct path_order_info) * nwords, ATEMP); - for (i = 0; i < nwords; i++) { - info[i].word = words[i]; - info[i].base = x_basename(words[i], NULL); - if (!last_info || info[i].base != last_info->base - || FILENCMP(words[i], - last_info->word, info[i].base) != 0) - { - last_info = &info[i]; - path_order++; - } - info[i].path_order = path_order; - } - qsort(info, nwords, sizeof(struct path_order_info), - path_order_cmp); - for (i = 0; i < nwords; i++) - words[i] = info[i].word; - afree((void *) info, ATEMP); - } else { - /* Sort and remove duplicate entries */ - char **words = (char **) XPptrv(w); - int i, j; - - qsortp(XPptrv(w), (size_t) nwords, xstrcmp); - - for (i = j = 0; i < nwords - 1; i++) { - if (strcmp(words[i], words[i + 1])) - words[j++] = words[i]; - else - afree(words[i], ATEMP); - } - words[j++] = words[i]; - nwords = j; - w.cur = (void **) &words[j]; - } - - XPput(w, NULL); - *wordsp = (char **) XPclose(w); - - return nwords; -} - -#define IS_WORDC(c) !( ctype(c, C_LEX1) || (c) == '\'' || (c) == '"' \ - || (c) == '`' || (c) == '=' || (c) == ':' ) - -static int -x_locate_word(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(buf[p]); p--) - ; - iscmd = p < 0 || strchr(";|&()`", buf[p]); - if (iscmd) { - /* If command has a /, path, etc. is not searched; - * only current directory is searched, which is just - * like file globbing. - */ - for (p = start; p < end; p++) - if (ISDIRSEP(buf[p])) - break; - iscmd = p == end; - } - *is_commandp = iscmd; - } - - *startp = start; - - return end - start; -} - -int -x_cf_glob(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 (ISDIRSEP(*s)) - saw_slash = true; - } - if (!*s && (*toglob != '~' || saw_slash)) { - toglob[slen] = '*'; - toglob[slen + 1] = '\0'; - } - - return toglob; -} - -/* - * Find longest common prefix - */ -int -x_longest_prefix(int nwords, char *const *words) -{ - int i, j; - int prefix_len; - char *p; - - if (nwords <= 0) - return 0; - - prefix_len = strlen(words[0]); - for (i = 1; i < nwords; i++) - for (j = 0, p = words[i]; j < prefix_len; j++) - if (FILECHCONV(p[j]) != FILECHCONV(words[0][j])) { - prefix_len = j; - break; - } - return prefix_len; -} - -void -x_free_words(int nwords, char **words) -{ - int i; - - for (i = 0; i < nwords; i++) - if (words[i]) - afree(words[i], ATEMP); - afree(words, ATEMP); -} - -/* Return the offset of the basename of string s (which ends at se - need not - * be null terminated). Trailing slashes are ignored. If s is just a slash, - * then the offset is 0 (actually, length - 1). - * s Return - * /etc 1 - * /etc/ 1 - * /etc// 1 - * /etc/fo 5 - * foo 0 - * /// 2 - * 0 - */ -int -x_basename(const char *s, const char *se) -{ - const char *p; - - if (se == NULL) - se = s + strlen(s); - if (s == se) - return 0; - - /* Skip trailing slashes */ - for (p = se - 1; p > s && ISDIRSEP(*p); p--) - ; - for (; p > s && !ISDIRSEP(*p); p--) - ; - if (ISDIRSEP(*p) && p + 1 < se) - p++; - - return p - s; -} - -/* - * Apply pattern matching to a table: all table entries that match a pattern - * are added to wp. - */ -static void -glob_table(const char *pat, XPtrV *wp, struct table *tp) -{ - struct tstate ts; - struct tbl *te; - - for (twalk(&ts, tp); (te = tnext(&ts)); ) { - if (gmatch(te->name, pat, false)) - XPput(*wp, str_save(te->name, ATEMP)); - } -} - -static void -glob_path(int flags, const char *pat, XPtrV *wp, const char *path) -{ - const char *sp, *p; - char *xp; - int staterr; - int pathlen; - int patlen; - int oldsize, newsize, i, j; - char **words; - XString xs; - - patlen = strlen(pat) + 1; - sp = path; - Xinit(xs, xp, patlen + 128, ATEMP); - while (sp) { - xp = Xstring(xs, xp); - if (!(p = strchr(sp, PATHSEP))) - p = sp + strlen(sp); - pathlen = p - sp; - if (pathlen) { - /* Copy sp into xp, stuffing any MAGIC characters - * on the way - */ - const char *s = sp; - - XcheckN(xs, xp, pathlen * 2); - while (s < p) { - if (ISMAGIC(*s)) - *xp++ = MAGIC; - *xp++ = *s++; - } - *xp++ = DIRSEP; - pathlen++; - } - sp = p; - XcheckN(xs, xp, patlen); - memcpy(xp, pat, patlen); - - oldsize = XPsize(*wp); - glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */ - newsize = XPsize(*wp); - - /* Check that each match is executable... */ - words = (char **) XPptrv(*wp); - for (i = j = oldsize; i < newsize; i++) { - staterr = 0; - if ((search_access(words[i], X_OK, &staterr) >= 0) - || (staterr == EISDIR)) { - words[j] = words[i]; - if (!(flags & XCF_FULLPATH)) - memmove(words[j], words[j] + pathlen, - strlen(words[j] + pathlen) + 1); - j++; - } else - afree(words[i], ATEMP); - } - wp->cur = (void **) &words[j]; - - if (!*sp++) - break; - } - Xfree(xs, xp); -} - -/* - * if argument string contains any special characters, they will - * be escaped and the result will be put into edit buffer by - * keybinding-specific function - */ -int -x_escape(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); -} diff --git a/edit.h b/edit.h deleted file mode 100644 index 812a9a3..0000000 --- a/edit.h +++ /dev/null @@ -1,74 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: edit.h,v 1.3 1999/11/14 22:04:02 d Exp $ */ -/* $From: edit.h,v 1.2 1994/05/19 18:32:40 michael Exp michael $ */ - -#ifndef EDIT_H -#define EDIT_H - -/* some useful #defines */ -#ifdef EXTERN -# define I__(i) = i -#else -# define I__(i) -# define EXTERN extern -# define EXTERN_DEFINED -#endif - -#define BEL 0x07 - -/* tty driver characters we are interested in */ -typedef struct { - int erase; - int kill; - int werase; - int intr; - int quit; - int eof; -} X_chars; - -EXTERN X_chars edchars; - -/* x_fc_glob() flags */ -#define XCF_COMMAND BIT(0) /* Do command completion */ -#define XCF_FILE BIT(1) /* Do file completion */ -#define XCF_FULLPATH BIT(2) /* command completion: store full path */ -#define XCF_COMMAND_FILE (XCF_COMMAND|XCF_FILE) - -/* edit.c */ -int x_getc(void); -void x_flush(void); -void x_putc(int c); -void x_puts(const char *s); -bool x_mode(bool onoff); -int promptlen(const char *cp, const char **spp); -int x_do_comment(char *buf, int bsize, int *lenp); -void x_print_expansions(int nwords, char *const *words, int is_command); -int x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp, - int *endp, char ***wordsp, int *is_commandp); -int x_longest_prefix(int nwords, char *const *words); -int x_basename(const char *s, const char *se); -void x_free_words(int nwords, char **words); -int x_escape(const char *, size_t, int (*)(const char *s, size_t len)); -/* emacs.c */ -int x_emacs(char *buf, size_t len); -void x_init_emacs(void); -void x_emacs_keys(X_chars *ec); -/* vi.c */ -int x_vi(char *buf, size_t len); - - -#ifdef DEBUG -# define D__(x) x -#else -# define D__(x) -#endif - -/* This lot goes at the END */ -/* be sure not to interfere with anyone else's idea about EXTERN */ -#ifdef EXTERN_DEFINED -# undef EXTERN_DEFINED -# undef EXTERN -#endif -#undef I__ - -#endif /* ndef EDIT_H */ diff --git a/emacs-gen.sh b/emacs-gen.sh deleted file mode 100644 index 1a35d96..0000000 --- a/emacs-gen.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/sh -# $MirOS$ -# $OpenBSD: emacs-gen.sh,v 1.1.1.1 1996/08/14 06:19:10 downsj Exp $ - -case $# in -1) file=$1;; -*) - echo "$0: Usage: $0 path-to-emacs.c" 1>&2 - exit 1 -esac; - -if [ ! -r "$file" ] ;then - echo "$0: can't read $file" 1>&2 - exit 1 -fi - -cat << E_O_F || exit 1 -/* - * NOTE: THIS FILE WAS GENERATED AUTOMATICALLY FROM $file - * - * DO NOT BOTHER EDITING THIS FILE - */ -E_O_F - -# Pass 1: print out lines before @START-FUNC-TAB@ -# and generate defines and function declarations, -sed -e '1,/@START-FUNC-TAB@/d' -e '/@END-FUNC-TAB@/,$d' < $file | - awk 'BEGIN { nfunc = 0; } - /^[ ]*#/ { - print $0; - next; - } - { - fname = $2; - c = substr(fname, length(fname), 1); - if (c == ",") - fname = substr(fname, 1, length(fname) - 1); - if (fname != "0") { - printf "#define XFUNC_%s %d\n", substr(fname, 3, length(fname) - 2), nfunc; - printf "static int %s(int c);\n", fname; - nfunc++; - } - }' || exit 1 - -exit 0 diff --git a/emacs.c b/emacs.c deleted file mode 100644 index 7bd013b..0000000 --- a/emacs.c +++ /dev/null @@ -1,2026 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: emacs.c,v 1.34 2004/12/23 11:29:02 jsg Exp $ */ - -/* - * Emacs-like command line editing and history - * - * created by Ron Natalie at BRL - * modified by Doug Kingston, Doug Gwyn, and Lou Salkind - * adapted to PD ksh by Eric Gisin - */ - -#include "config.h" - -#include "sh.h" -#include "ksh_stat.h" -#include -#include -#include -#include "edit.h" - -__RCSID("$MirOS$"); - -static Area aedit; -#define AEDIT &aedit /* area for kill ring and macro defns */ - -#undef CTRL /* _BSD brain damage */ -#define CTRL(x) ((x) == '?' ? 0x7F : (x) & 0x1F) /* ASCII */ -#define UNCTRL(x) ((x) == 0x7F ? '?' : (x) | 0x40) /* ASCII */ -#define META(x) ((x) & 0x7f) -#define ISMETA(x) (Flag(FEMACSUSEMETA) && ((x) & 0x80)) - - -/* values returned by keyboard functions */ -#define KSTD 0 -#define KEOL 1 /* ^M, ^J */ -#define KINTR 2 /* ^G, ^C */ - -struct x_ftab { - int (*xf_func)(int c); - const char *xf_name; - short xf_flags; -}; - -struct x_defbindings { - u_char xdb_func; /* XFUNC_* */ - unsigned char xdb_tab; - unsigned char xdb_char; -}; - -#define XF_ARG 1 /* command takes number prefix */ -#define XF_NOBIND 2 /* not allowed to bind to function */ -#define XF_PREFIX 4 /* function sets prefix */ - -/* Separator for completion */ -#define is_cfs(c) (c == ' ' || c == '\t' || c == '"' || c == '\'') -#define is_mfs(c) (!(isalnum(c) || c == '_' || c == '$')) /* Separator for motion */ - -#define CHARMASK 0xFF /* 8-bit character mask */ -#define X_NTABS 3 /* normal, meta1, meta2 */ -#define X_TABSZ (CHARMASK+1) /* size of keydef tables etc */ - -/* Arguments for do_complete() - * 0 = enumerate M-= complete as much as possible and then list - * 1 = complete M-Esc - * 2 = list M-? - */ -typedef enum { CT_LIST, /* list the possible completions */ - CT_COMPLETE, /* complete to longest prefix */ - CT_COMPLIST /* complete and then list (if non-exact) */ - } Comp_type; - -/* { from 4.9 edit.h */ -/* - * The following are used for my horizontal scrolling stuff - */ -static char *xbuf; /* beg input buffer */ -static char *xend; /* end input buffer */ -static char *xcp; /* current position */ -static char *xep; /* current end */ -static char *xbp; /* start of visible portion of input buffer */ -static char *xlp; /* last char visible on screen */ -static int x_adj_ok; -/* - * we use x_adj_done so that functions can tell - * whether x_adjust() has been called while they are active. - */ -static int x_adj_done; - -static int xx_cols; -static int x_col; -static int x_displen; -static int x_arg; /* general purpose arg */ -static int x_arg_defaulted;/* x_arg not explicitly set; defaulted to 1 */ - -static int xlp_valid; -/* end from 4.9 edit.h } */ - -static int x_prefix1 = CTRL('['), x_prefix2 = CTRL('X'); -static char **x_histp; /* history position */ -static int x_nextcmd; /* for newline-and-next */ -static char *xmp; /* mark pointer */ -static u_char x_last_command; -static u_char (*x_tab)[X_TABSZ]; /* key definition */ -static char *(*x_atab)[X_TABSZ]; /* macro definitions */ -static unsigned char x_bound[(X_TABSZ * X_NTABS + 7) / 8]; -#define KILLSIZE 20 -static char *killstack[KILLSIZE]; -static int killsp, killtp; -static int x_curprefix; -static char *macroptr; -static int prompt_skip; - -static int x_ins(char *cp); -static void x_delete(int nc, int push); -static int x_bword(void); -static int x_fword(void); -static void x_goto(char *cp); -static void x_bs(int c); -static int x_size_str(char *cp); -static int x_size(int c); -static void x_zots(char *str); -static void x_zotc(int c); -static void x_load_hist(char **hp); -static int x_search(char *pat, int sameline, int offset); -static int x_match(char *str, char *pat); -static void x_redraw(int limit); -static void x_push(int nchars); -static char * x_mapin(const char *cp); -static char * x_mapout(int c); -static void x_print(int prefix, int key); -static void x_adjust(void); -static void x_e_ungetc(int c); -static int x_e_getc(void); -static void x_e_putc(int c); -static void x_e_puts(const char *s); -static int x_comment(int c); -static int x_fold_case(int c); -static char *x_lastcp(void); -static void do_complete(int flags, Comp_type type); -static int x_emacs_putbuf(const char *s, size_t len); - - -/* The lines between START-FUNC-TAB .. END-FUNC-TAB are run through a - * script (emacs-gen.sh) that generates emacs.out which contains: - * - function declarations for x_* functions - * - defines of the form XFUNC_ where is function - * name, sans leading x_. - * Note that the script treats #ifdef and { 0, 0, 0} specially - use with - * caution. - */ -#include "emacs.out" -static const struct x_ftab x_ftab[] = { -/* @START-FUNC-TAB@ */ - { x_abort, "abort", 0 }, - { x_beg_hist, "beginning-of-history", 0 }, - { x_comp_comm, "complete-command", 0 }, - { x_comp_file, "complete-file", 0 }, - { x_complete, "complete", 0 }, - { x_del_back, "delete-char-backward", XF_ARG }, - { x_del_bword, "delete-word-backward", XF_ARG }, - { x_del_char, "delete-char-forward", XF_ARG }, - { x_del_fword, "delete-word-forward", XF_ARG }, - { x_del_line, "kill-line", 0 }, - { x_draw_line, "redraw", 0 }, - { x_end_hist, "end-of-history", 0 }, - { x_end_of_text, "eot", 0 }, - { x_enumerate, "list", 0 }, - { x_eot_del, "eot-or-delete", XF_ARG }, - { x_error, "error", 0 }, - { x_goto_hist, "goto-history", XF_ARG }, - { x_ins_string, "macro-string", XF_NOBIND }, - { x_insert, "auto-insert", XF_ARG }, - { x_kill, "kill-to-eol", XF_ARG }, - { x_kill_region, "kill-region", 0 }, - { x_list_comm, "list-command", 0 }, - { x_list_file, "list-file", 0 }, - { x_literal, "quote", 0 }, - { x_meta1, "prefix-1", XF_PREFIX }, - { x_meta2, "prefix-2", XF_PREFIX }, - { x_meta_yank, "yank-pop", 0 }, - { x_mv_back, "backward-char", XF_ARG }, - { x_mv_begin, "beginning-of-line", 0 }, - { x_mv_bword, "backward-word", XF_ARG }, - { x_mv_end, "end-of-line", 0 }, - { x_mv_forw, "forward-char", XF_ARG }, - { x_mv_fword, "forward-word", XF_ARG }, - { x_newline, "newline", 0 }, - { x_next_com, "down-history", XF_ARG }, - { x_nl_next_com, "newline-and-next", 0 }, - { x_noop, "no-op", 0 }, - { x_prev_com, "up-history", XF_ARG }, - { x_prev_histword, "prev-hist-word", XF_ARG }, - { x_search_char_forw, "search-character-forward", XF_ARG }, - { x_search_char_back, "search-character-backward", XF_ARG }, - { x_search_hist, "search-history", 0 }, - { x_set_mark, "set-mark-command", 0 }, - { x_stuff, "stuff", 0 }, - { x_stuffreset, "stuff-reset", 0 }, - { x_transpose, "transpose-chars", 0 }, - { x_version, "version", 0 }, - { x_xchg_point_mark, "exchange-point-and-mark", 0 }, - { x_yank, "yank", 0 }, - { x_comp_list, "complete-list", 0 }, - { x_expand, "expand-file", 0 }, - { x_fold_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 }, -#ifdef DEBUG - { x_debug_info, "debug-info", 0 }, -#else - { 0, 0, 0 }, -#endif -/* @END-FUNC-TAB@ */ - }; - -static struct x_defbindings const x_defbindings[] = { - { XFUNC_del_back, 0, CTRL('?') }, - { XFUNC_del_bword, 1, CTRL('?') }, - { XFUNC_eot_del, 0, CTRL('D') }, - { XFUNC_del_back, 0, CTRL('H') }, - { XFUNC_del_bword, 1, CTRL('H') }, - { XFUNC_del_bword, 1, 'h' }, - { XFUNC_mv_bword, 1, 'b' }, - { XFUNC_mv_fword, 1, 'f' }, - { XFUNC_del_fword, 1, 'd' }, - { XFUNC_mv_back, 0, CTRL('B') }, - { XFUNC_mv_forw, 0, CTRL('F') }, - { XFUNC_search_char_forw, 0, CTRL(']') }, - { XFUNC_search_char_back, 1, CTRL(']') }, - { XFUNC_newline, 0, CTRL('M') }, - { XFUNC_newline, 0, CTRL('J') }, - { XFUNC_end_of_text, 0, CTRL('_') }, - { XFUNC_abort, 0, CTRL('G') }, - { XFUNC_prev_com, 0, CTRL('P') }, - { XFUNC_next_com, 0, CTRL('N') }, - { XFUNC_nl_next_com, 0, CTRL('O') }, - { XFUNC_search_hist, 0, CTRL('R') }, - { XFUNC_beg_hist, 1, '<' }, - { XFUNC_end_hist, 1, '>' }, - { XFUNC_goto_hist, 1, 'g' }, - { XFUNC_mv_end, 0, CTRL('E') }, - { XFUNC_mv_begin, 0, CTRL('A') }, - { XFUNC_draw_line, 0, CTRL('L') }, - { XFUNC_meta1, 0, CTRL('[') }, - { XFUNC_meta2, 0, CTRL('X') }, - { XFUNC_kill, 0, CTRL('K') }, - { XFUNC_yank, 0, CTRL('Y') }, - { XFUNC_meta_yank, 1, 'y' }, - { XFUNC_literal, 0, CTRL('^') }, - { XFUNC_comment, 1, '#' }, -#if defined(BRL) && defined(TIOCSTI) - { XFUNC_stuff, 0, CTRL('T') }, -#else - { XFUNC_transpose, 0, CTRL('T') }, -#endif - { XFUNC_complete, 1, CTRL('[') }, - { XFUNC_comp_list, 0, CTRL('I') }, - { XFUNC_comp_list, 1, '=' }, - { XFUNC_enumerate, 1, '?' }, - { XFUNC_expand, 1, '*' }, - { XFUNC_comp_file, 1, CTRL('X') }, - { XFUNC_comp_comm, 2, CTRL('[') }, - { XFUNC_list_comm, 2, '?' }, - { XFUNC_list_file, 2, CTRL('Y') }, - { XFUNC_set_mark, 1, ' ' }, - { XFUNC_kill_region, 0, CTRL('W') }, - { XFUNC_xchg_point_mark, 2, CTRL('X') }, - { XFUNC_version, 0, CTRL('V') }, -#ifdef DEBUG - { XFUNC_debug_info, 1, CTRL('H') }, -#endif - { XFUNC_prev_histword, 1, '.' }, - { XFUNC_prev_histword, 1, '_' }, - { XFUNC_set_arg, 1, '0' }, - { XFUNC_set_arg, 1, '1' }, - { XFUNC_set_arg, 1, '2' }, - { XFUNC_set_arg, 1, '3' }, - { XFUNC_set_arg, 1, '4' }, - { XFUNC_set_arg, 1, '5' }, - { XFUNC_set_arg, 1, '6' }, - { XFUNC_set_arg, 1, '7' }, - { XFUNC_set_arg, 1, '8' }, - { XFUNC_set_arg, 1, '9' }, - { XFUNC_fold_upper, 1, 'U' }, - { XFUNC_fold_upper, 1, 'u' }, - { XFUNC_fold_lower, 1, 'L' }, - { XFUNC_fold_lower, 1, 'l' }, - { XFUNC_fold_capitalize, 1, 'C' }, - { XFUNC_fold_capitalize, 1, 'c' }, - /* These for ansi arrow keys: arguablely shouldn't be here by - * default, but its simpler/faster/smaller than using termcap - * entries. - */ - { XFUNC_meta2, 1, '[' }, - { XFUNC_meta2, 1, 'O' }, - { XFUNC_prev_com, 2, 'A' }, - { XFUNC_next_com, 2, 'B' }, - { XFUNC_mv_forw, 2, 'C' }, - { XFUNC_mv_back, 2, 'D' }, -}; - -int -x_emacs(char *buf, size_t len) -{ - int c; - const char *p; - int i; - u_char f; - - xbp = xbuf = buf; xend = buf + len; - xlp = xcp = xep = buf; - *xcp = 0; - xlp_valid = true; - xmp = NULL; - x_curprefix = 0; - macroptr = NULL; - x_histp = histptr + 1; - x_last_command = XFUNC_error; - - xx_cols = x_cols; - x_col = promptlen(prompt, &p); - prompt_skip = p - prompt; - x_adj_ok = 1; - x_displen = xx_cols - 2 - x_col; - x_adj_done = 0; - - pprompt(prompt, 0); - - if (x_nextcmd >= 0) { - int off = source->line - x_nextcmd; - if (histptr - history >= off) - x_load_hist(histptr - off); - x_nextcmd = -1; - } - - while (1) { - x_flush(); - if ((c = x_e_getc()) < 0) - return 0; - - if (ISMETA(c)) { - c = META(c); - x_curprefix = 1; - } - - f = x_curprefix == -1 ? XFUNC_insert - : x_tab[x_curprefix][c&CHARMASK]; - - if (!(x_ftab[f].xf_flags & XF_PREFIX) - && x_last_command != XFUNC_set_arg) - { - x_arg = 1; - x_arg_defaulted = 1; - } - i = c | (x_curprefix << 8); - x_curprefix = 0; - switch (i = (*x_ftab[f].xf_func)(i)) { - case KSTD: - if (!(x_ftab[f].xf_flags & XF_PREFIX)) - x_last_command = f; - break; - case KEOL: - i = xep - xbuf; - return i; - case KINTR: /* special case for interrupt */ - trapsig(SIGINT); - x_mode(false); - unwind(LSHELL); - } - } -} - -static int -x_insert(int c GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - if (macroptr) { - x_e_putc(BEL); - return KSTD; - } - macroptr = x_atab[c>>8][c & CHARMASK]; - if (macroptr && !*macroptr) { - /* XXX bell? */ - macroptr = NULL; - } - return KSTD; -} - -static int x_do_ins(const char *cp, int len); - -static int -x_do_ins(const char *cp, int len) -{ - if (xep+len >= xend) { - x_e_putc(BEL); - return -1; - } - - memmove(xcp+len, xcp, xep - xcp + 1); - memmove(xcp, cp, len); - xcp += len; - xep += len; - return 0; -} - -static int -x_ins(char *s) -{ - char *cp = xcp; - int adj = x_adj_done; - - if (x_do_ins(s, strlen(s)) < 0) - return -1; - /* - * x_zots() may result in a call to x_adjust() - * we want xcp to reflect the new position. - */ - xlp_valid = false; - x_lastcp(); - x_adj_ok = (xcp >= xlp); - x_zots(cp); - if (adj == x_adj_done) /* has x_adjust() been called? */ - { - /* no */ - for (cp = xlp; cp > xcp; ) - x_bs(*--cp); - } - - x_adj_ok = 1; - return 0; -} - -/* - * this is used for x_escape() in do_complete() - */ -static int -x_emacs_putbuf(const char *s, size_t len) -{ - int rval; - - if ((rval = x_do_ins(s, len)) != 0) - return (rval); - return (rval); -} - -static int -x_del_back(int c GCC_FUNC_ATTR(unused)) -{ - int col = xcp - xbuf; - - if (col == 0) { - x_e_putc(BEL); - return KSTD; - } - if (x_arg > col) - x_arg = col; - x_goto(xcp - x_arg); - x_delete(x_arg, false); - return KSTD; -} - -static int -x_del_char(int c GCC_FUNC_ATTR(unused)) -{ - int nleft = xep - xcp; - - if (!nleft) { - x_e_putc(BEL); - return KSTD; - } - if (x_arg > nleft) - x_arg = nleft; - x_delete(x_arg, false); - return KSTD; -} - -/* Delete nc chars to the right of the cursor (including cursor position) */ -static void -x_delete(int nc, int push) -{ - int i,j; - char *cp; - - if (nc == 0) - return; - if (xmp != NULL && xmp > xcp) { - if (xcp + nc > xmp) - xmp = xcp; - else - xmp -= nc; - } - - /* - * This lets us yank a word we have deleted. - */ - if (push) - x_push(nc); - - xep -= nc; - cp = xcp; - j = 0; - i = nc; - while (i--) { - j += x_size(*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 GCC_FUNC_ATTR(unused)) -{ - x_delete(x_bword(), true); - return KSTD; -} - -static int -x_mv_bword(int c GCC_FUNC_ATTR(unused)) -{ - (void)x_bword(); - return KSTD; -} - -static int -x_mv_fword(int c GCC_FUNC_ATTR(unused)) -{ - x_goto(xcp + x_fword()); - return KSTD; -} - -static int -x_del_fword(int c GCC_FUNC_ATTR(unused)) -{ - 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(*--xcp); - } - else - { - if (cp > xcp) /* move forward */ - { - while (cp > xcp) - x_zotc(*xcp++); - } - } - } -} - -static void -x_bs(int c GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - if (c=='\t') - return 4; /* Kludge, tabs are always four spaces. */ - if (iscntrl(c)) /* control char */ - return 2; - return 1; -} - -static void -x_zots(char *str) -{ - int adj = x_adj_done; - - x_lastcp(); - while (*str && str < xlp && adj == x_adj_done) - x_zotc(*str++); -} - -static void -x_zotc(int c GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - int col = xcp - xbuf; - - if (col == 0) { - x_e_putc(BEL); - return KSTD; - } - if (x_arg > col) - x_arg = col; - x_goto(xcp - x_arg); - return KSTD; -} - -static int -x_mv_forw(int c GCC_FUNC_ATTR(unused)) -{ - int nleft = xep - xcp; - - if (!nleft) { - x_e_putc(BEL); - return KSTD; - } - if (x_arg > nleft) - x_arg = nleft; - x_goto(xcp + x_arg); - return KSTD; -} - -static int -x_search_char_forw(int c GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - x_e_putc('\r'); - x_e_putc('\n'); - x_flush(); - *xep++ = '\n'; - return KEOL; -} - -static int -x_end_of_text(int c GCC_FUNC_ATTR(unused)) -{ - return KEOL; -} - -static int -x_beg_hist(int c GCC_FUNC_ATTR(unused)) -{ - x_load_hist(history); - return KSTD; -} - -static int -x_end_hist(int c GCC_FUNC_ATTR(unused)) -{ - x_load_hist(histptr); - return KSTD; -} - -static int -x_prev_com(int c GCC_FUNC_ATTR(unused)) -{ - x_load_hist(x_histp - x_arg); - return KSTD; -} - -static int -x_next_com(int c GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - 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_goto(xep); - else - x_redraw(oldsize); -} - -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)); -} - -/* reverse incremental history search */ -static int -x_search_hist(int c GCC_FUNC_ATTR(unused)) -{ - int offset = -1; /* offset of match in xbuf, else -1 */ - char pat [256+1]; /* pattern buffer */ - char *p = pat; - u_char f; - - *p = '\0'; - while (1) { - if (offset < 0) { - x_e_puts("\nI-search: "); - x_e_puts(pat); - } - x_flush(); - if ((c = x_e_getc()) < 0) - return KSTD; - f = x_tab[0][c&CHARMASK]; - if (c == CTRL('[')) - break; - else if (f == XFUNC_search_hist) - offset = x_search(pat, 0, offset); - else if (f == XFUNC_del_back) { - if (p == pat) { - offset = -1; - break; - } - if (p > pat) - *--p = '\0'; - if (p == pat) - offset = -1; - else - offset = x_search(pat, 1, offset); - continue; - } else if (f == XFUNC_insert) { - /* add char to pattern */ - /* overflow check... */ - if (p >= &pat[sizeof(pat) - 1]) { - x_e_putc(BEL); - continue; - } - *p++ = c, *p = '\0'; - if (offset >= 0) { - /* already have partial match */ - offset = x_match(xbuf, pat); - if (offset >= 0) { - x_goto(xbuf + offset + (p - pat) - (*pat == '^')); - continue; - } - } - offset = x_search(pat, 0, offset); - } else { /* other command */ - x_e_ungetc(c); - break; - } - } - if (offset < 0) - x_redraw(-1); - return KSTD; -} - -/* search backward from current line */ -static int -x_search(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 GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - x_goto(xep); - return KSTD; -} - -static int -x_mv_begin(int c GCC_FUNC_ATTR(unused)) -{ - x_goto(xbuf); - return KSTD; -} - -static int -x_draw_line(int c GCC_FUNC_ATTR(unused)) -{ - 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; - char *cp; - - x_adj_ok = 0; - if (limit == -1) - x_e_putc('\n'); - else - x_e_putc('\r'); - x_flush(); - if (xbp == xbuf) - { - pprompt(prompt + prompt_skip, 0); - x_col = promptlen(prompt, NULL); - } - x_displen = xx_cols - 2 - x_col; - xlp_valid = false; - cp = x_lastcp(); - x_zots(xbp); - if (xbp != xbuf || xep > xlp) - limit = xx_cols; - if (limit >= 0) - { - if (xep > xlp) - i = 0; /* we fill the line */ - else - i = limit - (xlp - xbp); - - for (j = 0; j < i && x_col < (xx_cols - 2); j++) - x_e_putc(' '); - i = ' '; - if (xep > xlp) /* more off screen */ - { - if (xbp > xbuf) - i = '*'; - else - i = '>'; - } - else - if (xbp > xbuf) - i = '<'; - x_e_putc(i); - j++; - while (j--) - x_e_putc('\b'); - } - for (cp = xlp; cp > xcp; ) - x_bs(*--cp); - x_adj_ok = 1; - D__(x_flush();) - return; -} - -static int -x_transpose(int c GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - x_curprefix = -1; - return KSTD; -} - -static int -x_meta1(int c GCC_FUNC_ATTR(unused)) -{ - x_curprefix = 1; - return KSTD; -} - -static int -x_meta2(int c GCC_FUNC_ATTR(unused)) -{ - x_curprefix = 2; - return KSTD; -} - -static int -x_kill(int c GCC_FUNC_ATTR(unused)) -{ - int col = xcp - xbuf; - int lastcol = xep - xbuf; - int ndel; - - if (x_arg_defaulted) - x_arg = lastcol; - else if (x_arg > lastcol) - x_arg = lastcol; - ndel = x_arg - col; - if (ndel < 0) { - x_goto(xbuf + x_arg); - ndel = -ndel; - } - x_delete(ndel, true); - return KSTD; -} - -static void -x_push(int nchars) -{ - char *cp = str_nsave(xcp, nchars, AEDIT); - if (killstack[killsp]) - afree((void *)killstack[killsp], AEDIT); - killstack[killsp] = cp; - killsp = (killsp + 1) % KILLSIZE; -} - -static int -x_yank(int c GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - int len; - if ((x_last_command != XFUNC_yank && x_last_command != XFUNC_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 GCC_FUNC_ATTR(unused)) -{ - /* x_zotc(c); */ - xlp = xep = xcp = xbp = xbuf; - xlp_valid = true; - *xcp = 0; - return KINTR; -} - -static int -x_error(int c GCC_FUNC_ATTR(unused)) -{ - x_e_putc(BEL); - return KSTD; -} - -static int -x_stuffreset(int c GCC_FUNC_ATTR(unused)) -{ -#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 GCC_FUNC_ATTR(unused)) -{ -#if defined(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 * -x_mapin(const char *cp) -{ - char *new, *op; - - op = new = str_save(cp, ATEMP); - while (*cp) { - /* XXX -- should handle \^ escape? */ - if (*cp == '^') { - cp++; - if (*cp >= '?') /* includes '?'; ASCII */ - *op++ = CTRL(*cp); - else { - *op++ = '^'; - cp--; - } - } else - *op++ = *cp; - cp++; - } - *op = '\0'; - - return new; -} - -static char * -x_mapout(int c GCC_FUNC_ATTR(unused)) -{ - static char buf[8]; - char *p = buf; - - if (iscntrl(c)) { - *p++ = '^'; - *p++ = UNCTRL(c); - } else - *p++ = c; - *p = 0; - return buf; -} - -static void -x_print(int prefix, int key) -{ - if (prefix == 1) - shprintf("%s", x_mapout(x_prefix1)); - if (prefix == 2) - shprintf("%s", x_mapout(x_prefix2)); - shprintf("%s = ", x_mapout(key)); - if (x_tab[prefix][key] != XFUNC_ins_string) - shprintf("%s\n", x_ftab[x_tab[prefix][key]].xf_name); - else - shprintf("'%s'\n", x_atab[prefix][key]); -} - -int -x_bind(const char *a1, const char *a2, int macro, int list) - - /* bind -m */ - /* bind -l */ -{ - u_char f; - int prefix, key; - char *sp = NULL; - char *m1, *m2; - - if (x_tab == NULL) { - bi_errorf("cannot bind, not a tty"); - return 1; - } - - /* List function names */ - if (list) { - for (f = 0; f < NELEM(x_ftab); f++) - if (x_ftab[f].xf_name - && !(x_ftab[f].xf_flags & XF_NOBIND)) - shprintf("%s\n", x_ftab[f].xf_name); - return 0; - } - - if (a1 == NULL) { - for (prefix = 0; prefix < X_NTABS; prefix++) - for (key = 0; key < X_TABSZ; key++) { - f = x_tab[prefix][key]; - if (f == XFUNC_insert || f == XFUNC_error - || (macro && f != XFUNC_ins_string)) - continue; - x_print(prefix, key); - } - return 0; - } - - m1 = x_mapin(a1); - prefix = key = 0; - for (;; m1++) { - key = *m1 & CHARMASK; - if (x_tab[prefix][key] == XFUNC_meta1) - prefix = 1; - else if (x_tab[prefix][key] == XFUNC_meta2) - prefix = 2; - else - break; - } - - if (a2 == NULL) { - x_print(prefix, key); - return 0; - } - - if (*a2 == 0) - f = XFUNC_insert; - else if (!macro) { - for (f = 0; f < NELEM(x_ftab); f++) - if (x_ftab[f].xf_name - && strcmp(x_ftab[f].xf_name, a2) == 0) - break; - if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) { - bi_errorf("%s: no such function", a2); - return 1; - } - } else { - f = XFUNC_ins_string; - m2 = x_mapin(a2); - sp = str_save(m2, AEDIT); - } - - if (x_tab[prefix][key] == XFUNC_ins_string && x_atab[prefix][key]) - afree((void *)x_atab[prefix][key], AEDIT); - x_tab[prefix][key] = f; - x_atab[prefix][key] = sp; - - /* Track what the user has bound so x_emacs_keys() won't toast things */ - if (f == XFUNC_insert) - x_bound[(prefix * X_TABSZ + key) / 8] &= - ~(1 << ((prefix * X_TABSZ + key) % 8)); - else - x_bound[(prefix * X_TABSZ + key) / 8] |= - (1 << ((prefix * X_TABSZ + key) % 8)); - - return 0; -} - -void -x_init_emacs(void) -{ - unsigned i; - int j; - char *locale; - - ainit(AEDIT); - x_nextcmd = -1; - - x_tab = (u_char (*)[X_TABSZ]) alloc(sizeofN(*x_tab, X_NTABS), AEDIT); - for (j = 0; j < X_TABSZ; j++) - x_tab[0][j] = XFUNC_insert; - for (i = 1; i < X_NTABS; i++) - for (j = 0; j < X_TABSZ; j++) - x_tab[i][j] = XFUNC_error; - for (i = 0; i < NELEM(x_defbindings); i++) - x_tab[(unsigned char)x_defbindings[i].xdb_tab][x_defbindings[i].xdb_char] - = x_defbindings[i].xdb_func; - - x_atab = (char *(*)[X_TABSZ]) alloc(sizeofN(*x_atab, X_NTABS), AEDIT); - for (i = 1; i < X_NTABS; i++) - for (j = 0; j < X_TABSZ; j++) - x_atab[i][j] = NULL; - - /* Determine if we can translate meta key or use 8-bit AscII - * XXX - It would be nice if there was a locale attribute to - * determine if the locale is 7-bit or not. - */ - locale = setlocale(LC_CTYPE, NULL); - if (locale == NULL || !strcmp(locale, "C") || !strcmp(locale, "POSIX")) - Flag(FEMACSUSEMETA) = 1; -} - -static void bind_if_not_bound(int p, int k, int func); - -static void -bind_if_not_bound(int p, int k, int func) -{ - /* Has user already bound this key? If so, don't override it */ - if (x_bound[((p) * X_TABSZ + (k)) / 8] - & (1 << (((p) * X_TABSZ + (k)) % 8))) - return; - - x_tab[p][k] = func; -} - -void -x_emacs_keys(X_chars *ec) -{ - if (ec->erase >= 0) { - bind_if_not_bound(0, ec->erase, XFUNC_del_back); - bind_if_not_bound(1, ec->erase, XFUNC_del_bword); - } - if (ec->kill >= 0) - bind_if_not_bound(0, ec->kill, XFUNC_del_line); - if (ec->werase >= 0) - bind_if_not_bound(0, ec->werase, XFUNC_del_bword); - if (ec->intr >= 0) - bind_if_not_bound(0, ec->intr, XFUNC_abort); - if (ec->quit >= 0) - bind_if_not_bound(0, ec->quit, XFUNC_noop); -} - -static int -x_set_mark(int c GCC_FUNC_ATTR(unused)) -{ - xmp = xcp; - return KSTD; -} - -static int -x_kill_region(int c GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - return KSTD; -} - -/* - * File/command name completion routines - */ - - -static int -x_comp_comm(int c GCC_FUNC_ATTR(unused)) -{ - do_complete(XCF_COMMAND, CT_COMPLETE); - return KSTD; -} -static int -x_list_comm(int c GCC_FUNC_ATTR(unused)) -{ - do_complete(XCF_COMMAND, CT_LIST); - return KSTD; -} -static int -x_complete(int c GCC_FUNC_ATTR(unused)) -{ - do_complete(XCF_COMMAND_FILE, CT_COMPLETE); - return KSTD; -} -static int -x_enumerate(int c GCC_FUNC_ATTR(unused)) -{ - do_complete(XCF_COMMAND_FILE, CT_LIST); - return KSTD; -} -static int -x_comp_file(int c GCC_FUNC_ATTR(unused)) -{ - do_complete(XCF_FILE, CT_COMPLETE); - return KSTD; -} -static int -x_list_file(int c GCC_FUNC_ATTR(unused)) -{ - do_complete(XCF_FILE, CT_LIST); - return KSTD; -} -static int -x_comp_list(int c GCC_FUNC_ATTR(unused)) -{ - do_complete(XCF_COMMAND_FILE, CT_COMPLIST); - return KSTD; -} -static int -x_expand(int c GCC_FUNC_ATTR(unused)) -{ - char **words; - int nwords = 0; - int start, end; - int is_command; - int i; - - nwords = x_cf_glob(XCF_FILE, - xbuf, xep - xbuf, xcp - xbuf, - &start, &end, &words, &is_command); - - if (nwords == 0) { - x_e_putc(BEL); - return KSTD; - } - - x_goto(xbuf + start); - x_delete(end - start, false); - for (i = 0; i < nwords;) { - if (x_escape(words[i], strlen(words[i]), x_emacs_putbuf) < 0 || - (++i < nwords && x_ins(space) < 0)) - { - x_e_putc(BEL); - return KSTD; - } - } - x_adjust(); - - return KSTD; -} - -/* type == 0 for list, 1 for complete and 2 for complete-list */ -static void -do_complete(int flags, Comp_type type) - /* XCF_{COMMAND,FILE,COMMAND_FILE} */ - -{ - char **words; - int nwords; - int start, end, nlen, olen; - int is_command; - int completed = 0; - - nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf, - &start, &end, &words, &is_command); - /* no match */ - if (nwords == 0) { - x_e_putc(BEL); - return; - } - - if (type == CT_LIST) { - x_print_expansions(nwords, words, is_command); - x_redraw(0); - x_free_words(nwords, words); - return; - } - - olen = end - start; - nlen = x_longest_prefix(nwords, words); - /* complete */ - if (nwords == 1 || nlen > olen) { - x_goto(xbuf + start); - x_delete(olen, false); - x_escape(words[0], nlen, x_emacs_putbuf); - x_adjust(); - completed = 1; - } - /* add space if single non-dir match */ - if ((nwords == 1) && (!ISDIRSEP(words[0][nlen - 1]))) { - x_ins(space); - completed = 1; - } - - if (type == CT_COMPLIST && !completed) { - x_print_expansions(nwords, words, is_command); - completed = 1; - } - - if (completed) - x_redraw(0); - - x_free_words(nwords, words); -} - -/* NAME: - * x_adjust - redraw the line adjusting starting point etc. - * - * DESCRIPTION: - * This function is called when we have exceeded the bounds - * of the edit window. It increments x_adj_done so that - * functions like x_ins and x_delete know that we have been - * called and can skip the x_bs() stuff which has already - * been done by x_redraw. - * - * RETURN VALUE: - * None - */ - -static void -x_adjust(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 GCC_FUNC_ATTR(unused)) -{ - unget_char = c; -} - -static int -x_e_getc(void) -{ - int c; - - if (unget_char >= 0) { - c = unget_char; - unget_char = -1; - } else { - if (macroptr) { - c = *macroptr++; - if (!*macroptr) - macroptr = NULL; - } else - c = x_getc(); - } - - return c <= CHARMASK ? c : (c & CHARMASK); -} - -static void -x_e_putc(int c GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - x_flush(); - shellf("\nksh debug:\n"); - shellf("\tx_col == %d,\t\tx_cols == %d,\tx_displen == %d\n", - x_col, xx_cols, x_displen); - shellf("\txcp == 0x%lx,\txep == 0x%lx\n", (long) xcp, (long) xep); - shellf("\txbp == 0x%lx,\txbuf == 0x%lx\n", (long) xbp, (long) xbuf); - shellf("\txlp == 0x%lx\n", (long) xlp); - shellf("\txlp == 0x%lx\n", (long) x_lastcp()); - shellf(newline); - x_redraw(-1); - return 0; -} -#endif - -static void -x_e_puts(const char *s) -{ - int adj = x_adj_done; - - while (*s && adj == x_adj_done) - x_e_putc(*s++); -} - -/* NAME: - * x_set_arg - set an arg value for next function - * - * DESCRIPTION: - * This is a simple implementation of M-[0-9]. - * - * RETURN VALUE: - * KSTD - */ - -static int -x_set_arg(int c GCC_FUNC_ATTR(unused)) -{ - int n = 0; - int first = 1; - - c &= CHARMASK; /* strip command prefix */ - for (; c >= 0 && isdigit(c); c = x_e_getc(), first = 0) - n = n * 10 + (c - '0'); - if (c < 0 || first) { - x_e_putc(BEL); - x_arg = 1; - x_arg_defaulted = 1; - } else { - x_e_ungetc(c); - x_arg = n; - x_arg_defaulted = 0; - } - return KSTD; -} - - -/* Comment or uncomment the current line. */ -static int -x_comment(int c GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - return x_fold_case('U'); -} - -/* Lowercase N(1) words */ -static int -x_fold_lower(int c GCC_FUNC_ATTR(unused)) -{ - return x_fold_case('L'); -} - -/* Lowercase N(1) words */ -static int -x_fold_capitalize(int c GCC_FUNC_ATTR(unused)) -{ - 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 GCC_FUNC_ATTR(unused)) -{ - 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(*cp)) - *cp = tolower(*cp); - } else { /* uppercase, capitalize */ - if (islower(*cp)) - *cp = toupper(*cp); - } - cp++; - } - /* - * now for the rest of the word - */ - while (cp != xep && !is_mfs(*cp)) { - if (c == 'U') { /* uppercase */ - if (islower(*cp)) - *cp = toupper(*cp); - } else { /* lowercase, capitalize */ - if (isupper(*cp)) - *cp = tolower(*cp); - } - cp++; - } - } - x_goto(cp); - return KSTD; -} - -/* NAME: - * x_lastcp - last visible char - * - * SYNOPSIS: - * x_lastcp() - * - * DESCRIPTION: - * This function returns a pointer to that char in the - * edit buffer that will be the last displayed on the - * screen. The sequence: - * - * for (cp = x_lastcp(); cp > xcp; cp) - * x_bs(*--cp); - * - * Will position the cursor correctly on the screen. - * - * RETURN VALUE: - * cp or NULL - */ - -static char * -x_lastcp(void) -{ - char *rcp; - int i; - - if (!xlp_valid) - { - for (i = 0, rcp = xbp; rcp < xep && i < x_displen; rcp++) - i += x_size(*rcp); - xlp = rcp; - } - xlp_valid = true; - return (xlp); -} diff --git a/eval.c b/eval.c deleted file mode 100644 index 6b21163..0000000 --- a/eval.c +++ /dev/null @@ -1,1310 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: eval.c,v 1.24 2004/12/22 18:52:37 millert Exp $ */ - -/* - * Expansion - quoting, separation, substitution, globbing - */ - -#include "sh.h" -#include -#include -#include "ksh_stat.h" - -__RCSID("$MirOS$"); - -#ifdef OPENDIR_DOES_NONDIR -extern DIR *ksh_opendir(const char *d); -#else /* OPENDIR_DOES_NONDIR */ -# define ksh_opendir(d) opendir(d) -#endif /* OPENDIR_DOES_NONDIR */ - -/* - * 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 *xp, char *sp, char *word, int *stypep, int *slenp); -static int comsub(Expand *xp, char *cp); -static char *trimsub(char *str, char *pat, int how); -static void glob(char *cp, XPtrV *wp, int markdirs); -static void globit(XString *xs, char **xpp, char *sp, XPtrV *wp, - int check); -static char *maybe_expand_tilde(char *p, XString *dsp, char **dpp, - int isassign); -static char *tilde(char *acp); -static char *homedir(char *name); -static void alt_expand(XPtrV *wp, char *start, char *exp_start, - char *end, int fdo); - -/* 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 */ -#ifdef SHARPBANG - XPput(w, NULL); /* and space for one arg */ -#endif - while (*ap != NULL) - expand(*ap++, &w, f); - XPput(w, NULL); -#ifdef SHARPBANG - return (char **) XPclose(w) + 2; -#else - return (char **) XPclose(w) + 1; -#endif -} - -/* - * expand string - */ -char * -evalstr(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, XPtrV *wp, int f) - /* input word */ - /* output words */ - /* DO* flags */ -{ - int UNINITIALIZED(c); - 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 */ - SubType st_head, *st; - int UNINITIALIZED(newlines); /* 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; - if (Flag(FBRACEEXPAND) && (f & DOGLOB)) - f |= DOBRACE_; - - 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); - 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; - - sp = strchr(sp, '\0') + 1; /* skip variable */ - type = varsub(&x, varname, sp, &stype, &slen); - if (type < 0) { - char endc; - char *str, *end; - - sp = varname - 2; /* restore sp */ - end = (char *) wdscan(sp, CSUBST); - /* ({) the } or x is already skipped */ - endc = *end; - *end = EOS; - str = snptreef(NULL, 64, "%S", sp); - *end = endc; - errorf("%s: bad substitution", str); - } - if (f&DOBLANK) - doblank++; - tilde_ok = 0; - if (type == XBASE) { /* expand? */ - if (!st->next) { - SubType *newst; - - newst = (SubType *) alloc( - sizeof(SubType), ATEMP); - newst->next = 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++ = (char)('@' + 0x80); - break; - case '=': - /* Enabling tilde expansion - * after :'s here is - * non-standard ksh, but is - * consistent with rules for - * other assignments. Not - * sure what POSIX thinks of - * this. - * Not doing tilde expansion - * for integer variables is a - * non-POSIX thing - makes - * sense though, since ~ is - * a arithmetic operator. - */ - if (!(x.var->flag & INTEGER)) - f |= DOASNTILDE|DOTILDE; - f |= DOTEMP_; - /* These will be done after the - * value has been assigned. - */ - f &= ~(DOBLANK|DOGLOB|DOBRACE_); - tilde_ok = 1; - break; - case '?': - f &= ~DOBLANK; - f |= DOTEMP_; - /* fall through */ - default: - /* Enable tilde expansion */ - tilde_ok = 1; - f |= DOTILDE; - } - } else - /* skip word */ - sp = (char *) wdscan(sp, CSUBST); - continue; - } - case CSUBST: /* only get here if expanding word */ - sp++; /* ({) skip the } or x */ - tilde_ok = 0; /* in case of ${unset:-} */ - *dp = '\0'; - quote = st->quote; - f = st->f; - if (f&DOBLANK) - doblank--; - switch (st->stype&0x7f) { - case '#': - case '%': - /* Append end-pattern */ - *dp++ = MAGIC; *dp++ = ')'; *dp = '\0'; - dp = Xrestpos(ds, dp, st->base); - /* Must use st->var since calling - * global would break things - * like x[i+=1]. - */ - x.str = trimsub(str_val(st->var), - dp, st->stype); - type = XSUB; - if (f&DOBLANK) - doblank++; - st = st->prev; - continue; - case '=': - /* Restore our position and substitute - * the value of st->var (may not be - * the assigned value in the presence - * of integer/right-adj/etc attributes). - */ - dp = Xrestpos(ds, dp, st->base); - /* Must use st->var since calling - * global would cause with things - * like x[i+=1] to be evaluated twice. - */ - /* Note: not exported by FEXPORT - * in at&t ksh. - */ - /* XXX POSIX says readonly is only - * fatal for special builtins (setstr - * does readonly check). - */ - len = strlen(dp) + 1; - setstr(st->var, - debunk((char *) alloc(len, ATEMP), - dp, len), - KSH_UNWIND_ERROR); - x.str = str_val(st->var); - type = XSUB; - if (f&DOBLANK) - doblank++; - st = st->prev; - continue; - case '?': - { - char *s = Xrestpos(ds, dp, st->base); - - errorf("%s: %s", st->var->name, - dp == s ? - "parameter null or not set" - : (debunk(s, s, strlen(s) + 1), s)); - } - } - st = st->prev; - type = XBASE; - continue; - - case OPAT: /* open pattern: *(foo|bar) */ - /* Next char is the type of pattern */ - make_magic = 1; - c = *sp++ + 0x80; - break; - - case SPAT: /* pattern separator (|) */ - make_magic = 1; - c = '|'; - break; - - case CPAT: /* close pattern */ - make_magic = 1; - c = /*(*/ ')'; - break; - } - break; - - case XNULLSUB: - /* Special case for "$@" (and "${foo[@]}") - no - * word is generated if $# is 0 (unless there is - * other stuff inside the quotes). - */ - type = XBASE; - if (f&DOBLANK) { - doblank--; - /* not really correct: x=; "$x$@" should - * generate a null argument and - * set A; "${@:+}" shouldn't. - */ - if (dp == Xstring(ds, dp)) - word = IFS_WS; - } - continue; - - case XSUB: - case XSUBMID: - if ((c = *x.str++) == 0) { - type = XBASE; - if (f&DOBLANK) - doblank--; - continue; - } - break; - - case XARGSEP: - type = XARG; - quote = 1; - case XARG: - if ((c = *x.str++) == '\0') { - /* force null words to be created so - * set -- '' 2 ''; foo "$@" will do - * the right thing - */ - if (quote && x.split) - word = IFS_WORD; - if ((x.str = *x.u.strv++) == NULL) { - type = XBASE; - if (f&DOBLANK) - doblank--; - continue; - } - c = ifs0; - if (c == 0) { - if (quote && !x.split) - continue; - c = ' '; - } - if (quote && x.split) { - /* terminate word for "$@" */ - type = XARGSEP; - quote = 0; - } - } - break; - - case XCOM: - if (newlines) { /* Spit out saved nl's */ - c = '\n'; - --newlines; - } else { - while ((c = shf_getc(x.u.shf)) == 0 || c == '\n') - if (c == '\n') - newlines++; /* Save newlines */ - if (newlines && c != EOF) { - shf_ungetc(c, x.u.shf); - c = '\n'; - --newlines; - } - } - if (c == EOF) { - newlines = 0; - shf_close(x.u.shf); - if (x.split) - subst_exstat = waitlast(); - type = XBASE; - if (f&DOBLANK) - doblank--; - continue; - } - break; - } - - /* check for end of word or IFS separation */ - if (c == 0 || (!quote && (f & DOBLANK) && doblank && !make_magic - && ctype(c, C_IFS))) - { - /* How words are broken up: - * | value of c - * word | ws nws 0 - * ----------------------------------- - * IFS_WORD w/WS w/NWS w - * IFS_WS -/WS w/NWS - - * IFS_NWS -/NWS w/NWS w - * (w means generate a word) - * Note that IFS_NWS/0 generates a word (at&t ksh - * doesn't do this, but POSIX does). - */ - if (word == IFS_WORD - || (!ctype(c, C_IFSWS) && c && word == IFS_NWS)) - { - char *p; - - *dp++ = '\0'; - p = Xclose(ds, dp); - if (fdo & DOBRACE_) - /* also does globbing */ - alt_expand(wp, p, p, - p + Xlength(ds, (dp - 1)), - fdo | (f & DOMARKDIRS)); - else 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 = strdup(""); - - if (p == NULL) - internal_errorf(1, "unable " - "to allocate memory"); - XPput(*wp, p); - } - type = XSUBMID; - } - /* age tilde_ok info - ~ code tests second bit */ - tilde_ok <<= 1; - /* mark any special second pass chars */ - if (!quote) - switch (c) { - case '[': - case NOT: - case '-': - case ']': - /* For character classes - doesn't hurt - * to have magic !,-,]'s outside of - * [...] expressions. - */ - if (f & (DOPAT | DOGLOB)) { - fdo |= DOMAGIC_; - if (c == '[') - fdo |= f & DOGLOB; - *dp++ = MAGIC; - } - break; - case '*': - case '?': - if (f & (DOPAT | DOGLOB)) { - fdo |= DOMAGIC_ | (f & DOGLOB); - *dp++ = MAGIC; - } - break; - case OBRACE: - case ',': - case CBRACE: - if ((f & DOBRACE_) && (c == OBRACE - || (fdo & DOBRACE_))) - { - fdo |= DOBRACE_|DOMAGIC_; - *dp++ = MAGIC; - } - break; - case '=': - /* Note first unquoted = for ~ */ - if (!(f & DOTEMP_) && !saw_eq) { - saw_eq = 1; - tilde_ok = 1; - } - break; - case PATHSEP: /* : */ - /* Note unquoted : for ~ */ - if (!(f & DOTEMP_) && (f & DOASNTILDE)) - tilde_ok = 1; - break; - case '~': - /* tilde_ok is reset whenever - * any of ' " $( $(( ${ } are seen. - * Note that tilde_ok must be preserved - * through the sequence ${A=a=}~ - */ - if (type == XBASE - && (f & (DOTILDE|DOASNTILDE)) - && (tilde_ok & 2)) - { - char *p, *dp_x; - - dp_x = dp; - p = maybe_expand_tilde(sp, - &ds, &dp_x, - f & DOASNTILDE); - if (p) { - if (dp != dp_x) - word = IFS_WORD; - dp = dp_x; - sp = p; - continue; - } - } - break; - } - else - quote &= ~2; /* undo temporary */ - - if (make_magic) { - make_magic = 0; - fdo |= DOMAGIC_ | (f & DOGLOB); - *dp++ = MAGIC; - } else if (ISMAGIC(c)) { - fdo |= DOMAGIC_; - *dp++ = MAGIC; - } - *dp++ = c; /* save output char */ - word = IFS_WORD; - } - } -} - -/* - * Prepare to generate the string returned by ${} substitution. - */ -static int -varsub(Expand *xp, char *sp, char *word, int *stypep, int *slenp) - - - - /* becomes qualifier type */ - /* " " len (=, :=, etc.) valid iff *stypep != 0 */ -{ - int c; - int state; /* next state: XBASE, XARG, XSUB, XNULLSUB */ - int stype; /* substitution type */ - int slen; - char *p; - struct tbl *vp; - - if (sp[0] == '\0') /* Bad variable name */ - return -1; - - xp->var = NULL; - - /* ${#var}, string length or array size */ - if (sp[0] == '#' && (c = sp[1]) != '\0') { - int zero_ok = 0; - - /* Can't have any modifiers for ${#...} */ - if (*word != CSUBST) - return -1; - sp++; - /* Check for size of array */ - if ((p=strchr(sp,'[')) && (p[1]=='*'||p[1]=='@') && p[2]==']') { - int n = 0; - int max = 0; - vp = global(arrayname(sp)); - if (vp->flag & (ISSET|ARRAY)) - zero_ok = 1; - for (; vp; vp = vp->u.array) - if (vp->flag & ISSET) { - max = vp->index + 1; - n++; - } - c = n; /* ksh88/ksh93 go for number, not max index */ - } else if (c == '*' || c == '@') - c = e->loc->argc; - else { - p = str_val(global(sp)); - zero_ok = p != null; - c = strlen(p); - } - if (Flag(FNOUNSET) && c == 0 && !zero_ok) - errorf("%s: parameter not set", sp); - *stypep = 0; /* unqualified variable/string substitution */ - xp->str = str_save(ulton((unsigned long)c, 10), ATEMP); - return XSUB; - } - - /* Check for qualifiers in word part */ - stype = 0; - c = word[slen = 0] == CHAR ? word[1] : 0; - if (c == ':') { - slen += 2; - stype = 0x80; - c = word[slen + 0] == CHAR ? word[slen + 1] : 0; - } - if (ctype(c, C_SUBOP1)) { - slen += 2; - stype |= c; - } else if (ctype(c, C_SUBOP2)) { /* Note: ksh88 allows :%, :%%, etc */ - slen += 2; - stype = c; - if (word[slen + 0] == CHAR && c == word[slen + 1]) { - stype |= 0x80; - slen += 2; - } - } else if (stype) /* : is not ok */ - return -1; - if (!stype && *word != CSUBST) - return -1; - *stypep = stype; - *slenp = slen; - - c = sp[0]; - if (c == '*' || c == '@') { - switch (stype & 0x7f) { - case '=': /* can't assign to a vector */ - case '%': /* can't trim a vector (yet) */ - case '#': - return -1; - } - if (e->loc->argc == 0) { - xp->str = null; - xp->var = global(sp); - state = c == '@' ? XNULLSUB : XSUB; - } else { - xp->u.strv = (const char **) e->loc->argv + 1; - xp->str = *xp->u.strv++; - xp->split = c == '@'; /* $@ */ - state = XARG; - } - } else { - if ((p=strchr(sp,'[')) && (p[1]=='*'||p[1]=='@') && p[2]==']') { - XPtrV wv; - - switch (stype & 0x7f) { - case '=': /* can't assign to a vector */ - case '%': /* can't trim a vector (yet) */ - case '#': - case '?': - return -1; - } - XPinit(wv, 32); - vp = global(arrayname(sp)); - for (; vp; vp = vp->u.array) { - if (!(vp->flag&ISSET)) - continue; - XPput(wv, str_val(vp)); - } - if (XPsize(wv) == 0) { - xp->str = null; - state = p[1] == '@' ? XNULLSUB : XSUB; - XPfree(wv); - } else { - XPput(wv, 0); - xp->u.strv = (const char **) XPptrv(wv); - xp->str = *xp->u.strv++; - xp->split = p[1] == '@'; /* ${foo[@]} */ - state = XARG; - } - } else { - /* Can't assign things like $! or $1 */ - if ((stype & 0x7f) == '=' - && (ctype(*sp, C_VAR1) || digit(*sp))) - return -1; - xp->var = global(sp); - xp->str = str_val(xp->var); - state = XSUB; - } - } - - c = stype&0x7f; - /* test the compiler's code generator */ - if (ctype(c, C_SUBOP2) || - (((stype&0x80) ? *xp->str=='\0' : xp->str==null) ? /* undef? */ - c == '=' || c == '-' || c == '?' : c == '+')) - state = XBASE; /* expand word instead of variable value */ - if (Flag(FNOUNSET) && xp->str == null - && (ctype(c, C_SUBOP2) || (state != XBASE && c != '+'))) - errorf("%s: parameter not set", sp); - return state; -} - -/* - * Run the command in $(...) and read its output. - */ -static int -comsub(Expand *xp, char *cp) -{ - Source *s, *sold; - struct op *t; - struct shf *shf; - - s = pushs(SSTRING, ATEMP); - s->start = s->str = cp; - sold = source; - t = compile(s); - source = sold; - - if (t == NULL) - return XBASE; - - if (t != NULL && t->type == TCOM && /* $(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) - errorf("%s: cannot open $() input", name); - xp->split = 0; /* no waitlast() */ - } else { - int ofd1, pv[2]; - openpipe(pv); - shf = shf_fdopen(pv[0], SHF_RD, NULL); - ofd1 = savefd(1, 0); /* fd 1 may be closed... */ - if (pv[1] != 1) { - ksh_dup2(pv[1], 1, false); - close(pv[1]); - } - execute(t, XFORK|XXCOM|XPIPEO); - restfd(1, ofd1); - startlast(); - xp->split = 1; /* waitlast() */ - } - - xp->u.shf = shf; - return XCOM; -} - -/* - * perform #pattern and %pattern substitution in ${} - */ - -static char * -trimsub(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, char **xpp, char *sp, XPtrV *wp, int check) - /* dest string */ - /* ptr to dest end */ - /* source path */ - /* output list */ - /* 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) - && ISDIRSEP(xp[-1]) && !S_ISDIR(lstatb.st_mode) -#ifdef S_ISLNK - && (!S_ISLNK(lstatb.st_mode) - || stat_check() < 0 - || !S_ISDIR(statb.st_mode)) -#endif /* S_ISLNK */ - ) - return; - /* Possibly tack on a trailing / if there isn't already - * one and if the file is a directory or a symlink to a - * directory - */ - if (((check & GF_MARKDIR) && (check & GF_GLOBBED)) - && xp > Xstring(*xs, xp) && !ISDIRSEP(xp[-1]) - && (S_ISDIR(lstatb.st_mode) -#ifdef S_ISLNK - || (S_ISLNK(lstatb.st_mode) - && stat_check() > 0 - && S_ISDIR(statb.st_mode)) -#endif /* S_ISLNK */ - )) - { - *xp++ = DIRSEP; - *xp = '\0'; - } - } - XPput(*wp, str_nsave(Xstring(*xs, xp), Xlength(*xs, xp), - ATEMP)); - return; - } - - if (xp > Xstring(*xs, xp)) - *xp++ = DIRSEP; - while (ISDIRSEP(*sp)) { - Xcheck(*xs, xp); - *xp++ = *sp++; - } - np = ksh_strchr_dirsep(sp); - if (np != NULL) { - se = np; - odirsep = *np; /* don't assume DIRSEP, can be multiple kinds */ - *np++ = '\0'; - } else { - odirsep = '\0'; /* keep gcc quiet */ - se = sp + strlen(sp); - } - - - /* Check if sp needs globbing - done to avoid pattern checks for strings - * containing MAGIC characters, open ['s without the matching close ], - * etc. (otherwise opendir() will be called which may fail because the - * directory isn't readable - if no globbing is needed, only execute - * permission should be required (as per POSIX)). - */ - if (!has_globbing(sp, se)) { - XcheckN(*xs, xp, se - sp + 1); - debunk(xp, sp, 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 = ksh_opendir(prefix_len ? Xstring(*xs, xp) : "."); - if (dirp == NULL) - goto Nodir; - while ((d = readdir(dirp)) != NULL) { - name = d->d_name; - if (name[0] == '.' && - (name[1] == 0 || (name[1] == '.' && name[2] == 0))) - continue; /* always ignore . and .. */ - if ((*name == '.' && *sp != '.') - || !gmatch(name, sp, true)) - continue; - - len = 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; -} - -/* remove MAGIC from string */ -char * -debunk(char *dp, const char *sp, size_t dlen) -{ - char *d, *s; - - if ((s = strchr(sp, MAGIC))) { - if ((size_t)(s - sp) >= dlen) - return dp; - memcpy(dp, sp, s - sp); - for (d = dp + (s - sp); *s && ((size_t)(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 ((size_t)(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 && !ISDIRSEP(p[1]) - && (!isassign || p[1] != PATHSEP)) - { - Xcheck(ts, tp); - *tp++ = p[1]; - p += 2; - } - *tp = '\0'; - r = (p[0] == EOS || p[0] == CHAR || p[0] == CSUBST) ? tilde(Xstring(ts, tp)) : 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 = tenter(&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; -} - -static void -alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo) -{ - int UNINITIALIZED(count); - char *brace_start, *brace_end, *UNINITIALIZED(comma); - char *field_start; - char *p; - - /* search for open brace */ - for (p = exp_start; (p = strchr(p, MAGIC)) && p[1] != OBRACE; p += 2) - ; - brace_start = p; - - /* find matching close brace, if any */ - if (p) { - comma = 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 = (char *) alloc(l1 + l2 + l3 + 1, ATEMP); - memcpy(new, start, l1); - memcpy(new + l1, field_start, l2); - memcpy(new + l1 + l2, brace_end, l3); - new[l1 + l2 + l3] = '\0'; - alt_expand(wp, new, new + l1, - new + l1 + l2 + l3, fdo); - field_start = p + 1; - } - } - } - return; -} diff --git a/exec.c b/exec.c deleted file mode 100644 index 5e0bfc3..0000000 --- a/exec.c +++ /dev/null @@ -1,1497 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: exec.c,v 1.35 2004/12/18 22:35:41 millert Exp $ */ - -/* - * execute command tree - */ - -#include "sh.h" -#include "c_test.h" -#include -#include "ksh_stat.h" - -__RCSID("$MirOS$"); - -static int comexec(struct op *t, struct tbl *volatile tp, char **ap, - int volatile flags); -static void scriptexec(struct op *tp, char **ap); -static int call_builtin(struct tbl *tp, char **wp); -static int iosetup(struct ioword *iop, struct tbl *tp); -static int herein(const char *content, int sub); -static char *do_selectargs(char **ap, bool print_menu); -static int dbteste_isa(Test_env *te, Test_meta meta); -static const char *dbteste_getopnd(Test_env *te, Test_op op, - int do_eval); -static int dbteste_eval(Test_env *te, Test_op op, const char *opnd1, - const char *opnd2, int do_eval); -static void dbteste_error(Test_env *te, int offset, const char *msg); - - -/* - * handle systems that don't have F_SETFD - */ -#ifndef F_SETFD -# ifndef MAXFD -# define MAXFD 64 -# endif -/* a bit field would be smaller, but this will work */ -static char clexec_tab[MAXFD+1]; -#endif - -/* - * execute command tree - */ -int -execute(struct op *volatile t, volatile int flags) - - /* if XEXEC don't fork */ -{ - int i; - volatile int rv = 0; - int pv[2]; - char ** volatile ap; - char *s, *cp; - struct ioword **iowp; - struct tbl *tp = NULL; - - if (t == NULL) - return 0; - - /* Is this the end of a pipeline? If so, we want to evaluate the - * command arguments - bool eval_done = false; - if ((flags&XFORK) && !(flags&XEXEC) && (flags&XPCLOSE)) { - eval_done = true; - tp = eval_execute_args(t, &ap); - } - */ - if ((flags&XFORK) && !(flags&XEXEC) && t->type != TPIPE) - return exchild(t, flags & ~XTIME, -1); /* run in sub-process */ - - newenv(E_EXEC); - if (trap) - runtraps(0); - - if (t->type == TCOM) { - /* Clear subst_exstat before argument expansion. Used by - * null commands (see comexec() and c_eval()) and by c_set(). - */ - subst_exstat = 0; - - current_lineno = t->lineno; /* for $LINENO */ - - /* POSIX says expand command words first, then redirections, - * and assignments last.. - */ - ap = eval(t->args, t->u.evalflags | DOBLANK | DOGLOB | DOTILDE); - if (flags & XTIME) - /* Allow option parsing (bizarre, but POSIX) */ - timex_hook(t, &ap); - if (Flag(FXTRACE) && ap[0]) { - shf_fprintf(shl_out, "%s", - substitute(str_val(global("PS4")), 0)); - for (i = 0; ap[i]; i++) - shf_fprintf(shl_out, "%s%s", ap[i], - ap[i + 1] ? space : newline); - shf_flush(shl_out); - } - if (ap[0]) - tp = findcom(ap[0], FC_BI|FC_FUNC); - } - flags &= ~XTIME; - - if (t->ioact != NULL || t->type == TPIPE || t->type == TCOPROC) { - e->savefd = (short *) alloc(sizeofN(short, NUFILE), ATEMP); - /* initialize to not redirected */ - memset(e->savefd, 0, sizeofN(short, NUFILE)); - } - - /* do redirection, to be restored in quitenv() */ - if (t->ioact != NULL) - for (iowp = t->ioact; *iowp != NULL; iowp++) { - if (iosetup(*iowp, tp) < 0) { - exstat = rv = 1; - /* Redirection failures for special commands - * cause (non-interactive) shell to exit. - */ - if (tp && tp->type == CSHELL - && (tp->flag & SPEC_BI)) - errorf(null); - /* Deal with FERREXIT, quitenv(), etc. */ - goto Break; - } - } - - switch(t->type) { - case TCOM: - rv = comexec(t, tp, ap, flags); - break; - - case TPAREN: - rv = execute(t->left, flags|XFORK); - break; - - case TPIPE: - flags |= XFORK; - flags &= ~XEXEC; - e->savefd[0] = savefd(0, 0); - (void) ksh_dup2(e->savefd[0], 0, false); /* stdin of first */ - e->savefd[1] = savefd(1, 0); - while (t->type == TPIPE) { - openpipe(pv); - (void) ksh_dup2(pv[1], 1, false); /* stdout of curr */ - /* Let exchild() close pv[0] in child - * (if this isn't done, commands like - * (: ; cat /etc/termcap) | sleep 1 - * will hang forever). - */ - exchild(t->left, flags|XPIPEO|XCCLOSE, pv[0]); - (void) ksh_dup2(pv[0], 0, false); /* stdin of next */ - closepipe(pv); - flags |= XPIPEI; - t = t->right; - } - restfd(1, e->savefd[1]); /* stdout of last */ - e->savefd[1] = 0; /* no need to re-restore this */ - /* Let exchild() close 0 in parent, after fork, before wait */ - i = exchild(t, flags|XPCLOSE, 0); - if (!(flags&XBGND) && !(flags&XXCOM)) - rv = i; - break; - - case TLIST: - while (t->type == TLIST) { - execute(t->left, flags & XERROK); - t = t->right; - } - rv = execute(t, flags & XERROK); - break; - - case TCOPROC: - { -# ifdef JOB_SIGS - sigset_t omask; -# endif /* JOB_SIGS */ - -# ifdef JOB_SIGS - /* Block sigchild as we are using things changed in the - * signal handler - */ - sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); - e->type = E_ERRH; - i = ksh_sigsetjmp(e->jbuf, 0); - if (i) { - sigprocmask(SIG_SETMASK, &omask, NULL); - quitenv(NULL); - unwind(i); - /*NOTREACHED*/ - } -# endif /* JOB_SIGS */ - /* Already have a (live) co-process? */ - if (coproc.job && coproc.write >= 0) - errorf("coprocess already exists"); - - /* Can we re-use the existing co-process pipe? */ - coproc_cleanup(true); - - /* do this before opening pipes, in case these fail */ - e->savefd[0] = savefd(0, 0); - e->savefd[1] = savefd(1, 0); - - openpipe(pv); - 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; - } -# ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); - e->type = E_EXEC; /* no more need for error handler */ -# endif /* JOB_SIGS */ - - /* exchild() closes coproc.* in child after fork, - * will also increment coproc.njobs when the - * job is actually created. - */ - flags &= ~XEXEC; - exchild(t->left, flags|XBGND|XFORK|XCOPROC|XCCLOSE, - coproc.readw); - break; - } - - case TASYNC: - /* XXX non-optimal, I think - "(foo &)", forks for (), - * forks again for async... parent should optimize - * this to "foo &"... - */ - rv = execute(t->left, (flags&~XEXEC)|XBGND|XFORK); - break; - - case TOR: - case TAND: - rv = execute(t->left, XERROK); - if (t->right != NULL && (rv == 0) == (t->type == TAND)) - rv = execute(t->right, flags & XERROK); - else - flags |= XERROK; - break; - - case TBANG: - rv = !execute(t->right, XERROK); - break; - - case TDBRACKET: - { - Test_env te; - - te.flags = TEF_DBRACKET; - te.pos.wp = t->args; - te.isa = dbteste_isa; - te.getopnd = dbteste_getopnd; - te.eval = dbteste_eval; - te.error = dbteste_error; - - rv = test_parse(&te); - break; - } - - case TFOR: - case TSELECT: - { - volatile bool is_first = true; - ap = (t->vars != NULL) ? - eval(t->vars, DOBLANK|DOGLOB|DOTILDE) - : e->loc->argv + 1; - e->type = E_LOOP; - while (1) { - i = ksh_sigsetjmp(e->jbuf, 0); - if (!i) - break; - if ((e->flags&EF_BRKCONT_PASS) - || (i != LBREAK && i != LCONTIN)) - { - quitenv(NULL); - unwind(i); - } else if (i == LBREAK) { - rv = 0; - goto Break; - } - } - rv = 0; /* in case of a continue */ - if (t->type == TFOR) { - while (*ap != NULL) { - setstr(global(t->str), *ap++, KSH_UNWIND_ERROR); - rv = execute(t->left, flags & XERROK); - } - } 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); - } - } - } - break; - - case TWHILE: - case TUNTIL: - e->type = E_LOOP; - while (1) { - i = ksh_sigsetjmp(e->jbuf, 0); - if (!i) - break; - if ((e->flags&EF_BRKCONT_PASS) - || (i != LBREAK && i != LCONTIN)) - { - quitenv(NULL); - unwind(i); - } else if (i == LBREAK) { - rv = 0; - goto Break; - } - } - rv = 0; /* in case of a continue */ - while ((execute(t->left, XERROK) == 0) == (t->type == TWHILE)) - rv = execute(t->right, flags & XERROK); - break; - - case TIF: - case TELIF: - if (t->right == NULL) - break; /* should be error */ - rv = execute(t->left, XERROK) == 0 ? - execute(t->right->left, flags & XERROK) : - execute(t->right->right, flags & XERROK); - break; - - case TCASE: - cp = evalstr(t->str, DOTILDE); - for (t = t->left; t != NULL && t->type == TPAT; t = t->right) - for (ap = t->vars; *ap; ap++) - if ((s = evalstr(*ap, DOTILDE|DOPAT)) - && gmatch(cp, s, false)) - goto Found; - break; - Found: - rv = execute(t->left, flags & XERROK); - break; - - case TBRACE: - rv = execute(t->left, flags & XERROK); - break; - - case TFUNCT: - rv = define(t->str, t); - break; - - case TTIME: - /* Clear XEXEC so nested execute() call doesn't exit - * (allows "ls -l | time grep foo"). - */ - rv = timex(t, flags & ~XEXEC); - break; - - case TEXEC: /* an eval'd TCOM */ - s = t->args[0]; - ap = makenv(); -#ifndef F_SETFD - for (i = 0; i < sizeof(clexec_tab); i++) - if (clexec_tab[i]) { - close(i); - clexec_tab[i] = 0; - } -#endif - restoresigs(); - cleanup_proc_env(); - ksh_execve(t->str, t->args, ap, 0); - 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)) { - if (Flag(FERREXIT)) - unwind(LERROR); - trapsig(SIGERR_); - } - return rv; -} - -/* - * execute simple command - */ - -static int -comexec(struct op *t, struct tbl *volatile tp, char **ap, volatile int flags) -{ - 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", - substitute(str_val(global("PS4")), 0)); - shf_fprintf(shl_out, "%s%s", cp, - t->vars[i + 1] ? space : newline); - if (!t->vars[i + 1]) - shf_flush(shl_out); - } - typeset(cp, type_flags, 0, 0, 0); - if (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) && ksh_strchr_dirsep(cp)) { - warningf(true, "%s: restricted", cp); - rv = 1; - goto Leave; - } - tp = findcom(cp, fcflags); - } - - switch (tp->type) { - case CSHELL: /* shell built-in */ - rv = call_builtin(tp, ap); - break; - - case CFUNC: /* function call */ - { - volatile int old_xflag; - volatile Tflag old_inuse; - const char *volatile old_kshname; - - if (!(tp->flag & ISSET)) { - struct tbl *ftp; - - if (!tp->u.fpath) { - if (tp->u2.errno_) { - warningf(true, - "%s: can't find function definition file - %s", - cp, strerror(tp->u2.errno_)); - rv = 126; - } else { - warningf(true, - "%s: can't find function definition file", cp); - rv = 127; - } - break; - } - if (include(tp->u.fpath, 0, 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; - e->loc->argv = ap; - for (i = 0; *ap++ != NULL; i++) - ; - e->loc->argc = i - 1; - /* ksh-style functions handle getopts sanely, - * bourne/posix functions are insane... - */ - if (tp->flag & FKSH) { - e->loc->flags |= BF_DOGETOPTS; - e->loc->getopts_state = user_opt; - getopts_reset(1); - } - - old_xflag = Flag(FXTRACE); - Flag(FXTRACE) = tp->flag & TRACE ? true : false; - - old_inuse = tp->flag & FINUSE; - tp->flag |= FINUSE; - - e->type = E_FUNC; - i = ksh_sigsetjmp(e->jbuf, 0); - if (i == 0) { - /* seems odd to pass XERROK here, but at&t ksh does */ - exstat = execute(tp->val.t, flags & XERROK); - i = LRETURN; - } - kshname = old_kshname; - Flag(FXTRACE) = old_xflag; - tp->flag = (tp->flag & ~FINUSE) | old_inuse; - /* Were we deleted while executing? If so, free the execution - * tree. todo: Unfortunately, the table entry is never re-used - * until the lookup table is expanded. - */ - if ((tp->flag & (FDELETE|FINUSE)) == FDELETE) { - if (tp->flag & ALLOC) { - tp->flag &= ~ALLOC; - tfree(tp->val.t, tp->areap); - } - tp->flag = 0; - } - switch (i) { - case LRETURN: - case LERROR: - rv = exstat; - break; - case LINTR: - case LEXIT: - case LLEAVE: - case LSHELL: - quitenv(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, -1); - break; - } - Leave: - if (flags & XEXEC) { - exstat = rv; - unwind(LLEAVE); - } - return rv; -} - -static void -scriptexec(struct op *tp, char **ap) -{ - char *shell; - - shell = str_val(global(EXECSHELL_STR)); - if (shell && *shell) - shell = search(shell, path, X_OK, NULL); - if (!shell || !*shell) - shell = EXECSHELL; - - *tp->args-- = tp->str; -#ifdef SHARPBANG - { - char buf[LINE]; - char *cp; - int fd, n; - - buf[0] = '\0'; - if ((fd = open(tp->str, O_RDONLY)) >= 0) { - if ((n = read(fd, buf, LINE - 1)) > 0) - buf[n] = '\0'; - (void) close(fd); - } - if ((buf[0] == '#' && buf[1] == '!' && (cp = &buf[2]))) - { - while (*cp && (*cp == ' ' || *cp == '\t')) - cp++; - if (*cp && *cp != '\n') { - char *a0 = cp, *a1 = NULL; - - while (*cp && *cp != '\n' && *cp != ' ' - && *cp != '\t') - cp++; - if (*cp && *cp != '\n') { - *cp++ = '\0'; - while (*cp - && (*cp == ' ' || *cp == '\t')) - cp++; - if (*cp && *cp != '\n') { - a1 = cp; - /* all one argument */ - while (*cp && *cp != '\n') - cp++; - } - } - if (*cp == '\n') { - *cp = '\0'; - if (a1) - *tp->args-- = a1; - shell = a0; - } - } - } - } -#endif /* SHARPBANG */ - *tp->args = shell; - - ksh_execve(tp->args[0], tp->args, ap, 0); - - /* report both the program that was run and the bogus shell */ - errorf("%s: %s: %s", tp->str, shell, strerror(errno)); -} - -int -shcomexec(char **wp) -{ - struct tbl *tp; - - tp = tsearch(&builtins, *wp, hash(*wp)); - if (tp == NULL) - internal_errorf(1, "shcomexec: %s", *wp); - return call_builtin(tp, wp); -} - -/* - * Search function tables for a function. If create set, a table entry - * is created if none is found. - */ -struct tbl * -findfunc(const char *name, unsigned int h, int create) -{ - struct block *l; - struct tbl *tp = NULL; - - for (l = e->loc; l; l = l->next) { - tp = tsearch(&l->funs, name, h); - if (tp) - break; - if (!l->next && create) { - tp = tenter(&l->funs, name, h); - tp->flag = DEFINED; - tp->type = CFUNC; - tp->val.t = 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 */ - tdelete(tp); - return was_set ? 0 : 1; - } - - tp->val.t = tcopy(t->left, tp->areap); - tp->flag |= (ISSET|ALLOC); - if (t->u.ksh_func) - tp->flag |= FKSH; - - return 0; -} - -/* - * add builtin - */ -void -builtin(const char *name, int (*func) (char **)) -{ - struct tbl *tp; - Tflag flag; - - /* see if any flags should be set for this builtin */ - for (flag = 0; ; name++) { - if (*name == '=') /* command does variable assignment */ - flag |= KEEPASN; - else if (*name == '*') /* POSIX special builtin */ - flag |= SPEC_BI; - else if (*name == '+') /* POSIX regular builtin */ - flag |= REG_BI; - else - break; - } - - tp = tenter(&builtins, name, hash(name)); - tp->flag = DEFINED | flag; - tp->type = CSHELL; - tp->val.f = func; -} - -/* - * find command - * either function, hashed command, or built-in (in that order) - */ -struct tbl * -findcom(const char *name, int flags) - - /* FC_* */ -{ - static struct tbl temp; - unsigned int h = hash(name); - struct tbl *tp = NULL, *tbi; - int insert = Flag(FTRACKALL); /* insert if not found */ - char *fpath; /* for function autoloading */ - char *npath; - - if (ksh_strchr_dirsep(name) != NULL) { - insert = 0; - /* prevent FPATH search below */ - flags &= ~FC_FUNC; - goto Search; - } - tbi = (flags & FC_BI) ? tsearch(&builtins, name, h) : NULL; - /* POSIX says special builtins first, then functions, then - * POSIX regular builtins, then search path... - */ - if ((flags & FC_SPECBI) && tbi && (tbi->flag & SPEC_BI)) - tp = tbi; - if (!tp && (flags & FC_FUNC)) { - tp = findfunc(name, h, false); - if (tp && !(tp->flag & ISSET)) { - if ((fpath = str_val(global("FPATH"))) == null) { - tp->u.fpath = 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 = tsearch(&taliases, name, h); - if (tp && (tp->flag & ISSET) && eaccess(tp->val.s, X_OK) != 0) { - if (tp->flag & ALLOC) { - tp->flag &= ~ALLOC; - afree(tp->val.s, APERM); - } - tp->flag &= ~ISSET; - } - } - - Search: - if ((!tp || (tp->type == CTALIAS && !(tp->flag&ISSET))) - && (flags & FC_PATH)) - { - if (!tp) { - if (insert && !(flags & FC_DEFPATH)) { - tp = tenter(&taliases, name, h); - tp->type = CTALIAS; - } else { - tp = &temp; - tp->type = CEXEC; - } - tp->flag = DEFINED; /* make ~ISSET */ - } - npath = search(name, flags & FC_DEFPATH ? def_path : path, - X_OK, &tp->u2.errno_); - if (npath) { - tp->val.s = tp == &temp ? npath : str_save(npath, APERM); - tp->flag |= ISSET|ALLOC; - } else if ((flags & FC_FUNC) - && (fpath = str_val(global("FPATH"))) != null - && (npath = search(name, fpath, R_OK, - &tp->u2.errno_)) != 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 (twalk(&ts, &taliases); (tp = tnext(&ts)) != NULL; ) - if ((tp->flag&ISSET) && (all || !ISDIRSEP(tp->val.s[0]))) { - if (tp->flag&ALLOC) { - tp->flag &= ~(ALLOC|ISSET); - afree(tp->val.s, APERM); - } - tp->flag &= ~ISSET; - } -} - -/* Check if path is something we want to find. Returns -1 for failure. */ -int -search_access(const char *path, int mode, int *errnop) - - - /* set if candidate found, but not suitable */ -{ - int ret, err = 0; - struct stat statb; - - if (stat(path, &statb) < 0) - return -1; - ret = eaccess(path, mode); - if (ret < 0) - err = errno; /* File exists, but we can't access it */ - else if (mode == X_OK - && (!S_ISREG(statb.st_mode) - /* This 'cause access() says root can execute everything */ - || !(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) - { - ret = -1; - err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES; - } - if (err && errnop && !*errnop) - *errnop = err; - return ret; -} - -/* - * search for command with PATH - */ -char * -search(const char *name, const char *path, int mode, int *errnop) - - - /* R_OK or X_OK */ - /* set if candidate found, but not suitable */ -{ - const char *sp, *p; - char *xp; - XString xs; - int namelen; - - if (errnop) - *errnop = 0; - if (ksh_strchr_dirsep(name)) { - if (search_access(name, mode, errnop) == 0) - return (char *) name; - return NULL; - } - - namelen = strlen(name) + 1; - Xinit(xs, xp, 128, ATEMP); - - sp = path; - while (sp != NULL) { - xp = Xstring(xs, xp); - if (!(p = strchr(sp, PATHSEP))) - p = sp + strlen(sp); - if (p != sp) { - XcheckN(xs, xp, p - sp); - memcpy(xp, sp, p - sp); - xp += p - sp; - *xp++ = DIRSEP; - } - sp = p; - XcheckN(xs, xp, namelen); - memcpy(xp, name, namelen); - if (search_access(Xstring(xs, xp), mode, errnop) == 0) - 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, UNINITIALIZED(flags); - struct ioword iotmp; - struct stat statb; - - if (iotype != IOHERE) - cp = evalonestr(cp, DOTILDE|(Flag(FTALKING_I) ? DOGLOB : 0)); - - /* Used for tracing and error messages to print expanded cp */ - iotmp = *iop; - iotmp.name = (iotype == IOHERE) ? NULL : cp; - iotmp.flag |= IONAMEXP; - - if (Flag(FXTRACE)) - shellf("%s%s\n", - substitute(str_val(global("PS4")), 0), - 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 (e->savefd[iop->unit] == 0) { - /* If these are the same, it means unit was previously closed */ - if (u == iop->unit) - e->savefd[iop->unit] = -1; - else - /* c_exec() assumes e->savefd[fd] set for any - * redirections. Ask savefd() not to close iop->unit; - * this allows error messages to be seen if iop->unit - * is 2; also means we can't lose the fd (eg, both - * dup2 below and dup2 in restfd() failing). - */ - e->savefd[iop->unit] = savefd(iop->unit, 1); - } - - if (do_close) - close(iop->unit); - else if (u != iop->unit) { - if (ksh_dup2(u, iop->unit, true) < 0) { - warningf(true, - "could not finish (dup) redirection %s: %s", - snptreef(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, &e->temps); - if (!(shf = h->shf) || (fd = open(h->name, O_RDONLY, 0)) < 0) { - warningf(true, "can't %s temporary file %s: %s", - !shf ? "create" : "open", - h->name, strerror(errno)); - if (shf) - shf_close(shf); - return -2 /* special to iosetup(): don't print error */; - } - - osource = source; - newenv(E_ERRH); - i = ksh_sigsetjmp(e->jbuf, 0); - if (i) { - source = osource; - quitenv(shf); - close(fd); - return -2; /* special to iosetup(): don't print error */ - } - if (sub) { - /* Do substitutions on the content of heredoc */ - s = pushs(SSTRING, ATEMP); - s->start = s->str = content; - source = s; - if (yylex(ONEWORD) != LWORD) - internal_errorf(1, "herein: yylex"); - source = osource; - shf_puts(evalstr(yylval.cp, 0), shf); - } else - shf_puts(content, shf); - - quitenv(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; -} - -/* - * 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}; - 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 = atoi(s); - return (i >= 1 && i <= argct) ? ap[i - 1] : null; - } - print_menu = 1; - } -} - -struct select_menu_info { - char *const *args; - int arg_width; - int num_width; -} info; - -static char *select_fmt_entry(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; -} - -/* - * [[ ... ]] 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/expand.h b/expand.h deleted file mode 100644 index f10f618..0000000 --- a/expand.h +++ /dev/null @@ -1,113 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: expand.h,v 1.3 2001/03/26 16:19:45 todd Exp $ */ - -#ifndef EXPAND_H -#define EXPAND_H - -/* - * Expanding strings - */ - -#define X_EXTRA 8 /* this many extra bytes in X string */ - -#if 0 /* Usage */ - XString xs; - char *xp; - - Xinit(xs, xp, 128, ATEMP); /* allocate initial string */ - while ((c = generate()) { - Xcheck(xs, xp); /* expand string if necessary */ - Xput(xs, xp, c); /* add character */ - } - return Xclose(xs, xp); /* resize string */ -/* - * NOTE: - * The Xcheck and Xinit macros have a magic + X_EXTRA in the lengths. - * This is so that you can put up to X_EXTRA characters in a XString - * before calling Xcheck. (See yylex in lex.c) - */ -#endif /* 0 */ - -typedef struct XString { - char *end, *beg; /* end, begin of string */ - size_t len; /* length */ - Area *areap; /* area to allocate/free from */ -} XString; - -typedef char * XStringP; - -/* initialize expandable string */ -#define Xinit(xs, xp, length, area) do { \ - (xs).len = length; \ - (xs).areap = (area); \ - (xs).beg = alloc((xs).len + X_EXTRA, (xs).areap); \ - (xs).end = (xs).beg + (xs).len; \ - xp = (xs).beg; \ - } while (0) - -/* stuff char into string */ -#define Xput(xs, xp, c) (*xp++ = (c)) - -/* check if there are at least n bytes left */ -#define XcheckN(xs, xp, n) do { \ - int more = ((xp) + (n)) - (xs).end; \ - if (more > 0) \ - xp = Xcheck_grow_(&xs, xp, more); \ - } while (0) - -/* check for overflow, expand string */ -#define Xcheck(xs, xp) XcheckN(xs, xp, 1) - -/* free string */ -#define Xfree(xs, xp) afree((void*) (xs).beg, (xs).areap) - -/* close, return string */ -#define Xclose(xs, xp) (char*) aresize((void*)(xs).beg, \ - (size_t)((xp) - (xs).beg), (xs).areap) -/* begin of string */ -#define Xstring(xs, xp) ((xs).beg) - -#define Xnleft(xs, xp) ((xs).end - (xp)) /* may be less than 0 */ -#define Xlength(xs, xp) ((xp) - (xs).beg) -#define Xsize(xs, xp) ((xs).end - (xs).beg) -#define Xsavepos(xs, xp) ((xp) - (xs).beg) -#define Xrestpos(xs, xp, n) ((xs).beg + (n)) - -char * Xcheck_grow_(XString *xsp, char *xp, size_t more); - -/* - * expandable vector of generic pointers - */ - -typedef struct XPtrV { - void **cur; /* next avail pointer */ - void **beg, **end; /* begin, end of vector */ -} XPtrV; - -#define XPinit(x, n) do { \ - void **vp__; \ - vp__ = (void**) alloc(sizeofN(void*, n), ATEMP); \ - (x).cur = (x).beg = vp__; \ - (x).end = vp__ + n; \ - } while (0) - -#define XPput(x, p) do { \ - if ((x).cur >= (x).end) { \ - int n = XPsize(x); \ - (x).beg = (void**) aresize((void*) (x).beg, \ - sizeofN(void*, n*2), ATEMP); \ - (x).cur = (x).beg + n; \ - (x).end = (x).cur + n; \ - } \ - *(x).cur++ = (p); \ - } while (0) - -#define XPptrv(x) ((x).beg) -#define XPsize(x) ((x).cur - (x).beg) - -#define XPclose(x) (void**) aresize((void*)(x).beg, \ - sizeofN(void*, XPsize(x)), ATEMP) - -#define XPfree(x) afree((void*) (x).beg, ATEMP) - -#endif /* ndef EXPAND_H */ diff --git a/expr.c b/expr.c deleted file mode 100644 index 2b37c1d..0000000 --- a/expr.c +++ /dev/null @@ -1,580 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: expr.c,v 1.16 2004/12/22 18:57:28 otto Exp $ */ - -/* - * Korn expression evaluation - */ - -#include "sh.h" -#include - -__RCSID("$MirOS$"); - -/* The order of these enums is constrained by the order of opinfo[] */ -enum token { - /* some (long) unary operators */ - O_PLUSPLUS = 0, O_MINUSMINUS, - /* binary operators */ - O_EQ, O_NE, - /* assignments are assumed to be in range O_ASN .. O_BORASN */ - O_ASN, O_TIMESASN, O_DIVASN, O_MODASN, O_PLUSASN, O_MINUSASN, - O_LSHIFTASN, O_RSHIFTASN, O_BANDASN, O_BXORASN, O_BORASN, - O_LSHIFT, O_RSHIFT, - O_LE, O_GE, O_LT, O_GT, - O_LAND, - O_LOR, - O_TIMES, O_DIV, O_MOD, - O_PLUS, O_MINUS, - O_BAND, - O_BXOR, - O_BOR, - O_TERN, - O_COMMA, - /* things after this aren't used as binary operators */ - /* unary that are not also binaries */ - O_BNOT, O_LNOT, - /* misc */ - OPEN_PAREN, CLOSE_PAREN, CTERN, - /* things that don't appear in the opinfo[] table */ - VAR, LIT, END, BAD - }; -#define IS_BINOP(op) (((int)op) >= (int)O_EQ && ((int)op) <= (int)O_COMMA) -#define IS_ASSIGNOP(op) ((int)(op) >= (int)O_ASN && (int)(op) <= (int)O_BORASN) - -enum prec { - P_PRIMARY = 0, /* VAR, LIT, (), ~ ! - + */ - P_MULT, /* * / % */ - P_ADD, /* + - */ - P_SHIFT, /* << >> */ - P_RELATION, /* < <= > >= */ - P_EQUALITY, /* == != */ - P_BAND, /* & */ - P_BXOR, /* ^ */ - P_BOR, /* | */ - P_LAND, /* && */ - P_LOR, /* || */ - P_TERN, /* ?: */ - P_ASSIGN, /* = *= /= %= += -= <<= >>= &= ^= |= */ - P_COMMA /* , */ - }; -#define MAX_PREC P_COMMA - -struct opinfo { - char name[4]; - int len; /* name length */ - enum prec prec; /* precedence: lower is higher */ -}; - -/* Tokens in this table must be ordered so the longest are first - * (eg, += before +). If you change something, change the order - * of enum token too. - */ -static const struct opinfo opinfo[] = { - { "++", 2, P_PRIMARY }, /* before + */ - { "--", 2, P_PRIMARY }, /* before - */ - { "==", 2, P_EQUALITY }, /* before = */ - { "!=", 2, P_EQUALITY }, /* before ! */ - { "=", 1, P_ASSIGN }, /* keep assigns in a block */ - { "*=", 2, P_ASSIGN }, - { "/=", 2, P_ASSIGN }, - { "%=", 2, P_ASSIGN }, - { "+=", 2, P_ASSIGN }, - { "-=", 2, P_ASSIGN }, - { "<<=", 3, P_ASSIGN }, - { ">>=", 3, P_ASSIGN }, - { "&=", 2, P_ASSIGN }, - { "^=", 2, P_ASSIGN }, - { "|=", 2, P_ASSIGN }, - { "<<", 2, P_SHIFT }, - { ">>", 2, P_SHIFT }, - { "<=", 2, P_RELATION }, - { ">=", 2, P_RELATION }, - { "<", 1, P_RELATION }, - { ">", 1, P_RELATION }, - { "&&", 2, P_LAND }, - { "||", 2, P_LOR }, - { "*", 1, P_MULT }, - { "/", 1, P_MULT }, - { "%", 1, P_MULT }, - { "+", 1, P_ADD }, - { "-", 1, P_ADD }, - { "&", 1, P_BAND }, - { "^", 1, P_BXOR }, - { "|", 1, P_BOR }, - { "?", 1, P_TERN }, - { ",", 1, P_COMMA }, - { "~", 1, P_PRIMARY }, - { "!", 1, P_PRIMARY }, - { "(", 1, P_PRIMARY }, - { ")", 1, P_PRIMARY }, - { ":", 1, P_PRIMARY }, - { "", 0, P_PRIMARY } /* end of table */ - }; - - -typedef struct expr_state Expr_state; -struct expr_state { - const char *expression; /* expression being evaluated */ - const char *tokp; /* lexical position */ - enum token tok; /* token from token() */ - int noassign; /* don't do assigns (for ?:,&&,||) */ - struct tbl *val; /* value from token() */ - struct tbl *evaling; /* variable that is being recursively - * expanded (EXPRINEVAL flag set) - */ -}; - -enum error_type { ET_UNEXPECTED, ET_BADLIT, ET_RECURSIVE, - ET_LVALUE, ET_RDONLY, ET_STR }; - -static void evalerr(Expr_state *es, enum error_type type, - const char *str) GCC_FUNC_ATTR(noreturn); -static struct tbl *evalexpr(Expr_state *es, enum prec prec); -static void token(Expr_state *es); -static struct tbl *do_ppmm(Expr_state *es, enum token op, - struct tbl *vasn, bool is_prefix); -static void assign_check(Expr_state *es, enum token op, - struct tbl *vasn); -static struct tbl *tempvar(void); -static struct tbl *intvar(Expr_state *es, struct tbl *vp); - -/* - * parse and evaluate expression - */ -int -evaluate(const char *expr, long int *rval, int error_ok) -{ - struct tbl v; - int ret; - - v.flag = DEFINED|INTEGER; - v.type = 0; - ret = v_evaluate(&v, expr, error_ok); - *rval = v.val.i; - return ret; -} - -/* - * parse and evaluate expression, storing result in vp. - */ -int -v_evaluate(struct tbl *vp, const char *expr, volatile int error_ok) -{ - struct tbl *v; - Expr_state curstate; - Expr_state * const es = &curstate; - int i; - - /* save state to allow recursive calls */ - curstate.expression = curstate.tokp = expr; - curstate.noassign = 0; - curstate.evaling = NULL; - - newenv(E_ERRH); - i = ksh_sigsetjmp(e->jbuf, 0); - if (i) { - /* Clear EXPRINEVAL in of any variables we were playing with */ - if (curstate.evaling) - curstate.evaling->flag &= ~EXPRINEVAL; - quitenv(NULL); - if (i == LAEXPR) { - if (error_ok == KSH_RETURN_ERROR) - return 0; - errorf(null); - } - unwind(i); - /*NOTREACHED*/ - } - - token(es); - if (es->tok == END) { - es->tok = LIT; - es->val = tempvar(); - } - v = intvar(es, evalexpr(es, MAX_PREC)); - - if (es->tok != END) - evalerr(es, ET_UNEXPECTED, NULL); - - if (vp->flag & INTEGER) - setint_v(vp, v); - 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; - - 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, UNINITIALIZED(*vr), *vasn; - enum token op; - long UNINITIALIZED(res); - - if (prec == P_PRIMARY) { - op = es->tok; - if (op == O_BNOT || op == O_LNOT || op == O_MINUS - || op == O_PLUS) - { - token(es); - vl = intvar(es, evalexpr(es, P_PRIMARY)); - if (op == O_BNOT) - vl->val.i = ~vl->val.i; - else if (op == O_LNOT) - vl->val.i = !vl->val.i; - else if (op == O_MINUS) - vl->val.i = -vl->val.i; - /* op == O_PLUS is a no-op */ - } else if (op == OPEN_PAREN) { - token(es); - vl = evalexpr(es, MAX_PREC); - if (es->tok != CLOSE_PAREN) - evalerr(es, ET_STR, "missing )"); - token(es); - } else if (op == O_PLUSPLUS || op == O_MINUSMINUS) { - token(es); - vl = do_ppmm(es, op, es->val, true); - token(es); - } else if (op == VAR || op == LIT) { - vl = es->val; - token(es); - } else { - evalerr(es, ET_UNEXPECTED, 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: - res = vl->val.i / vr->val.i; - break; - case O_MOD: - case O_MODASN: - res = vl->val.i % vr->val.i; - break; - case O_PLUS: - case O_PLUSASN: - res = vl->val.i + vr->val.i; - break; - case O_MINUS: - case O_MINUSASN: - res = vl->val.i - vr->val.i; - break; - case O_LSHIFT: - case O_LSHIFTASN: - res = vl->val.i << vr->val.i; - break; - case O_RSHIFT: - case O_RSHIFTASN: - res = vl->val.i >> vr->val.i; - break; - case O_LT: - res = vl->val.i < vr->val.i; - break; - case O_LE: - res = vl->val.i <= vr->val.i; - break; - case O_GT: - res = vl->val.i > vr->val.i; - break; - case O_GE: - res = vl->val.i >= vr->val.i; - break; - case O_EQ: - res = vl->val.i == vr->val.i; - break; - case O_NE: - res = vl->val.i != vr->val.i; - break; - case O_BAND: - case O_BANDASN: - res = vl->val.i & vr->val.i; - break; - case O_BXOR: - case O_BXORASN: - res = vl->val.i ^ vr->val.i; - break; - case O_BOR: - case O_BORASN: - res = vl->val.i | vr->val.i; - break; - case O_LAND: - if (!vl->val.i) - es->noassign++; - vr = intvar(es, evalexpr(es, ((int) prec) - 1)); - res = vl->val.i && vr->val.i; - if (!vl->val.i) - es->noassign--; - break; - case O_LOR: - if (vl->val.i) - es->noassign++; - vr = intvar(es, evalexpr(es, ((int) prec) - 1)); - res = vl->val.i || vr->val.i; - if (vl->val.i) - es->noassign--; - break; - case O_TERN: - { - int e = vl->val.i != 0; - if (!e) - es->noassign++; - vl = evalexpr(es, MAX_PREC); - if (!e) - es->noassign--; - if (es->tok != CTERN) - evalerr(es, ET_STR, "missing :"); - token(es); - if (e) - es->noassign++; - vr = evalexpr(es, P_TERN); - if (e) - es->noassign--; - vl = e ? vl : vr; - } - break; - case O_ASN: - res = vr->val.i; - break; - case O_COMMA: - res = vr->val.i; - break; - } - if (IS_ASSIGNOP(op)) { - vr->val.i = res; - if (vasn->flag & INTEGER) - setint_v(vasn, vr); - else - setint(vasn, res); - vl = vr; - } else if (op != O_TERN) - vl->val.i = res; - } - return vl; -} - -static void -token(Expr_state *es) -{ - const char *cp; - int c; - char *tvar; - - /* skip white space */ - for (cp = es->tokp; (c = *cp), isspace(c); cp++) - ; - es->tokp = cp; - - if (c == '\0') - es->tok = END; - else if (letter(c)) { - for (; letnum(c); c = *cp) - cp++; - if (c == '[') { - int len; - - len = array_ref_len(cp); - if (len == 0) - evalerr(es, ET_STR, "missing ]"); - cp += len; - } 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) == 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); - 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 (vasn->name[0] == '\0' && !(vasn->flag & EXPRLVALUE)) - evalerr(es, ET_LVALUE, opinfo[(int) op].name); - else if (vasn->flag & RDONLY) - evalerr(es, ET_RDONLY, opinfo[(int) op].name); -} - -static struct tbl * -tempvar(void) -{ - struct tbl *vp; - - vp = (struct tbl*) alloc(sizeof(struct tbl), ATEMP); - vp->flag = ISSET|INTEGER; - vp->type = 0; - vp->areap = ATEMP; - vp->val.i = 0; - vp->name[0] = '\0'; - return vp; -} - -/* cast (string) variable to temporary integer variable */ -static struct tbl * -intvar(Expr_state *es, struct tbl *vp) -{ - struct tbl *vq; - - /* try to avoid replacing a temp var with another temp var */ - if (vp->name[0] == '\0' - && (vp->flag & (ISSET|INTEGER|EXPRLVALUE)) == (ISSET|INTEGER)) - return vp; - - vq = tempvar(); - if (setint_v(vq, vp) == NULL) { - if (vp->flag & EXPRINEVAL) - evalerr(es, ET_RECURSIVE, vp->name); - es->evaling = vp; - vp->flag |= EXPRINEVAL; - v_evaluate(vq, str_val(vp), KSH_UNWIND_ERROR); - vp->flag &= ~EXPRINEVAL; - es->evaling = NULL; - } - return vq; -} diff --git a/history.c b/history.c deleted file mode 100644 index f653f8b..0000000 --- a/history.c +++ /dev/null @@ -1,1113 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: history.c,v 1.24 2004/08/03 12:44:59 danh Exp $ */ - -/* - * command history - * - * only implements in-memory history. - */ - -/* - * This file contains - * a) the original in-memory history mechanism - * b) a simple file saving history mechanism done by sjg@zen - * define EASY_HISTORY to get this - * c) a more complicated mechanism done by pc@hillside.co.uk - * that more closely follows the real ksh way of doing - * things. You need to have the mmap system call for this - * to work on your system - */ - -#include "sh.h" -#include "ksh_stat.h" - -__RCSID("$MirOS$"); - -#ifndef EASY_HISTORY -/* Defines and includes for the complicated case */ - -# include -# include - -/* - * variables for handling the data file - */ -static int histfd; -static int hsize; - -static int hist_count_lines(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); - -# ifdef MAP_FILE -# define MAP_FLAGS (MAP_FILE|MAP_PRIVATE) -# else -# define MAP_FLAGS MAP_PRIVATE -# endif - -#endif /* of EASY_HISTORY */ - -static int hist_execute(char *cmd); -static int hist_replace(char **hp, const char *pat, const char *rep, - int global); -static char **hist_get(const char *str, int approx, int allow_cur); -static char **hist_get_newest(int allow_cur); -static char **hist_get_oldest(void); -static void histbackup(void); - -static char **current; /* current position in history[] */ -static int curpos; /* current index in history[] */ -static char *hname; /* current name of history file */ -static int hstarted; /* set after hist_init() called */ -static Source *hist_source; - - -int -c_fc(char **wp) -{ - struct shf *shf; - struct temp UNINITIALIZED(*tf); - 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,")) != EOF) - 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, &e->temps); - if (!(shf = tf->shf)) { - bi_errorf("cannot create temp file %s - %s", - tf->name, strerror(errno)); - return 1; - } - for (hp = rflag ? hlast : hfirst; - hp >= hfirst && hp <= hlast; hp += rflag ? -1 : 1) - shf_fprintf(shf, "%s\n", *hp); - if (shf_close(shf) == EOF) { - bi_errorf("error writing temporary file - %s", strerror(errno)); - return 1; - } - - /* Ignore setstr errors here (arbitrary) */ - setstr(local("_", false), tf->name, KSH_RETURN_ERROR); - - /* XXX: source should not get trashed by this.. */ - { - Source *sold = source; - int ret; - - ret = command(editor ? editor : "${FCEDIT:-/bin/ed} $_"); - source = sold; - if (ret) - return ret; - } - - { - struct stat statb; - XString xs; - char *xp; - int n; - - if (!(shf = shf_open(tf->name, O_RDONLY, 0, 0))) { - bi_errorf("cannot open temp file %s", tf->name); - return 1; - } - - n = fstat(shf_fileno(shf), &statb) < 0 ? 128 - : statb.st_size + 1; - Xinit(xs, xp, n, hist_source->areap); - while ((n = shf_read(xp, Xnleft(xs, xp), shf)) > 0) { - xp += n; - if (Xnleft(xs, xp) <= 0) - XcheckN(xs, xp, Xlength(xs, xp)); - } - if (n < 0) { - bi_errorf("error reading temp file %s - %s", - tf->name, strerror(shf_errno(shf))); - shf_close(shf); - return 1; - } - shf_close(shf); - *xp = '\0'; - strip_nuls(Xstring(xs, xp), Xlength(xs, xp)); - return hist_execute(Xstring(xs, xp)); - } -} - -/* Save cmd in history, execute cmd (cmd gets trashed) */ -static int -hist_execute(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; - } -#ifdef EASY_HISTORY - if (p != cmd) - histappend(p, true); - else -#endif /* EASY_HISTORY */ - histsave(++(hist_source->line), p, 1); - - shellf("%s\n", p); /* POSIX doesn't say this is done... */ - if ((p = q)) /* restore \n (trailing \n not restored) */ - q[-1] = '\n'; - } - - /* Commands are executed here instead of pushing them onto the - * input 'cause posix says the redirection and variable assignments - * in - * X=y fc -e - 42 2> /dev/null - * are to effect the repeated commands environment. - */ - /* XXX: source should not get trashed by this.. */ - sold = source; - ret = command(cmd); - source = sold; - return ret; -} - -static int -hist_replace(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 (hp < 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 */ -static 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 newest 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((void*)*histptr, APERM); - histptr--; - last_line = hist_source->line; - } -} - -/* - * Return the current position. - */ -char ** -histpos(void) -{ - return current; -} - -int -histN(void) -{ - return curpos; -} - -int -histnum(int n) -{ - int last = histptr - history; - - if (n < 0 || n >= last) { - current = histptr; - curpos = last; - return last; - } else { - current = &history[n]; - curpos = n; - return n; - } -} - -/* - * This will become unnecessary if hist_get is modified to allow - * searching from positions other than the end, and in either - * direction. - */ -int -findhist(int start, int fwd, const char *str, int anchored) -{ - char **hp; - int maxhist = histptr - history; - int incr = fwd ? 1 : -1; - int len = strlen(str); - - if (start < 0 || start >= maxhist) - start = maxhist; - - hp = &history[start]; - for (; hp >= history && hp <= histptr; hp += incr) - if ((anchored && strncmp(*hp, str, len) == 0) - || (!anchored && strstr(*hp, str))) - return hp - history; - - return -1; -} - -/* - * set history - * this means reallocating the dataspace - */ -void -sethistsize(int n) -{ - if (n > 0 && n != histsize) { - int cursize = histptr - history; - - /* save most recent history */ - if (n < cursize) { - memmove(history, histptr - n, n * sizeof(char *)); - cursize = n; - } - - history = (char **)aresize(history, n*sizeof(char *), APERM); - - histsize = n; - histptr = history + cursize; - } -} - -/* - * set history file - * This can mean reloading/resetting/starting history file - * maintenance - */ -void -sethistfile(const char *name) -{ - /* if not started then nothing to do */ - if (hstarted == 0) - return; - - /* if the name is the same as the name we have */ - if (hname && strcmp(hname, name) == 0) - return; - - /* - * its a new name - possibly - */ -#ifdef EASY_HISTORY - if (hname) { - afree(hname, APERM); - hname = NULL; - } -#else - if (histfd) { - /* yes the file is open */ - (void) close(histfd); - histfd = 0; - hsize = 0; - afree(hname, APERM); - hname = NULL; - /* let's reset the history */ - histptr = history - 1; - hist_source->line = 0; - } -#endif - - hist_init(hist_source); -} - -/* - * initialise the history vector - */ -void -init_histvec(void) -{ - if (history == (char **)NULL) { - histsize = HISTORYSIZE; - history = (char **)alloc(histsize*sizeof (char *), APERM); - histptr = history - 1; - } -} - -#ifdef EASY_HISTORY -/* - * save command in history - */ -void -histsave(int lno, const char *cmd, int dowrite) - /* ignored (compatibility with COMPLEX_HISTORY) */ - - /* ignored (compatibility with COMPLEX_HISTORY) */ -{ - char **hp = histptr; - char *cp; - - if (++hp >= history + histsize) { /* remove oldest command */ - afree((void*)history[0], APERM); - memmove(history, history + 1, - sizeof(history[0]) * (histsize - 1)); - hp = &history[histsize - 1]; - } - *hp = str_save(cmd, APERM); - /* trash trailing newline but allow imbedded newlines */ - cp = *hp + strlen(*hp); - if (cp > *hp && cp[-1] == '\n') - cp[-1] = '\0'; - histptr = hp; -} - -/* - * Append an entry to the last saved command. Used for multiline - * commands - */ -void -histappend(const char *cmd, int nl_separate) -{ - int hlen, clen; - char *p; - - hlen = strlen(*histptr); - clen = strlen(cmd); - if (clen > 0 && cmd[clen-1] == '\n') - clen--; - p = *histptr = (char *) aresize(*histptr, hlen + clen + 2, APERM); - p += hlen; - if (nl_separate) - *p++ = '\n'; - memcpy(p, cmd, clen); - p[clen] = '\0'; -} - -/* - * 92-04-25 - * A simple history file implementation. - * At present we only save the history when we exit. - * This can cause problems when there are multiple shells are - * running under the same user-id. The last shell to exit gets - * to save its history. - */ -void -hist_init(Source *s) -{ - char *f; - FILE *fh; - - if (Flag(FTALKING) == 0) - return; - - hstarted = 1; - - hist_source = s; - - if ((f = str_val(global("HISTFILE"))) == NULL || *f == '\0') { - hname = NULL; - return; - } else - hname = str_save(f, APERM); - - if ((fh = fopen(hname, "r"))) { - int pos = 0, nread = 0; - int contin = 0; /* continuation of previous command */ - char *end; - char hline[LINE + 1]; - - while (1) { - if (pos >= nread) { - pos = 0; - nread = fread(hline, 1, LINE, fh); - if (nread <= 0) - break; - hline[nread] = '\0'; - } - end = strchr(hline + pos, 0); /* will always succeed */ - if (contin) - histappend(hline + pos, 0); - else { - hist_source->line++; - histsave(0, hline + pos, 0); - } - pos = end - hline + 1; - contin = end == &hline[nread]; - } - fclose(fh); - } -} - -/* - * save our history. - * We check that we do not have more than we are allowed. - * If the history file is read-only we do nothing. - * Handy for having all shells start with a useful history set. - */ - -void -hist_finish(void) -{ - static int once; - FILE *fh; - int i; - char **hp; - - if (once++) - return; - /* check how many we have */ - i = histptr - history; - if (i >= histsize) - hp = &histptr[-histsize]; - else - hp = history; - if (hname && (fh = fopen(hname, "w"))) - { - for (i = 0; hp + i <= histptr && hp[i]; i++) - fprintf(fh, "%s%c", hp[i], '\0'); - fclose(fh); - } -} - -#else /* EASY_HISTORY */ - -/* - * Routines added by Peter Collinson BSDI(Europe)/Hillside Systems to - * a) permit HISTSIZE to control number of lines of history stored - * b) maintain a physical history file - * - * It turns out that there is a lot of ghastly hackery here - */ - - -/* - * save command in history - */ -void -histsave(int lno, const char *cmd, int dowrite) -{ - char **hp; - char *c, *cp; - - c = str_save(cmd, APERM); - if ((cp = strchr(c, '\n')) != NULL) - *cp = '\0'; - - if (histfd && dowrite) - writehistfile(lno, c); - - hp = histptr; - - if (++hp >= history + histsize) { /* remove oldest command */ - afree((void*)*history, APERM); - for (hp = history; hp < history + histsize - 1; hp++) - hp[0] = hp[1]; - } - *hp = c; - histptr = hp; -} - -/* - * Write history data to a file nominated by HISTFILE - * if HISTFILE is unset then history still happens, but - * the data is not written to a file - * All copies of ksh looking at the file will maintain the - * same history. This is ksh behaviour. - * - * This stuff uses mmap() - * if your system ain't got it - then you'll have to undef HISTORYFILE - */ - -/* - * Open a history file - * Format is: - * Bytes 1, 2: HMAGIC - just to check that we are dealing with - * the correct object - * Then follows a number of stored commands - * Each command is - * - */ -#define HMAGIC1 0xab -#define HMAGIC2 0xcd -#define COMMAND 0xff - -void -hist_init(Source *s) -{ - unsigned char *base; - int lines; - int fd; - - if (Flag(FTALKING) == 0) - return; - - hstarted = 1; - - hist_source = s; - - hname = str_val(global("HISTFILE")); - if (hname == NULL) - return; - hname = str_save(hname, APERM); - - retry: - /* we have a file and are interactive */ - if ((fd = open(hname, O_RDWR|O_CREAT|O_APPEND, 0600)) < 0) - return; - - histfd = savefd(fd, 0); - - (void) flock(histfd, LOCK_EX); - - hsize = lseek(histfd, 0L, SEEK_END); - - if (hsize == 0) { - /* add magic */ - if (sprinkle(histfd)) { - hist_finish(); - return; - } - } - else if (hsize > 0) { - /* - * we have some data - */ - base = (unsigned char *)mmap(0, hsize, PROT_READ, MAP_FLAGS, histfd, 0); - /* - * check on its validity - */ - if (base == MAP_FAILED || *base != HMAGIC1 || base[1] != HMAGIC2) { - if (base != MAP_FAILED) - munmap((caddr_t)base, hsize); - hist_finish(); - unlink(hname); - goto retry; - } - if (hsize > 2) { - lines = hist_count_lines(base+2, hsize-2); - if (lines > histsize) { - /* we need to make the file smaller */ - if (hist_shrink(base, hsize)) - unlink(hname); - munmap((caddr_t)base, hsize); - hist_finish(); - goto retry; - } - } - histload(hist_source, base+2, hsize-2); - munmap((caddr_t)base, hsize); - } - (void) flock(histfd, LOCK_UN); - hsize = lseek(histfd, 0L, SEEK_END); -} - -typedef enum state { - shdr, /* expecting a header */ - sline, /* looking for a null byte to end the line */ - sn1, /* bytes 1 to 4 of a line no */ - sn2, sn3, sn4 -} State; - -static int -hist_count_lines(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[PATH_MAX]; - struct stat statb; - unsigned char *nbase = oldbase; - int nbytes = oldbytes; - - nbase = hist_skip_back(nbase, &nbytes, histsize); - if (nbase == NULL) - return 1; - if (nbase == oldbase) - return 0; - - /* - * create temp file - */ - (void) shf_snprintf(nfile, sizeof(nfile), "%s.%d", hname, procpid); - if ((fd = creat(nfile, 0600)) < 0) - return 1; - - if (sprinkle(fd)) { - close(fd); - unlink(nfile); - return 1; - } - if (write(fd, nbase, nbytes) != nbytes) { - close(fd); - unlink(nfile); - return 1; - } - /* - * worry about who owns this file - */ - if (fstat(histfd, &statb) >= 0) - fchown(fd, statb.st_uid, statb.st_gid); - close(fd); - - /* - * rename - */ - if (rename(nfile, hname) < 0) - return 1; - return 0; -} - - -/* - * find a pointer to the data 'no' back from the end of the file - * return the pointer and the number of bytes left - */ -static unsigned char * -hist_skip_back(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; - histsave(lno, (char *)line, 0); - } - state = shdr; - } - } - } -} - -/* - * Insert a line into the history at a specified number - */ -static void -histinsert(Source *s, int lno, unsigned char *line) -{ - char **hp; - - if (lno >= s->line-(histptr-history) && lno <= s->line) { - hp = &histptr[lno-s->line]; - if (*hp) - afree((void*)*hp, APERM); - *hp = str_save((char *)line, APERM); - } -} - -/* - * write a command to the end of the history file - * This *MAY* seem easy but it's also necessary to check - * that the history file has not changed in size. - * If it has - then some other shell has written to it - * and we should read those commands to update our history - */ -static void -writehistfile(int lno, char *cmd) -{ - int sizenow; - unsigned char *base; - unsigned char *new; - int bytes; - unsigned char hdr[5]; - - (void) flock(histfd, LOCK_EX); - sizenow = lseek(histfd, 0L, SEEK_END); - if (sizenow != hsize) { - /* - * Things have changed - */ - if (sizenow > hsize) { - /* someone has added some lines */ - bytes = sizenow - hsize; - base = (unsigned char *)mmap(0, sizenow, PROT_READ, MAP_FLAGS, histfd, 0); - if (base == MAP_FAILED) - goto bad; - new = base + hsize; - if (*new != COMMAND) { - munmap((caddr_t)base, sizenow); - goto bad; - } - hist_source->line--; - histload(hist_source, new, bytes); - hist_source->line++; - lno = hist_source->line; - munmap((caddr_t)base, sizenow); - hsize = sizenow; - } else { - /* it has shrunk */ - /* but to what? */ - /* we'll give up for now */ - goto bad; - } - } - /* - * we can write our bit now - */ - hdr[0] = COMMAND; - hdr[1] = (lno>>24)&0xff; - hdr[2] = (lno>>16)&0xff; - hdr[3] = (lno>>8)&0xff; - hdr[4] = lno&0xff; - (void) write(histfd, hdr, 5); - (void) write(histfd, cmd, strlen(cmd)+1); - hsize = lseek(histfd, 0L, SEEK_END); - (void) flock(histfd, LOCK_UN); - return; -bad: - hist_finish(); -} - -void -hist_finish(void) -{ - (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); -} -#endif diff --git a/io.c b/io.c deleted file mode 100644 index 8bd930e..0000000 --- a/io.c +++ /dev/null @@ -1,476 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: io.c,v 1.17 2004/12/18 22:35:41 millert Exp $ */ - -/* - * shell buffered IO and formatted output - */ - -#include -#include "sh.h" -#include "ksh_stat.h" - -static int initio_done; - -/* - * formatted output functions - */ - - -/* A shell error occurred (eg, syntax error, etc.) */ -void -errorf(const char *fmt, ...) -{ - va_list va; - - shl_stdout_ok = 0; /* debugging: note that stdout not valid */ - exstat = 1; - if (*fmt) { - error_prefix(true); - SH_VA_START(va, fmt); - shf_vfprintf(shl_out, fmt, va); - va_end(va); - shf_putchar('\n', shl_out); - } - shf_flush(shl_out); - unwind(LERROR); -} - -/* like errorf(), but no unwind is done */ -void -warningf(int fileline, const char *fmt, ...) -{ - va_list va; - - error_prefix(fileline); - SH_VA_START(va, fmt); - shf_vfprintf(shl_out, fmt, va); - va_end(va); - shf_putchar('\n', shl_out); - shf_flush(shl_out); -} - -/* Used by built-in utilities to prefix shell and utility name to message - * (also unwinds environments for special builtins). - */ -void -bi_errorf(const char *fmt, ...) -{ - va_list va; - - shl_stdout_ok = 0; /* debugging: note that stdout not valid */ - exstat = 1; - if (*fmt) { - error_prefix(true); - /* not set when main() calls parse_args() */ - if (builtin_argv0) - shf_fprintf(shl_out, "%s: ", builtin_argv0); - SH_VA_START(va, fmt); - shf_vfprintf(shl_out, fmt, va); - va_end(va); - shf_putchar('\n', shl_out); - } - shf_flush(shl_out); - /* POSIX special builtins and ksh special builtins cause - * non-interactive shells to exit. - * XXX odd use of KEEPASN; also may not want LERROR here - */ - if ((builtin_flag & SPEC_BI) - || (Flag(FPOSIX) && (builtin_flag & KEEPASN))) - { - builtin_argv0 = 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: "); - SH_VA_START(va, fmt); - shf_vfprintf(shl_out, fmt, va); - va_end(va); - shf_putchar('\n', shl_out); - shf_flush(shl_out); - if (jump) - unwind(LERROR); -} - -/* used by error reporting functions to print "ksh: .kshrc[25]: " */ -void -error_prefix(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; - SH_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"); - SH_VA_START(va, fmt); - shf_vfprintf(shl_stdout, fmt, va); - va_end(va); -} - -#ifdef KSH_DEBUG -static struct shf *kshdebug_shf; - -void -kshdebug_init_() -{ - if (kshdebug_shf) - shf_close(kshdebug_shf); - kshdebug_shf = shf_open("/tmp/ksh-debug.log", - O_WRONLY|O_APPEND|O_CREAT, 0600, - SHF_WR|SHF_MAPHI); - if (kshdebug_shf) { - shf_fprintf(kshdebug_shf, "\nNew shell[pid %d]\n", getpid()); - shf_flush(kshdebug_shf); - } -} - -/* print to debugging log */ -void -kshdebug_printf_(const char *fmt, ...) -{ - va_list va; - - if (!kshdebug_shf) - return; - SH_VA_START(va, fmt); - shf_fprintf(kshdebug_shf, "[%d] ", getpid()); - shf_vfprintf(kshdebug_shf, fmt, va); - va_end(va); - shf_flush(kshdebug_shf); -} - -void -kshdebug_dump_(str, mem, nbytes) - const char *str; - const void *mem; - int nbytes; -{ - int i, j; - int nprow = 16; - - if (!kshdebug_shf) - return; - shf_fprintf(kshdebug_shf, "[%d] %s:\n", getpid(), str); - for (i = 0; i < nbytes; i += nprow) { - char c = '\t'; - for (j = 0; j < nprow && i + j < nbytes; j++) { - shf_fprintf(kshdebug_shf, "%c%02x", - c, ((const unsigned char *) mem)[i + j]); - c = ' '; - } - shf_fprintf(kshdebug_shf, "\n"); - } - shf_flush(kshdebug_shf); -} -#endif /* KSH_DEBUG */ - -/* test if we can seek backwards fd (returns 0 or SHF_UNBUF) */ -int -can_seek(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"); - -#ifdef DUP2_BROKEN - /* Ultrix systems like to preserve the close-on-exec flag */ - if (ret >= 0) - (void) fcntl(nfd, F_SETFD, 0); -#endif /* DUP2_BROKEN */ - - return ret; -} - -/* - * move fd from user space (0<=fd<10) to shell space (fd>=10), - * set close-on-exec flag. - */ -int -savefd(int fd, int noclose) -{ - int nfd; - - if (fd < FDBASE) { - nfd = ksh_dupbase(fd, FDBASE); - if (nfd < 0) { - if (errno == EBADF) - return -1; - else - errorf("too many files open in shell"); - } - if (!noclose) - close(fd); - } else - nfd = fd; - 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) -{ - if (pipe(pv) < 0) - errorf("can't create pipe - try again"); - pv[0] = savefd(pv[0], 0); - pv[1] = savefd(pv[1], 0); -} - -void -closepipe(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(name[0]) && !name[1]) { - fd = name[0] - '0'; - if ((fl = fcntl(fd = name[0] - '0', F_GETFL, 0)) < 0) { - if (emsgp) - *emsgp = "bad file descriptor"; - return -1; - } - fl &= O_ACCMODE; - /* X_OK is a kludge to disable this check for dups (x<&1): - * historical shells never did this check (XXX don't know what - * posix has to say). - */ - if (!(mode & X_OK) && fl != O_RDWR - && (((mode & R_OK) && fl != O_RDONLY) - || ((mode & W_OK) && fl != O_WRONLY))) - { - if (emsgp) - *emsgp = (fl == O_WRONLY) ? - "fd not open for reading" - : "fd not open for writing"; - return -1; - } - return fd; - } else if (name[0] == 'p' && !name[1]) - return coproc_getfd(mode, emsgp); - if (emsgp) - *emsgp = "illegal file descriptor name"; - return -1; -} - -/* Called once from main */ -void -coproc_init(void) -{ - coproc.read = coproc.readw = coproc.write = -1; - coproc.njobs = 0; - coproc.id = 0; -} - -/* Called by c_read() when eof is read - close fd if it is the co-process fd */ -void -coproc_read_close(int fd) -{ - if (coproc.read >= 0 && fd == coproc.read) { - coproc_readw_close(fd); - close(coproc.read); - coproc.read = -1; - } -} - -/* Called by c_read() and by iosetup() to close the other side of the - * read pipe, so reads will actually terminate. - */ -void -coproc_readw_close(int fd) -{ - if (coproc.readw >= 0 && coproc.read >= 0 && fd == coproc.read) { - close(coproc.readw); - coproc.readw = -1; - } -} - -/* Called by c_print when a write to a fd fails with EPIPE and by iosetup - * when co-process input is dup'd - */ -void -coproc_write_close(int fd) -{ - if (coproc.write >= 0 && fd == coproc.write) { - close(coproc.write); - coproc.write = -1; - } -} - -/* Called to check for existence of/value of the co-process file descriptor. - * (Used by check_fd() and by c_read/c_print to deal with -p option). - */ -int -coproc_getfd(int mode, const char **emsgp) -{ - int fd = (mode & R_OK) ? coproc.read : coproc.write; - - if (fd >= 0) - return fd; - if (emsgp) - *emsgp = "no coprocess"; - return -1; -} - -/* called to close file descriptors related to the coprocess (if any) - * Should be called with SIGCHLD blocked. - */ -void -coproc_cleanup(int reuse) -{ - /* This to allow co-processes to share output pipe */ - if (!reuse || coproc.readw < 0 || coproc.read < 0) { - if (coproc.read >= 0) { - close(coproc.read); - coproc.read = -1; - } - if (coproc.readw >= 0) { - close(coproc.readw); - coproc.readw = -1; - } - } - if (coproc.write >= 0) { - close(coproc.write); - coproc.write = -1; - } -} - -/* - * temporary files - */ - -struct temp * -maketemp(Area *ap, Temp_type type, struct temp **tlist) -{ -#ifndef HAVE_MKSTEMP - static unsigned int inc; -#endif - struct temp *tp; - int len; - int fd; - char *path; - const char *dir; - - dir = tmpdir ? tmpdir : "/tmp"; - /* The 20 + 20 is a paranoid worst case for pid/inc */ - len = strlen(dir) + 3 + 20 + 20 + 1; - tp = (struct temp *) alloc(sizeof(struct temp) + len, ap); - tp->name = path = (char *) &tp[1]; - tp->shf = NULL; - tp->type = type; -#ifdef HAVE_MKSTEMP - shf_snprintf(path, len, "%s/shXXXXXXXX", dir); - fd = mkstemp(path); - if (fd >= 0) - tp->shf = shf_fdopen(fd, SHF_WR, NULL); -#else - while (1) { - /* Note that temp files need to fit 8.3 DOS limits */ - shf_snprintf(path, len, "%s/sh%05u.%03x", - dir, (unsigned) procpid, inc++); - /* Mode 0600 to be paranoid, O_TRUNC in case O_EXCL isn't - * really there. - */ - fd = open(path, O_RDWR|O_CREAT|O_EXCL|O_TRUNC, 0600); - if (fd >= 0) { - tp->shf = shf_fdopen(fd, SHF_WR, NULL); - break; - } - if (errno != EINTR -#ifdef EEXIST - && errno != EEXIST -#endif /* EEXIST */ -#ifdef EISDIR - && errno != EISDIR -#endif /* EISDIR */ - ) - /* Error must be printed by caller: don't know here if - * errorf() or bi_errorf() should be used. - */ - break; - } -#endif /* !def HAVE_MKSTEMP */ - tp->pid = procpid; - - tp->next = *tlist; - *tlist = tp; - - return tp; -} diff --git a/jobs.c b/jobs.c deleted file mode 100644 index b5f2e51..0000000 --- a/jobs.c +++ /dev/null @@ -1,1816 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: jobs.c,v 1.30 2004/12/22 18:48:56 millert Exp $ */ - -/* - * Process and job control - */ - -/* - * Reworked/Rewritten version of Eric Gisin's/Ron Natalie's code by - * Larry Bouzane (larry@cs.mun.ca) and hacked again by - * Michael Rendell (michael@cs.mun.ca) - * - * The interface to the rest of the shell should probably be changed - * to allow use of vfork() when available but that would be way too much - * work :) - * - * Notes regarding the copious ifdefs: - * - JOB_SIGS is independent of JOBS - it is defined if there are modern - * signal and wait routines available. This is preferred, even when - * JOBS is not defined, since the shell will not otherwise notice when - * background jobs die until the shell waits for a foreground process - * to die. - * - TTY_PGRP defined iff JOBS is defined - defined if there are tty - * process groups - * - NEED_PGRP_SYNC defined iff JOBS is defined - see comment below - */ - -#include "sh.h" -#include -#include "ksh_wait.h" -#include -#include -#include "tty.h" - -__RCSID("$MirOS$"); - -/* Start of system configuration stuff */ - -/* We keep CHILD_MAX zombie processes around (exact value isn't critical) */ -#ifndef CHILD_MAX -# if defined(HAVE_SYSCONF) && defined(_SC_CHILD_MAX) -# define CHILD_MAX sysconf(_SC_CHILD_MAX) -# else /* _SC_CHILD_MAX */ -# ifdef _POSIX_CHILD_MAX -# define CHILD_MAX ((_POSIX_CHILD_MAX) * 2) -# else /* _POSIX_CHILD_MAX */ -# define CHILD_MAX 20 -# endif /* _POSIX_CHILD_MAX */ -# endif /* _SC_CHILD_MAX */ -#endif /* !CHILD_MAX */ - -#ifdef JOBS -# if defined(HAVE_TCSETPGRP) || defined(TIOCSPGRP) -# define TTY_PGRP -# endif -# ifdef BSD_PGRP -# define setpgid setpgrp -# define getpgID() getpgrp(0) -# else -# define getpgID() getpgrp() -# endif -# if defined(TTY_PGRP) && !defined(HAVE_TCSETPGRP) -int tcsetpgrp(int fd, pid_t grp); -int tcgetpgrp(int fd); - -int -tcsetpgrp(int fd, pid_t grp) -{ - return ioctl(fd, TIOCSPGRP, &grp); -} - -int -tcgetpgrp(int fd) -{ - int r, grp; - - if ((r = ioctl(fd, TIOCGPGRP, &grp)) < 0) - return r; - return grp; -} -# endif /* !HAVE_TCSETPGRP && TIOCSPGRP */ -#else /* JOBS */ -/* These so we can use ifdef xxx instead of if defined(JOBS) && defined(xxx) */ -# undef TTY_PGRP -# undef NEED_PGRP_SYNC -#endif /* JOBS */ - -/* End of system configuration stuff */ - - -/* Order important! */ -#define PRUNNING 0 -#define PEXITED 1 -#define PSIGNALLED 2 -#define PSTOPPED 3 - -typedef struct proc Proc; -struct proc { - Proc *next; /* next process in pipeline (if any) */ - int state; - WAIT_T status; /* wait status */ - pid_t pid; /* process id */ - char command[48]; /* process command string */ -}; - -/* Notify/print flag - j_print() argument */ -#define JP_NONE 0 /* don't print anything */ -#define JP_SHORT 1 /* print signals processes were killed by */ -#define JP_MEDIUM 2 /* print [job-num] -/+ command */ -#define JP_LONG 3 /* print [job-num] -/+ pid command */ -#define JP_PGRP 4 /* print pgrp */ - -/* put_job() flags */ -#define PJ_ON_FRONT 0 /* at very front */ -#define PJ_PAST_STOPPED 1 /* just past any stopped jobs */ - -/* Job.flags values */ -#define JF_STARTED 0x001 /* set when all processes in job are started */ -#define JF_WAITING 0x002 /* set if j_waitj() is waiting on job */ -#define JF_W_ASYNCNOTIFY 0x004 /* set if waiting and async notification ok */ -#define JF_XXCOM 0x008 /* set for `command` jobs */ -#define JF_FG 0x010 /* running in foreground (also has tty pgrp) */ -#define JF_SAVEDTTY 0x020 /* j->ttystate is valid */ -#define JF_CHANGED 0x040 /* process has changed state */ -#define JF_KNOWN 0x080 /* $! referenced */ -#define JF_ZOMBIE 0x100 /* known, unwaited process */ -#define JF_REMOVE 0x200 /* flagged for removal (j_jobs()/j_noityf()) */ -#define JF_USETTYMODE 0x400 /* tty mode saved if process exits normally */ -#define JF_SAVEDTTYPGRP 0x800 /* j->saved_ttypgrp is valid */ - -typedef struct job Job; -struct job { - Job *next; /* next job in list */ - int job; /* job number: %n */ - int flags; /* see JF_* */ - int state; /* job state */ - int status; /* exit status of last process */ - pid_t pgrp; /* process group of job */ - pid_t ppid; /* pid of process that forked job */ - INT32 age; /* number of jobs started */ - struct timeval systime; /* system time used by job */ - struct timeval usrtime; /* user time used by job */ - Proc *proc_list; /* process list */ - Proc *last_proc; /* last process in list */ - Coproc_id coproc_id; /* 0 or id of coprocess output pipe */ -#ifdef TTY_PGRP - struct termios ttystate;/* saved tty state for stopped jobs */ - pid_t saved_ttypgrp; /* saved tty process group for stopped jobs */ -#endif /* TTY_PGRP */ -}; - -/* Flags for j_waitj() */ -#define JW_NONE 0x00 -#define JW_INTERRUPT 0x01 /* ^C will stop the wait */ -#define JW_ASYNCNOTIFY 0x02 /* asynchronous notification during wait ok */ -#define JW_STOPPEDWAIT 0x04 /* wait even if job stopped */ - -/* Error codes for j_lookup() */ -#define JL_OK 0 -#define JL_NOSUCH 1 /* no such job */ -#define JL_AMBIG 2 /* %foo or %?foo is ambiguous */ -#define JL_INVALID 3 /* non-pid, non-% job id */ - -static const char *const lookup_msgs[] = { - null, - "no such job", - "ambiguous", - "argument must be %job or process id", - 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 */ -static INT32 njobs; /* # of jobs started */ -static int child_max; /* CHILD_MAX */ - - -#ifdef JOB_SIGS -/* held_sigchld is set if sigchld occurs before a job is completely started */ -static volatile sig_atomic_t held_sigchld; -#endif /* JOB_SIGS */ - -#ifdef JOBS -static struct shf *shl_j; -#endif /* JOBS */ - -#ifdef NEED_PGRP_SYNC -/* On some systems, the kernel doesn't count zombie processes when checking - * if a process group is valid, which can cause problems in creating the - * pipeline "cmd1 | cmd2": if cmd1 can die (and go into the zombie state) - * before cmd2 is started, the kernel doesn't allow the setpgid() for cmd2 - * to succeed. Solution is to create a pipe between the parent and the first - * process; the first process doesn't do anything until the pipe is closed - * and the parent doesn't close the pipe until all the processes are started. - */ -static int j_sync_pipe[2]; -static int j_sync_open; -#endif /* NEED_PGRP_SYNC */ - -#ifdef TTY_PGRP -static int ttypgrp_ok; /* set if can use tty pgrps */ -static pid_t restore_ttypgrp = -1; -static pid_t our_pgrp; -static int const tt_sigs[] = { SIGTSTP, SIGTTIN, SIGTTOU }; -#endif /* TTY_PGRP */ - -static void j_set_async(Job *j); -static void j_startjob(Job *j); -static int j_waitj(Job *j, int flags, const char *where); -static RETSIGTYPE j_sigchld(int sig); -static void j_print(Job *j, int how, struct shf *shf); -static Job *j_lookup(const char *cp, int *ecodep); -static Job *new_job(void); -static Proc *new_proc(void); -static void check_job(Job *j); -static void put_job(Job *j, int where); -static void remove_job(Job *j, const char *where); -static int kill_job(Job *j, int sig); - -/* initialize job control */ -void -j_init(int mflagset) -{ - child_max = CHILD_MAX; /* so syscon() isn't always being called */ - -#ifdef JOB_SIGS - 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); -#else /* JOB_SIGS */ - /* Make sure SIGCHLD isn't ignored - can do odd things under SYSV */ - setsig(&sigtraps[SIGCHLD], SIG_DFL, SS_RESTORE_ORIG|SS_FORCE); -#endif /* JOB_SIGS */ - -#ifdef JOBS - if (!mflagset && Flag(FTALKING)) - Flag(FMONITOR) = 1; - - /* shl_j is used to do asynchronous notification (used in - * an interrupt handler, so need a distinct shf) - */ - shl_j = shf_fdopen(2, SHF_WR, NULL); - -# ifdef TTY_PGRP - if (Flag(FMONITOR) || Flag(FTALKING)) { - int i; - - /* the TF_SHELL_USES test is a kludge that lets us know if - * if the signals have been changed by the shell. - */ - for (i = NELEM(tt_sigs); --i >= 0; ) { - sigtraps[tt_sigs[i]].flags |= TF_SHELL_USES; - /* j_change() sets this to SS_RESTORE_DFL if FMONITOR */ - setsig(&sigtraps[tt_sigs[i]], SIG_IGN, - SS_RESTORE_IGN|SS_FORCE); - } - } -# endif /* TTY_PGRP */ - - /* j_change() calls tty_init() */ - if (Flag(FMONITOR)) - j_change(); - else -#endif /* JOBS */ - if (Flag(FTALKING)) - tty_init(true); -} - -/* job cleanup before shell exit */ -void -j_exit(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 -# ifdef TTY_PGRP - if (kshpid == procpid && restore_ttypgrp >= 0) { - /* Need to restore the tty pgrp to what it was when the - * shell started up, so that the process that started us - * will be able to access the tty when we are done. - * Also need to restore our process group in case we are - * about to do an exec so that both our parent and the - * process we are to become will be able to access the tty. - */ - tcsetpgrp(tty_fd, restore_ttypgrp); - setpgid(0, restore_ttypgrp); - } -# endif /* TTY_PGRP */ - if (Flag(FMONITOR)) { - Flag(FMONITOR) = 0; - j_change(); - } -#endif /* JOBS */ -} - -#ifdef JOBS -/* turn job control on or off according to Flag(FMONITOR) */ -void -j_change(void) -{ - int i; - - if (Flag(FMONITOR)) { - /* Don't call tcgetattr() 'til we own the tty process group */ - tty_init(false); - -# ifdef TTY_PGRP - /* no controlling tty, no SIGT* */ - ttypgrp_ok = tty_fd >= 0 && tty_devtty; - - if (ttypgrp_ok && (our_pgrp = getpgID()) < 0) { - warningf(false, "j_init: getpgrp() failed: %s", - strerror(errno)); - ttypgrp_ok = 0; - } - if (ttypgrp_ok) { - setsig(&sigtraps[SIGTTIN], SIG_DFL, - SS_RESTORE_ORIG|SS_FORCE); - /* wait to be given tty (POSIX.1, B.2, job control) */ - while (1) { - pid_t ttypgrp; - - if ((ttypgrp = tcgetpgrp(tty_fd)) < 0) { - warningf(false, - "j_init: tcgetpgrp() failed: %s", - strerror(errno)); - ttypgrp_ok = 0; - break; - } - if (ttypgrp == our_pgrp) - break; - kill(0, SIGTTIN); - } - } - for (i = NELEM(tt_sigs); --i >= 0; ) - setsig(&sigtraps[tt_sigs[i]], SIG_IGN, - SS_RESTORE_DFL|SS_FORCE); - if (ttypgrp_ok && our_pgrp != kshpid) { - if (setpgid(0, kshpid) < 0) { - warningf(false, - "j_init: setpgid() failed: %s", - strerror(errno)); - ttypgrp_ok = 0; - } else { - if (tcsetpgrp(tty_fd, kshpid) < 0) { - warningf(false, - "j_init: tcsetpgrp() failed: %s", - strerror(errno)); - ttypgrp_ok = 0; - } else - restore_ttypgrp = our_pgrp; - our_pgrp = kshpid; - } - } -# if defined(NTTYDISC) && defined(TIOCSETD) && !defined(HAVE_TERMIOS_H) && !defined(HAVE_TERMIO_H) - if (ttypgrp_ok) { - int ldisc = NTTYDISC; - - if (ioctl(tty_fd, TIOCSETD, &ldisc) < 0) - warningf(false, - "j_init: can't set new line discipline: %s", - strerror(errno)); - } -# endif /* NTTYDISC && TIOCSETD */ - if (!ttypgrp_ok) - warningf(false, "warning: won't have full job control"); -# endif /* TTY_PGRP */ - if (tty_fd >= 0) - tcgetattr(tty_fd, &tty_state); - } else { -# ifdef TTY_PGRP - ttypgrp_ok = 0; - if (Flag(FTALKING)) - for (i = NELEM(tt_sigs); --i >= 0; ) - setsig(&sigtraps[tt_sigs[i]], SIG_IGN, - SS_RESTORE_IGN|SS_FORCE); - else - for (i = NELEM(tt_sigs); --i >= 0; ) { - if (sigtraps[tt_sigs[i]].flags & (TF_ORIG_IGN - |TF_ORIG_DFL)) - setsig(&sigtraps[tt_sigs[i]], - (sigtraps[tt_sigs[i]].flags & TF_ORIG_IGN) ? SIG_IGN : SIG_DFL, - SS_RESTORE_ORIG|SS_FORCE); - } -# endif /* TTY_PGRP */ - if (!Flag(FTALKING)) - tty_close(); - } -} -#endif /* JOBS */ - -/* execute tree in child subprocess */ -int -exchild(struct op *t, int flags, int close_fd) - - - /* used if XPCLOSE or XCCLOSE */ -{ - static Proc *last_proc; /* for pipelines */ - - int i; -#ifdef JOB_SIGS - sigset_t omask; -#endif /* JOB_SIGS */ - Proc *p; - Job *j; - int rv = 0; - int forksleep; - int ischild; - - if (flags & XEXEC) - /* Clear XFORK|XPCLOSE|XCCLOSE|XCOPROC|XPIPEO|XPIPEI|XXCOM|XBGND - * (also done in another execute() below) - */ - return execute(t, flags & (XEXEC | XERROK)); - -#ifdef JOB_SIGS - /* no SIGCHLD's while messing with job and process lists */ - sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); -#endif /* JOB_SIGS */ - - p = new_proc(); - p->next = NULL; - p->state = PRUNNING; - WSTATUS(p->status) = 0; - p->pid = 0; - - /* link process into jobs list */ - if (flags&XPIPEI) { /* continuing with a pipe */ - if (!last_job) - internal_errorf(1, "exchild: XPIPEI and no last_job - pid %d", (int) procpid); - j = last_job; - last_proc->next = p; - last_proc = p; - } else { -#ifdef NEED_PGRP_SYNC - if (j_sync_open) { /* should never happen */ - j_sync_open = 0; - closepipe(j_sync_pipe); - } - /* don't do the sync pipe business if there is no pipeline */ - if (flags & XPIPEO) { - openpipe(j_sync_pipe); - j_sync_open = 1; - } -#endif /* NEED_PGRP_SYNC */ - j = new_job(); /* fills in j->job */ - /* we don't consider XXCOM's foreground since they don't get - * tty process group and we don't save or restore tty modes. - */ - j->flags = (flags & XXCOM) ? JF_XXCOM - : ((flags & XBGND) ? 0 : (JF_FG|JF_USETTYMODE)); - 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"); -#ifdef NEED_PGRP_SYNC - if (j_sync_open) { - closepipe(j_sync_pipe); - j_sync_open = 0; - } -#endif /* NEED_PGRP_SYNC */ -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ - errorf("cannot fork - try again"); - } - ischild = i == 0; - if (ischild) - p->pid = procpid = getpid(); - else - p->pid = i; - -#ifdef JOBS - /* job control set up */ - if (Flag(FMONITOR) && !(flags&XXCOM)) { - int dotty = 0; -# ifdef NEED_PGRP_SYNC - int first_child_sync = 0; -# endif /* NEED_PGRP_SYNC */ - -# ifdef NEED_PGRP_SYNC - if (j_sync_open) { - /* - * The Parent closes 0, keeps 1 open 'til the whole - * pipeline is started. The First child closes 1, - * keeps 0 open (reads from it). The remaining - * children just have to close 1 (parent has already - * closeed 0). - */ - if (j->pgrp == 0) { /* First process */ - close(j_sync_pipe[ischild]); - j_sync_pipe[ischild] = -1; - first_child_sync = ischild; - } else if (ischild) { - j_sync_open = 0; - closepipe(j_sync_pipe); - } - } -# endif /* NEED_PGRP_SYNC */ - if (j->pgrp == 0) { /* First process */ - j->pgrp = p->pid; - dotty = 1; - } - - /* set pgrp in both parent and child to deal with race - * condition - */ - setpgid(p->pid, j->pgrp); -# ifdef TTY_PGRP - /* YYY: should this be - if (ttypgrp_ok && ischild && !(flags&XBGND)) - tcsetpgrp(tty_fd, j->pgrp); - instead? (see also YYY below) - */ - if (ttypgrp_ok && dotty && !(flags & XBGND)) - tcsetpgrp(tty_fd, j->pgrp); -# endif /* TTY_PGRP */ -# ifdef NEED_PGRP_SYNC - if (first_child_sync) { - char c; - while (read(j_sync_pipe[0], &c, 1) == -1 - && errno == EINTR) - ; - close(j_sync_pipe[0]); - j_sync_open = 0; - } -# endif /* NEED_PGRP_SYNC */ - } -#endif /* JOBS */ - - /* used to close pipe input fd */ - if (close_fd >= 0 && (((flags & XPCLOSE) && !ischild) - || ((flags & XCCLOSE) && ischild))) - close(close_fd); - if (ischild) { /* child */ - /* Do this before restoring signal */ - if (flags & XCOPROC) - coproc_cleanup(false); -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ - cleanup_parents_env(); -#ifdef TTY_PGRP - /* If FMONITOR or FTALKING is set, these signals are ignored, - * if neither FMONITOR nor FTALKING are set, the signals have - * their inherited values. - */ - if (Flag(FMONITOR) && !(flags & XXCOM)) { - for (i = NELEM(tt_sigs); --i >= 0; ) - setsig(&sigtraps[tt_sigs[i]], SIG_DFL, - SS_RESTORE_DFL|SS_FORCE); - } -#endif /* TTY_PGRP */ -#ifdef HAVE_NICE - if (Flag(FBGNICE) && (flags & XBGND)) - nice(4); -#endif /* HAVE_NICE */ - if ((flags & XBGND) && !Flag(FMONITOR)) { - setsig(&sigtraps[SIGINT], SIG_IGN, - SS_RESTORE_IGN|SS_FORCE); - setsig(&sigtraps[SIGQUIT], SIG_IGN, - SS_RESTORE_IGN|SS_FORCE); - if (!(flags & (XPIPEI | XCOPROC))) { - int fd = open("/dev/null", 0); - 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); /* no return */ - internal_errorf(0, "exchild: execute() returned"); - unwind(LLEAVE); - /* NOTREACHED */ - } - - /* shell (parent) stuff */ - /* Ensure next child gets a (slightly) different $RANDOM sequence */ - change_random(); - if (!(flags & XPIPEO)) { /* last process in a job */ -#ifdef TTY_PGRP - /* YYY: Is this needed? (see also YYY above) - if (Flag(FMONITOR) && !(flags&(XXCOM|XBGND))) - tcsetpgrp(tty_fd, j->pgrp); - */ -#endif /* TTY_PGRP */ - j_startjob(j); - 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"); - } - -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ - - return rv; -} - -/* start the last job: only used for `command` jobs */ -void -startlast(void) -{ -#ifdef JOB_SIGS - sigset_t omask; - - sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); -#endif /* JOB_SIGS */ - - if (last_job) { /* no need to report error - waitlast() will do it */ - /* ensure it isn't removed by check_job() */ - last_job->flags |= JF_WAITING; - j_startjob(last_job); - } -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ -} - -/* wait for last job: only used for `command` jobs */ -int -waitlast(void) -{ - int rv; - Job *j; -#ifdef JOB_SIGS - sigset_t omask; - - sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); -#endif /* JOB_SIGS */ - - j = last_job; - if (!j || !(j->flags & JF_STARTED)) { - if (!j) - warningf(true, "waitlast: no last job"); - else - internal_errorf(0, "waitlast: not started"); -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ - return 125; /* not so arbitrary, non-zero value */ - } - - rv = j_waitj(j, JW_NONE, "jw:waitlast"); - -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ - - 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; -#ifdef JOB_SIGS - sigset_t omask; - - sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); -#endif /* JOB_SIGS */ - - *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) { -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ - return -1; - } - } else if ((j = j_lookup(cp, &ecode))) { - /* don't report normal job completion */ - flags &= ~JW_ASYNCNOTIFY; - if (j->ppid != procpid) { -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ - return -1; - } - } else { -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ - if (ecode != JL_NOSUCH) - bi_errorf("%s: %s", cp, lookup_msgs[ecode]); - return -1; - } - - /* at&t ksh will wait for stopped jobs - we don't */ - rv = j_waitj(j, flags, "jw:waitfor"); - -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ - - 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; -#ifdef JOB_SIGS - sigset_t omask; - - sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); -#endif /* JOB_SIGS */ - - if ((j = j_lookup(cp, &ecode)) == NULL) { -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ - bi_errorf("%s: %s", cp, lookup_msgs[ecode]); - return 1; - } - - if (j->pgrp == 0) { /* started when !Flag(FMONITOR) */ - if (kill_job(j, sig) < 0) { - bi_errorf("%s: %s", cp, strerror(errno)); - rv = 1; - } - } else { -#ifdef JOBS - if (j->state == PSTOPPED && (sig == SIGTERM || sig == SIGHUP)) - (void) killpg(j->pgrp, SIGCONT); -#endif /* JOBS */ - if (killpg(j->pgrp, sig) < 0) { - bi_errorf("%s: %s", cp, strerror(errno)); - rv = 1; - } - } - -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ - - 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; - WSTATUS(p->status) = 0; - running = 1; - } - shprintf("%s%s", p->command, p->next ? "| " : null); - } - shprintf(newline); - shf_flush(shl_stdout); - if (running) - j->state = PRUNNING; - - put_job(j, PJ_PAST_STOPPED); - if (bg) - j_set_async(j); - else { -# ifdef TTY_PGRP - /* attach tty to job */ - if (j->state == PRUNNING) { - if (ttypgrp_ok && (j->flags & JF_SAVEDTTY)) - 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 /* TTY_PGRP */ - 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 TTY_PGRP - 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 /* TTY_PGRP */ - } - sigprocmask(SIG_SETMASK, &omask, NULL); - bi_errorf("cannot continue job %s: %s", - cp, strerror(err)); - return 1; - } - if (!bg) { -# ifdef TTY_PGRP - if (ttypgrp_ok) { - j->flags &= ~(JF_SAVEDTTY | JF_SAVEDTTYPGRP); - } -# endif /* TTY_PGRP */ - rv = j_waitj(j, JW_NONE, "jw:resume"); - } - sigprocmask(SIG_SETMASK, &omask, 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; -} - -/* 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; -#ifdef JOB_SIGS - sigset_t omask; - - sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); -#endif /* JOB_SIGS */ - - if (nflag < 0) { /* kludge: print zombies */ - nflag = 0; - zflag = 1; - } - if (cp) { - int ecode; - - if ((j = j_lookup(cp, &ecode)) == NULL) { -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ - bi_errorf("%s: %s", cp, lookup_msgs[ecode]); - return 1; - } - } else - j = job_list; - how = slp == 0 ? JP_MEDIUM : (slp == 1 ? JP_LONG : JP_PGRP); - for (; j; j = j->next) { - if ((!(j->flags & JF_ZOMBIE) || zflag) - && (!nflag || (j->flags & JF_CHANGED))) - { - j_print(j, how, shl_stdout); - if (j->state == PEXITED || j->state == PSIGNALLED) - j->flags |= JF_REMOVE; - } - if (cp) - break; - } - /* Remove jobs after printing so there won't be multiple + or - jobs */ - for (j = job_list; j; j = tmp) { - tmp = j->next; - if (j->flags & JF_REMOVE) - remove_job(j, "jobs"); - } -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ - return 0; -} - -/* list jobs for top-level notification */ -void -j_notify(void) -{ - Job *j, *tmp; -#ifdef JOB_SIGS - sigset_t omask; - - sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); -#endif /* JOB_SIGS */ - for (j = job_list; j; j = j->next) { -#ifdef JOBS - if (Flag(FMONITOR) && (j->flags & JF_CHANGED)) - j_print(j, JP_MEDIUM, shl_out); -#endif /* JOBS */ - /* Remove job after doing reports so there aren't - * multiple +/- jobs. - */ - if (j->state == PEXITED || j->state == PSIGNALLED) - j->flags |= JF_REMOVE; - } - for (j = job_list; j; j = tmp) { - tmp = j->next; - if (j->flags & JF_REMOVE) - remove_job(j, "notify"); - } - shf_flush(shl_out); -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ -} - -/* Return pid of last process in last asynchronous job */ -pid_t -j_async(void) -{ -#ifdef JOB_SIGS - sigset_t omask; - - sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); -#endif /* JOB_SIGS */ - - if (async_job) - async_job->flags |= JF_KNOWN; - -#ifdef JOB_SIGS - sigprocmask(SIG_SETMASK, &omask, NULL); -#endif /* JOB_SIGS */ - - return async_pid; -} - -/* Make j the last async process - * - * If jobs are compiled in then this routine expects sigchld to be blocked. - */ -static void -j_set_async(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; - -#ifdef NEED_PGRP_SYNC - if (j_sync_open) { - j_sync_open = 0; - closepipe(j_sync_pipe); - } -#endif /* NEED_PGRP_SYNC */ -#ifdef JOB_SIGS - if (held_sigchld) { - held_sigchld = 0; - /* Don't call j_sigchld() as it may remove job... */ - kill(procpid, SIGCHLD); - } -#endif /* JOB_SIGS */ -} - -/* - * wait for job to complete or change state - * - * If jobs are compiled in then this routine expects sigchld to be blocked. - */ -static int -j_waitj(Job *j, int flags, const char *where) - - /* see JW_* */ - -{ - int rv; - - /* - * No auto-notify on the job we are waiting on. - */ - j->flags |= JF_WAITING; - if (flags & JW_ASYNCNOTIFY) - j->flags |= JF_W_ASYNCNOTIFY; - - if (!Flag(FMONITOR)) - flags |= JW_STOPPEDWAIT; - - while ((volatile int) j->state == PRUNNING - || ((flags & JW_STOPPEDWAIT) - && (volatile int) j->state == PSTOPPED)) - { -#ifdef JOB_SIGS - sigsuspend(&sm_default); -#else /* JOB_SIGS */ - j_sigchld(SIGCHLD); -#endif /* JOB_SIGS */ - if (fatal_trap) { - int oldf = j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY); - j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); - runtraps(TF_FATAL); - j->flags |= oldf; /* not reached... */ - } - if ((flags & JW_INTERRUPT) && (rv = trap_pending())) { - j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); - return -rv; - } - } - j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); - - if (j->flags & JF_FG) { - WAIT_T status; - - j->flags &= ~JF_FG; -#ifdef TTY_PGRP - if (Flag(FMONITOR) && ttypgrp_ok && j->pgrp) { - /* - * Save the tty's current pgrp so it can be restored - * when the job is foregrounded. This is to - * deal with things like the GNU su which does - * a fork/exec instead of an exec (the fork means - * the execed shell gets a different pid from its - * pgrp, so naturally it sets its pgrp and gets hosed - * when it gets foregrounded by the parent shell, which - * has restored the tty's pgrp to that of the su - * process). - */ - if (j->state == PSTOPPED - && (j->saved_ttypgrp = tcgetpgrp(tty_fd)) >= 0) - j->flags |= JF_SAVEDTTYPGRP; - if (tcsetpgrp(tty_fd, our_pgrp) < 0) { - warningf(true, - "j_waitj: tcsetpgrp(%d, %d) failed: %s", - tty_fd, (int) our_pgrp, - strerror(errno)); - } - if (j->state == PSTOPPED) { - j->flags |= JF_SAVEDTTY; - tcgetattr(tty_fd, &j->ttystate); - } - } -#endif /* TTY_PGRP */ - if (tty_fd >= 0) { - /* Only restore tty settings if job was originally - * started in the foreground. Problems can be - * caused by things like 'more foobar &' which will - * typically get and save the shell's vi/emacs tty - * settings before setting up the tty for itself; - * when more exits, it restores the 'original' - * settings, and things go down hill from there... - */ - if (j->state == PEXITED && j->status == 0 - && (j->flags & JF_USETTYMODE)) - { - 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 RETSIGTYPE -j_sigchld(int sig GCC_FUNC_ATTR(unused)) -{ - int errno_ = errno; - Job *j; - Proc UNINITIALIZED(*p); - int pid; - WAIT_T status; - struct rusage ru0, ru1; - -#ifdef JOB_SIGS - /* Don't wait for any processes if a job is partially started. - * This is so we don't do away with the process group leader - * before all the processes in a pipe line are started (so the - * setpgid() won't fail) - */ - for (j = job_list; j; j = j->next) - if (j->ppid == procpid && !(j->flags & JF_STARTED)) { - held_sigchld = 1; - return RETSIGVAL; - } -#endif /* JOB_SIGS */ - - getrusage(RUSAGE_CHILDREN, &ru0); - do { -#ifdef JOB_SIGS - pid = ksh_waitpid(-1, &status, (WNOHANG|WUNTRACED)); -#else /* JOB_SIGS */ - pid = wait(&status); -#endif /* JOB_SIGS */ - - if (pid <= 0) /* return if would block (0) ... */ - break; /* ... or no children or interrupted (-1) */ - - 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 */ - } -#ifdef JOB_SIGS - while (1); -#else /* JOB_SIGS */ - while (0); -#endif /* JOB_SIGS */ - - errno = errno_; - - return RETSIGVAL; -} - -/* - * Called only when a process in j has exited/stopped (ie, called only - * from j_sigchld()). If no processes are running, the job status - * and state are updated, asynchronous job notification is done and, - * if unneeded, the job is removed. - * - * If jobs are compiled in then this routine expects sigchld to be blocked. - */ -static void -check_job(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 = e; ep; ep = ep->oenv) - if (ep->savefd && ep->savefd[2]) - fd = ep->savefd[2]; - shf_reopen(fd, SHF_WR, shl_j); - } - /* Can't call j_notify() as it removes jobs. The job - * must stay in the job list as j_waitj() may be - * running with this job. - */ - j_print(j, JP_MEDIUM, shl_j); - shf_flush(shl_j); - if (!(j->flags & JF_WAITING) && j->state != PSTOPPED) - remove_job(j, "notify"); - } - } -#endif /* JOBS */ - if (!Flag(FMONITOR) && !(j->flags & (JF_WAITING|JF_FG)) - && j->state != PSTOPPED) - { - if (j == async_job || (j->flags & JF_KNOWN)) { - j->flags |= JF_ZOMBIE; - j->job = -1; - nzombie++; - } else - remove_job(j, "checkjob"); - } -} - -/* - * Print job status in either short, medium or long format. - * - * If jobs are compiled in then this routine expects sigchld to be blocked. - */ -static void -j_print(Job *j, int how, struct shf *shf) -{ - Proc *p; - int state; - WAIT_T status; - int coredumped; - char jobchar = ' '; - char buf[64]; - const char *filler; - int output = 0; - - if (how == JP_PGRP) { - /* POSIX doesn't say what to do it there is no process - * group leader (ie, !FMONITOR). We arbitrarily return - * last pid (which is what $! returns). - */ - shf_fprintf(shf, "%d\n", j->pgrp ? j->pgrp - : (j->last_proc ? j->last_proc->pid : 0)); - return; - } - j->flags &= ~JF_CHANGED; - filler = j->job > 10 ? "\n " : "\n "; - if (j == job_list) - jobchar = '+'; - else if (j == job_list->next) - jobchar = '-'; - - for (p = j->proc_list; p != 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 (WIFCORED(p->status)) - coredumped = 1; - /* kludge for not reporting 'normal termination signals' - * (ie, SIGINT, SIGPIPE) - */ - if (how == JP_SHORT && !coredumped - && (WTERMSIG(p->status) == SIGINT - || WTERMSIG(p->status) == SIGPIPE)) { - buf[0] = '\0'; - } else - strlcpy(buf, sigtraps[WTERMSIG(p->status)].mess, - sizeof buf); - break; - } - - if (how != JP_SHORT) { - if (p == j->proc_list) - shf_fprintf(shf, "[%d] %c ", j->job, jobchar); - else - shf_fprintf(shf, "%s", filler); - } - - if (how == JP_LONG) - shf_fprintf(shf, "%5d ", p->pid); - - if (how == JP_SHORT) { - if (buf[0]) { - output = 1; - shf_fprintf(shf, "%s%s ", - buf, coredumped ? " (core dumped)" : null); - } - } else { - output = 1; - shf_fprintf(shf, "%-20s %s%s%s", buf, p->command, - p->next ? "|" : null, - coredumped ? " (core dumped)" : null); - } - - state = p->state; - status = p->status; - p = p->next; - while (p && p->state == state - && WSTATUS(p->status) == WSTATUS(status)) - { - if (how == JP_LONG) - shf_fprintf(shf, "%s%5d %-20s %s%s", filler, p->pid, - space, p->command, p->next ? "|" : null); - else if (how == JP_MEDIUM) - shf_fprintf(shf, " %s%s", p->command, - p->next ? "|" : null); - p = p->next; - } - } - if (output) - shf_fprintf(shf, newline); -} - -/* Convert % sequence to job - * - * If jobs are compiled in then this routine expects sigchld to be blocked. - */ -static Job * -j_lookup(const char *cp, int *ecodep) -{ - Job *j, *last_match; - Proc *p; - int len, job = 0; - - if (digit(*cp)) { - job = atoi(cp); - /* Look for last_proc->pid (what $! returns) first... */ - for (j = job_list; j != 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 = atoi(cp); - 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 = (Job *) alloc(sizeof(Job), APERM); - - /* brute force method */ - for (i = 1; ; i++) { - for (j = job_list; j && j->job != i; j = j->next) - ; - if (j == NULL) - break; - } - newj->job = i; - - return newj; -} - -/* Allocate new process strut - * - * If jobs are compiled in then this routine expects sigchld to be blocked. - */ -static Proc * -new_proc(void) -{ - Proc *p; - - if (free_procs != NULL) { - p = free_procs; - free_procs = free_procs->next; - } else - p = (Proc *) alloc(sizeof(Proc), APERM); - - return p; -} - -/* Take job out of job_list and put old structures into free list. - * Keeps nzombies, last_job and async_job up to date. - * - * If jobs are compiled in then this routine expects sigchld to be blocked. - */ -static void -remove_job(Job *j, const char *where) -{ - Proc *p, *tmp; - Job **prev, *curr; - - prev = &job_list; - curr = *prev; - for (; curr != 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/ksh.1 b/ksh.1 deleted file mode 100644 index 566e563..0000000 --- a/ksh.1 +++ /dev/null @@ -1,5367 +0,0 @@ -.\" $MirOS$ -.\" $OpenBSD: ksh.1tbl,v 1.84 2004/12/22 18:58:44 millert Exp $ -.\" $OpenBSD: sh.1tbl,v 1.53 2004/12/10 01:56:56 jaredy Exp $ -.\" -.\" Public Domain. -.\" -.Dd November 10, 2004 -.Dt KSH 1 -.Os MirOS -.\" define Mx macro for portable mksh, just in case... -.de Mx -.nr cF \\n(.f -.nr cZ \\n(.s -.ds aa \&\f\\n(cF\s\\n(cZ -.if \\n(aC==0 \{\ -. if \\n(.$==0 \&MirOS\\*(aa -.\} -.if \\n(.$==1 \{\ -. if "\\$1"6" \&MirOS #6\\*(aa -. if "\\$1"." \&MirOS\\$1\\*(aa -. if "\\$1"," \&MirOS\\$1\\*(aa -. if "\\$1"MirBSD" \&MirBSD\\*(aa -.\} -.. -.Sh NAME -.Nm ksh , -.Nm sh , -.Nm mksh -.Nd MirBSD Korn/Bourne shell -.Sh SYNOPSIS -.Nm -.Op Fl +abCefhiklmnprsuvxX -.Op Fl T Ar /dev/ttyCn -.Op Fl +o Ar option -.Xo -.Bk -words -.Oo Oo Fl c Ar command-string -.Op Ar command-name -.Li \&| Fl s -.Li \&| Ar file Oc -.Ek -.Op Ar argument ... Oc -.Xc -.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 C -shell language and compatible to the original Korn shell. -.Nm mksh -is derived from -.Nm pdksh , -the public domain Korn shell. -.Ss Shell startup -The following options can be specified only on the command line: -.Bl -tag -width Ds -.It Fl c Ar command-string -.Nm -will execute the command(s) contained in -.Ar command-string . -.It Fl i -Interactive mode; see below. -.It Fl l -Login shell; see below. -.It Fl s -The shell reads commands from standard input; all non-option arguments -are positional parameters. -.It Fl r -Restricted mode; see below. -.It Fl T Ar tty -Spawn -.Nm -on -.Xr tty 4 -device given. -Superuser only. -.El -.Pp -In addition to the above, the options described in the -.Ic set -built-in command can also be used on the command line. -.Pp -If neither the -.Fl c -nor the -.Fl s -option is specified, the first non-option argument specifies the name -of a file the shell reads commands from. -If there are no non-option -arguments, the shell reads commands from the standard input. -The name of -the shell (i.e., the contents of -.Ic $0 ) -is determined as follows: if the -.Fl c -option is used and there is a non-option argument, it is used as the name; -if commands are being read from a file, the file is used as the name; -otherwise, the name the shell was called with (i.e., -.Li argv[0] ) -is used. -.Pp -A shell is -.Dq interactive -if the -.Fl i -option is used or if both standard input and standard error are attached to a -.Xr tty 4 . -An interactive shell has job control enabled (if available), ignores the -.Dv SIGINT , -.Dv SIGQUIT -and -.Dv SIGTERM -signals and prints prompts before reading input (see -.Ev PS1 -and -.Ev PS2 -parameters). -For non-interactive shells, the -.Ic trackall -option is on by default (see the -.Ic set -command below). -.Pp -If the -.Fl T Ar ttydev -option is given, an interactive shell is spawned in the background -with the given device as controlling terminal. -The terminal devices are probed in the following order: -.Bl -enum -compact -.It -ttydev -.It -.Pa /dev/ttyC Ns ttydev -.It -.Pa /dev/tty Ns ttydev -.El -.Pp -A shell is -.Dq restricted -if the -.Fl r -option is used or if either the basename of the name the shell was invoked -with or the -.Ev SHELL -parameter match the pattern -.Dq \&*r\&*sh -(e.g., -.Dq rsh , -.Dq rmksh , -.Dq rpdksh , -etc.). -The following restrictions come into effect after the shell processes any -profile and -.Ev ENV -files: -.Pp -.Bl -bullet -compact -.It -The -.Ic cd -command is disabled. -.It -The -.Ev SHELL , -.Ev ENV -and -.Ev PATH -parameters cannot be changed. -.It -Command names can't be specified with absolute or relative paths. -.It -The -.Fl p -option of the built-in command -.Ic command -can't be used. -.It -Redirections that create files can't be used (i.e., -.Ql > , -.Ql >| , -.Ql >> , -.Ql <> ) . -.El -.Pp -A shell is -.Dq privileged -if the -.Fl p -option is used or if the real user ID or group ID does not match the -effective user ID or group ID (see -.Xr getuid 2 -and -.Xr getgid 2 ) . -A privileged shell does not process -.Pa $HOME/.profile -nor the -.Ev ENV -parameter (see below). -Instead, the file -.Pa /etc/suid_profile -is processed. -Clearing the privileged option causes the shell to set -its effective user ID (group ID) to its real user ID (group ID). -.Pp -If the basename of the name the shell is called with (i.e., -.Li argv[0] ) -starts with -.Ql - -or if the -.Fl l -option is used, -the shell is assumed to be a login shell and the shell reads and executes -the contents of -.Pa /etc/profile -and -.Pa $HOME/.profile -if they exist and are readable. -.Pp -If the -.Ev ENV -parameter is set when the shell starts (or, in the case of login shells, -after any profiles are processed), its value is subjected to parameter, -command, arithmetic and tilde -.Pq Sq \&~ -substitution and the resulting file -(if any) is read and executed. -If the -.Ev ENV -parameter is not set (and not -.Dv NULL ) -and -.Nm -was compiled with the non-default -.Dv DEFAULT_ENV -macro defined, the file named in that macro is included (after the above -mentioned substitutions have been performed). -.Pp -The exit status of the shell is 127 if the command file specified on the -command line could not be opened, or non-zero if a fatal syntax error -occurred during the execution of a script. -In the absence of fatal errors, -the exit status is that of the last command executed, or zero if no -command is executed. -.Ss Command syntax -The shell begins parsing its input by breaking it into -.Em words . -Words, which are sequences of characters, are delimited by unquoted whitespace -characters (space, tab and newline) or meta-characters -.Po -.Ql < , -.Ql > , -.Ql | , -.Ql \&; , -.Ql ( -and -.Ql \&) -.Pc . -Aside from delimiting words, spaces and tabs are ignored, while newlines -usually delimit commands. -The meta-characters are used in building the following tokens: -.Ql < , -.Ql <& , -.Ql << , -.Ql > , -.Ql >& , -.Ql >> , -etc. are used to specify redirections (see -.Sx Input/output redirection -below); -.Ql | -is used to create pipelines; -.Ql |& -is used to create co-processes (see -.Sx Co-processes -below); -.Ql \&; -is used to separate commands; -.Ql & -is used to create asynchronous pipelines; -.Ql && -and -.Ql || -are used to specify conditional execution; -.Ql \&;\&; -is used in -.Ic case -statements; -.Ql \&(\&( .. \&)\&) -is used in arithmetic expressions; -and lastly, -.Ql \&( .. \&) -is used to create subshells. -.Pp -Whitespace and meta-characters can be quoted individually using a backslash -.Pq Sq \e -or in groups using double -.Pq Sq \&" -or single -.Pq Sq \&' -quotes. -Note that the following characters are also treated specially by the -shell and must be quoted if they are to represent themselves: -.Ql \e , -.Ql \&" , -.Ql ' , -.Ql # , -.Ql $ , -.Ql ` , -.Ql ~ , -.Ql { , -.Ql } , -.Ql * , -.Ql \&? -and -.Ql [ . -The first three of these are the above mentioned quoting characters (see -.Sx Quoting -below); -.Ql # , -if used at the beginning of a word, introduces a comment \(em everything after -the -.Ql # -up to the nearest newline is ignored; -.Ql $ -is used to introduce parameter, command and arithmetic substitutions (see -.Sx Substitution -below); -.Ql ` -introduces an old-style command substitution (see -.Sx Substitution -below); -.Ql ~ -begins a directory expansion (see -.Sx Tilde expansion -below); -.Ql { -and -.Ql } -delimit -.Xr csh 1 -style alterations (see -.Sx Brace expansion -below); -and finally, -.Ql * , -.Ql \&? -and -.Ql [ -are used in file name generation (see -.Sx File name patterns -below). -.Pp -As words and tokens are parsed, the shell builds commands, of which there -are two basic types: -.Em simple-commands , -typically programs that are executed, and -.Em compound-commands , -such as -.Ic for -and -.Ic if -statements, grouping constructs and function definitions. -.Pp -A simple-command consists of some combination of parameter assignments -(see -.Sx Parameters -below), -input/output redirections (see -.Sx Input/output redirections -below) and command words; the only restriction is that -parameter assignments come before any command words. -The command words, if any, define the command -that is to be executed and its arguments. -The command may be a shell built-in -command, a function or an external command (i.e., a separate executable file -that is located using the -.Ev PATH -parameter (see -.Sx Command execution -below)). -Note that all command constructs have an exit status: for external commands, -this is related to the status returned by -.Xr wait 2 -(if the command could not be found, the exit status is 127; if it could not -be executed, the exit status is 126); the exit status of other command -constructs (built-in commands, functions, compound-commands, pipelines, lists, -etc.) are all well-defined and are described where the construct is -described. -The exit status of a command consisting only of parameter -assignments is that of the last command substitution performed during the -parameter assignment or 0 is there were no command substitutions. -.Pp -Commands can be chained together using the -.Ql | -token to form pipelines, in which the standard output of each command but the -last is piped (see -.Xr pipe 2 ) -to the standard input of the following command. -The exit status of a pipeline is that of its last command. -A pipeline may be prefixed by the -.Ql \&! -reserved word, which causes the exit status of the pipeline to be logically -complemented: if the original status was 0, the complemented status will be 1; -if the original status was not 0, the complemented status will be 0. -.Pp -.Em Lists -of commands can be created by separating pipelines by any of the following -tokens: -.Ql && , -.Ql || , -.Ql & , -.Ql |& -and -.Ql \&; . -The first two are for conditional execution: -.Dq Ar cmd1 No && Ar cmd2 -executes -.Ar cmd2 -only if the exit status of -.Ar cmd1 -is zero; -.Ql || -is the opposite \(em -.Ar cmd2 -is executed only if the exit status of -.Ar cmd1 -is non-zero. -.Ql && -and -.Ql || -have equal precedence which is higher than that of -.Ql & , -.Ql |& -and -.Ql \&; , -which also have equal precedence. -Note that the -.Ql && -and -.Ql || -operators are -.Qq left-associative . -For example, both of these commands will print only -.Qq bar : -.Bd -literal -offset indent -false && echo foo || echo bar -true || echo foo && echo bar -.Ed -.Pp -The -.Ql & -token causes the preceding command to be executed asynchronously; that is, -the shell starts the command but does not wait for it to complete (the shell -does keep track of the status of asynchronous commands, see -.Sx Job control -below). -When an asynchronous command is started when job control is disabled -(i.e., in most scripts), the command is started with signals -.Dv SIGINT -and -.Dv SIGQUIT -ignored and with input redirected from -.Pa /dev/null -(however, redirections specified in the asynchronous command have precedence). -The -.Ql |& -operator starts a co-process which is a special kind of asynchronous process -(see -.Sx Co-processes -below). -Note that a command must follow the -.Ql && -and -.Ql || -operators, while it need not follow -.Ql & , -.Ql |& -or -.Ql \&; . -The exit status of a list is that of the last command executed, with the -exception of asynchronous lists, for which the exit status is 0. -.Pp -Compound commands are created using the following reserved words. -These words -are only recognized if they are unquoted and if they are used as the first -word of a command (i.e., they can't be preceded by parameter assignments or -redirections): -.Bd -literal -offset center -case else function then ! -do esac if time [[ -done fi in until { -elif for select while } -.Ed -.Pp -.Sy Note: -Some shells (but not this one) execute control structure commands in a -subshell when one or more of their file descriptors are redirected, so any -environment changes inside them may fail. -To be portable, the -.Ic exec -statement should be used instead to redirect file descriptors before the -control structure. -.Pp -In the following compound command descriptions, command lists (denoted as -.Em list ) -that are followed by reserved words must end with a semicolon, a newline or -a (syntactically correct) reserved word. -For example, -.Bd -unfilled -offset indent -.Ic { echo foo; echo bar; } -.Ic { echo foo; echo bar } -.Ic { { echo foo; echo bar; } } -.Ed -.Pp -are all valid, but -.Bd -unfilled -offset indent -.Ic { echo foo; echo bar } -.Ed -.Pp -is not. -.Bl -tag -width Ds -.It Ic \&( Ar list Ic \&) -Execute -.Ar list -in a subshell. -There is no implicit way to pass environment changes from a -subshell back to its parent. -.It Ic \&{ Ar list Ic \&} -Compound construct; -.Ar list -is executed, but not in a subshell. -Note that -.Ic \&{ -and -.Ic \&} -are reserved words, not meta-characters. -.It Xo Ic case Ar word Ic in [ -.Ns [ Ic \&( ] Ar pattern [ -.Ns Ic \&| Ar pattern ] ... Ic \&) -.Ar list Ic \&;\&; -.Ns ] Ar ... -.Ic esac -.Xc -The -.Ic case -statement attempts to match -.Ar word -against the specified -.Ar pattern Ns s ; -the -.Ar list -associated with the first successfully matched pattern is executed. -Patterns used in -.Ic case -statements are the same as those used for file name patterns except that the -restrictions regarding -.Ql \&. -and -.Ql / -are dropped. -Note that any unquoted space before and after a pattern is -stripped; any space within a pattern must be quoted. -Both the word and the -patterns are subject to parameter, command and arithmetic substitution, as -well as tilde substitution. -For historical reasons, open and close braces may be used instead of -.Ic in -and -.Ic esac -(e.g., -.Ic case $foo { *) print bar; } ) . -The exit status of a -.Ic case -statement is that of the executed -.Ar list ; -if no -.Ar list -is executed, the exit status is zero. -.It Xo Ic for Ar name No [ -.Ic in Ar word ... term Ns ] -.Ic do Ar list Ic done -.Xc -For each -.Ar word -in the specified word list, the parameter -.Ar name -is set to the word and -.Ar list -is executed. -If -.Ic in -is not used to specify a word list, the positional parameters -.Po -.Ic $1 , $2 , -etc.\& -.Pc -are used instead. -For historical reasons, open and close braces may be used instead of -.Ic do -and -.Ic done -(e.g., -.Ic for i\&; { print $i; } ) . -The exit status of a -.Ic for -statement is the last exit status of -.Ar list ; -if -.Ar list -is never executed, the exit status is zero. -.Ar term -is either a newline or a -.Ql \&; . -.It Xo Ic if Ar list Ic then -.Ar list [ Ic elif Ar list Ic then -.Ar list ] Ar ... [ Ic else -.Ar list ] Ic fi -.Xc -If the exit status of the first -.Ar list -is zero, the second -.Ar list -is executed; otherwise, the -.Ar list -following the -.Ic elif , -if any, is executed with similar consequences. -If all the lists following the -.Ic if -and -.Ic elif Ns s -fail (i.e., exit with non-zero status), the -.Ar list -following the -.Ic else -is executed. -The exit status of an -.Ic if -statement is that of non-conditional -.Ar list -that is executed; if no non-conditional -.Ar list -is executed, the exit status is zero. -.It Xo Ic select Ar name No [ -.Ic in Ar word ... term Ns ] -.Ic do Ar list Ic done -.Xc -The -.Ic select -statement provides an automatic method of presenting the user with a menu and -selecting from it. -An enumerated list of the specified -.Ar word Ns s -is printed on standard error, followed by a prompt -.Po -.Ev PS3, normally -.Dq #?\ \& -.Pc . -A number corresponding to one of the enumerated words is then read from -standard input, -.Ar name -is set to the selected word (or unset if the selection is not valid), -.Ev REPLY -is set to what was read (leading/trailing space is stripped) and -.Ar list -is executed. -If a blank line (i.e., zero or more -.Ev IFS -characters) is entered, the menu is reprinted without executing -.Ar list . -When -.Ar list -completes, the enumerated list is printed if -.Ev REPLY -is -.Dv NULL , -the prompt is printed and so on. -This process continues until an end-of-file -is read, an interrupt is received or a -.Ic break -statement is executed inside the loop. -If -.Ic in Ar word ...\& -is omitted, the positional parameters are used (i.e., -.Ic $1 , $2 , -etc.). -For historical reasons, open and close braces may be used instead of -.Ic do -and -.Ic done -(e.g., -.Ic select i; { print $i; } ) . -The exit status of a -.Ic select -statement is zero if a -.Ic break -statement is used to exit the loop, non-zero otherwise. -.It Xo Ic until Ar list Ic do Ar list -.Ic done -.Xc -This works like -.Ic while , -except that the body is executed only while the exit status of the first -.Ar list -is non-zero. -.It Xo Ic while Ar list Ic do Ar list -.Ic done -.Xc -A -.Ic while -is a pre-checked loop. -Its body is executed as often as the exit status of the first -.Ar list -is zero. -The exit status of a -.Ic while -statement is the last exit status of the -.Ar list -in the body of the loop; if the body is not executed, the exit status is zero. -.It Xo Ic function Ar name Ic \&{ -.Ar list Ic \&} -.Xc -Defines the function -.Ar name -(see -.Sx Functions -below). -Note that redirections specified after a function definition are -performed whenever the function is executed, not when the function definition -is executed. -.It Ar name Ic () Ar command -Mostly the same as -.Ic function -(see -.Sx Functions -below). -.It Xo Ic time Op Fl p -.Op Ar pipeline -.Xc -The -.Ic time -reserved word is described in the -.Sx Command execution -section. -.It Ic (( Ar expression Ic )) -The arithmetic expression -.Ar expression -is evaluated; equivalent to -.Ic let Ar expression -(see -.Sx Arithmetic expressions -and the -.Ic let -command below). -.It Ic [[ Ar expression Ic ]] -Similar to the -.Ic test -and -.Ic \&[ Ar ... Ic \&] -commands (described later), with the following exceptions: -.Bl -bullet -offset indent -.It -Field splitting and file name generation are not performed on arguments. -.It -The -.Fl a -.Pq Tn AND -and -.Fl o -.Pq Tn OR -operators are replaced with -.Ql && -and -.Ql || , -respectively. -.It -Operators (e.g., -.Sq Fl f , -.Sq = , -.Sq \&! , -etc.) must be unquoted. -.It -The second operand of the -.Sq != -and -.Sq = -expressions are patterns (e.g., the comparison -.Ic [[ foobar = f*r ]] -succeeds). -.It -There are two additional binary operators: -.Ql < -and -.Ql > -which return true if their first string operand is less than, or greater than, -their second string operand, respectively. -.It -The single argument form of -.Ic test , -which tests if the argument has a non-zero length, is not valid; explicit -operators must always be used (e.g., instead of -.Ic \&[ Ar str Ic \&] -use -.Ic \&[[ Fl n Ar str Ic \&]] ) . -.It -Parameter, command and arithmetic substitutions are performed as expressions -are evaluated and lazy expression evaluation is used for the -.Ql && -and -.Ql || -operators. -This means that in the statement -.Pp -.Dl Ic "[[ -r foo && $(: , -the sequence of characters -.Dq A:B::D -contains four fields: -.Sq A , -.Sq B , -.Sq -(an empty field) and -.Sq D . -Note that if the -.Ev IFS -parameter is set to the null -string, no field splitting is done; if the parameter is unset, the default -value of space, tab and newline is used. -.Pp -The results of substitution are, unless otherwise specified, also subject to -brace expansion and file name expansion (see the relevant sections below). -.Pp -A command substitution is replaced by the output generated by the specified -command, which is run in a subshell. -For -.Ic $( Ns Ar command Ns Ic \&) -substitutions, normal quoting rules are used when -.Ar command -is parsed; however, for the -.Ic ` Ns Ar command Ns Ic ` -form, a -.Ql \e -followed by any of -.Ql $ , -.Ql ` -or -.Ql \e -is stripped (a -.Ql \e -followed by any other character is unchanged). -As a special case in command substitutions, a command of the form -.Ic \&< Ar file -is interpreted to mean substitute the contents of -.Ar file -(note that -.Ic $(< foo) -has the same effect as -.Ic $(cat foo) , -but it is carried out more efficiently because no process is started). -.Pp -.Sy Note: -.Ic $( Ns Ar command Ns Ic \&) -expressions are currently parsed by finding the matching parenthesis, -regardless of quoting. -This should be fixed soon. -.Pp -Arithmetic substitutions are replaced by the value of the specified expression. -For example, the command -.Ic print $((2+3*4)) -displays 14. -See -.Sx Arithmetic expressions -for a description of an expression. -.Ss Parameters -Parameters are shell variables; they can be assigned values and their values -can be accessed using a parameter substitution. -A parameter name is either one -of the special single punctuation or digit character parameters described -below or a letter followed by zero or more letters or digits -.Po -.Ql _ -counts as a letter -.Pc . -The latter form can be treated as arrays by appending an array index of the -form -.Op Ar expr -where -.Ar expr -is an arithmetic expression. -Array indices are currently limited to the range 0 through 2147483647 (for -.Nm mksh ) -or 0 to 1023 (for other korn shells), inclusive. -Parameter substitutions take the form -.Ic $ Ns Ar name , -.Ic ${ Ns Ar name Ns Ic \&} -or -.Sm off -.Xo -.Ic ${ Ar name Oo Ar expr Oc -.Ic \&} , -.Xc -.Sm on -where -.Ar name -is a parameter name. -If substitution is performed on a parameter -(or an array parameter element) -that is not set, a null string is substituted unless the -.Ic nounset -option -.Po -.Ic set Fl o Ic nounset -or -.Ic set Fl u -.Pc -is set, in which case an error occurs. -.Pp -Parameters can be assigned values in a number of ways. -First, the shell implicitly sets some parameters like -.Ic # , PWD , -etc.; this is the only way the special single character parameters are set. -Second, parameters are imported from the shell's environment at startup. -Third, parameters can be assigned values on the command line, for example, -.Ic FOO=bar -sets the parameter -.Ev FOO -to -.Dq bar ; -multiple parameter assignments can be given on a single command line and they -can be followed by a simple-command, in which case the assignments are in -effect only for the duration of the command (such assignments are also -exported, see below for implications of this). -Note that both the parameter name and the -.Ql = -must be unquoted for the shell to recognize a parameter assignment. -The fourth way of setting a parameter is with the -.Ic export , -.Ic readonly -and -.Ic typeset -commands; see their descriptions in the -.Sx Command execution -section. -Fifth, -.Ic for -and -.Ic select -loops set parameters as well as the -.Ic getopts , -.Ic read -and -.Ic set Fl A -commands. -Lastly, parameters can be assigned values using assignment operators -inside arithmetic expressions (see -.Sx Arithmetic expressions -below) or using the -.Xo Ic ${ Ns Ar name Ns = -.Ns Ar value Ns Ic \&} -.Xc -form of the parameter substitution (see below). -.Pp -Parameters with the export attribute (set using the -.Ic export -or -.Ic typeset Fl x -commands or by parameter assignments followed by simple commands) are put in -the environment (see -.Xr environ 7 ) -of commands run by the shell as -.Ar name Ns = Ns Ar value -pairs. -The order in which parameters appear in the environment of a command is -unspecified. -When the shell starts up, it extracts parameters and their values -from its environment and automatically sets the export attribute for those -parameters. -.Pp -Modifiers can be applied to the -.Ic ${ Ns Ar name Ns Ic \&} -form of parameter substitution: -.Bl -tag -width Ds -.It Xo Ic ${ Ns Ar name Ns -.Ic \&:\&- Ns Ar word Ns Ic \&} -.Xc -If -.Ar name -is set and not -.Dv NULL , -it is substituted; otherwise, -.Ar word -is substituted. -.It Xo Ic ${ Ns Ar name Ns -.Ic \&:\&+ Ns Ar word Ns Ic \&} -.Xc -If -.Ar name -is set and not -.Dv NULL , -.Ar word -is substituted; otherwise, nothing is substituted. -.It Xo Ic ${ Ns Ar name Ns -.Ic \&:\&= Ns Ar word Ns Ic \&} -.Xc -If -.Ar name -is set and not -.Dv NULL , -it is substituted; otherwise, it is assigned -.Ar word -and the resulting value of -.Ar name -is substituted. -.It Xo Ic ${ Ns Ar name Ns -.Ic \&:\&? Ns Ar word Ns Ic \&} -.Xc -If -.Ar name -is set and not -.Dv NULL , -it is substituted; otherwise, -.Ar word -is printed on standard error (preceded by -.Ar name Ns \&: ) -and an error occurs (normally causing termination of a shell script, function -or .-script). -If word is omitted the string -.Dq parameter null or not set -is used instead. -.El -.Pp -In the above modifiers, the -.Ql \&: -can be omitted, in which case the conditions only depend on -.Ar name -being set (as opposed to set and not -.Dv NULL ) . -If -.Ar word -is needed, parameter, command, arithmetic and tilde substitution are performed -on it; if -.Ar word -is not needed, it is not evaluated. -.Pp -The following forms of parameter substitution can also be used: -.Bl -tag -width Ds -.It Ic ${# Ns Ar name Ns Ic \&} -The number of positional parameters if -.Ar name -is -.Ql * , -.Ql @ , -not specified, or the length of the string value of parameter -.Ar name . -.It Xo Ic ${# Ns Ar name Ns -.Ic [*\&]} , ${# Ns Ar name Ns Ic [@\&]} -.Xc -The number of elements in the array -.Ar name . -.Sm off -.It Xo -.Ic ${ Ar name Ic # Ar pattern -.Sm on -.Ic } , -.Sm off -.Ic ${ Ar name Ic ## Ar pattern Ic \&} -.Xc -.Sm on -If -.Ar pattern -matches the beginning of the value of parameter -.Ar name , -the matched text is deleted from the result of substitution. -A single -.Ql # -results in the shortest match, two of them result in the longest match. -.Sm off -.It Xo -.Ic ${ Ar name Ic % Ar pattern -.Sm on -.Ic } , -.Sm off -.Ic ${ Ar name Ic %% Ar pattern Ic } -.Xc -.Sm on -Like -.Ic ${..#..} -substitution, but it deletes from the end of the value. -.El -.Pp -The following special parameters are implicitly set by the shell and cannot be -set directly using assignments: -.Bl -tag -width "1 ... 9" -.It Ev \&! -Process ID of the last background process started. -If no background processes have been started, the parameter is not set. -.It Ev \&# -The number of positional parameters (i.e., -.Ic $1 , $2 , -etc.). -.It Ev \&$ -The process ID of the shell, or the -.Tn PID -of the original shell if it is a subshell. -Do -.Em NOT -use this mechanism for generating temporary file names; see -.Xr mktemp 1 -instead. -.It Ev \&- -The concatenation of the current single letter options (see the -.Ic set -command below for a list of options). -.It Ev \&? -The exit status of the last non-asynchronous command executed. -If the last command was killed by a signal, -.Va \&$?\& -is set to 128 plus the signal number. -.It Ev 0 -The name the shell was invoked with (i.e., -.Li argv[0] ) -or the -.Ar command-name -if it was invoked with the -.Fl c -option and the -.Ar command-name -was supplied, or the -.Ar file -argument, if it was supplied. -If the -.Ic posix -option is not set, -.Ic \&$0 -is the name of the current function or script. -.It Ev 1 ... Ev 9 -The first nine positional parameters that were supplied to the shell, function -or .-script. -Further positional parameters may be accessed using -.Ic ${ Ns Ar number Ns Ic \&} . -.It Ev \&* -All positional parameters (except parameter 0); i.e., -.Ic $1 , $2 , $3 , -\&... -If used -outside of double quotes, parameters are separate words (which are subjected -to word splitting); if used within double quotes, parameters are separated -by the first character of the -.Ev IFS -parameter (or the empty string if -.Ev IFS -is -.Dv NULL ) . -.It Ev \&@ -Same as -.Ic \&$\&* , -unless it is used inside double quotes, in which case a separate word is -generated for each positional parameter. -If there are no positional parameters, no word is generated. -.Ic \&$\&@ -can be used to access arguments, verbatim, without losing -.Dv NULL -arguments or splitting arguments with spaces. -.El -.Pp -The following parameters are set and/or used by the shell: -.Bl -tag -width "EXECSHELL" -.It Ev \&_ No (underscore) -When an external command is executed by the shell, this parameter is set in the -environment of the new process to the path of the executed command. -In interactive use, this parameter is also set in the parent shell to the last -word of the previous command. -.It Ev CDPATH -Search path for the -.Ic cd -built-in command. -Works the same way as -.Ev PATH -for those directories not beginning with -.Ql / -in -.Ic cd -commands. -Note that if -.Ev CDPATH -is set and does not contain -.Dq \&. -or contains an empty path, the current directory is not searched. -Also, the -.Ic cd -built-in command will display the resulting directory when a match is found -in any search path other than the empty path. -.It Ev COLUMNS -Set to the number of columns on the terminal or window. -Currently set to the -.Dq cols -value as reported by -.Xr stty 1 -if that value is non-zero. -This parameter is used by the -interactive line editing modes and by the -.Ic select , -.Ic set Fl o -and -.Ic kill Fl l -commands to format information columns. -.It Ev EDITOR -If the -.Ev VISUAL -parameter is not set, this parameter controls the command-line editing mode for -interactive shells. -See -.Ev VISUAL -parameter below for how this works. -.It Ev ENV -If this parameter is found to be set after any profile files are executed, the -expanded value is used as a shell startup file. -It typically contains function and alias definitions. -.It Ev ERRNO -Integer value of the shell's -.Va errno -variable. -It indicates the reason the last system call failed. -Not yet implemented. -.It Ev EXECSHELL -If set, this parameter is assumed to contain the shell that is to be used to -execute commands that -.Xr execve 2 -fails to execute and which do not start with a -.Dq \&#\&! Ns Ar shell -sequence. -.It Ev FCEDIT -The editor used by the -.Ic fc -command (see below). -.It Ev FPATH -Like -.Ev PATH , -but used when an undefined function is executed to locate the file defining the -function. -It is also searched when a command can't be found using -.Ev PATH . -See -.Sx Functions -below for more information. -.It Ev HISTFILE -The name of the file used to store command history. -When assigned to, history is loaded from the specified file. -Also, several invocations of the shell -running on the same machine will share history if their -.Ev HISTFILE -parameters all point to the same file. -.Pp -.Sy Note: -If -.Ev HISTFILE -isn't set, no history file is used. -This is different from the original Korn shell. -.It Ev HISTSIZE -The number of commands normally stored for history. -The default is 511. -.It Ev HOME -The default directory for the -.Ic cd -command and the value substituted for an unqualified -.Ic ~ -(see -.Sx Tilde expansion -below). -.It Ev IFS -Internal field separator, used during substitution and by the -.Ic read -command, to split values into distinct arguments; normally set to space, tab -and newline. -See -.Sx Substitution -above for details. -.Pp -.Sy Note: -This parameter is not imported from the environment when the shell is -started. -.It Ev KSH_VERSION -The version of -.Nm -(read-only). -See also the version commands in -.Sx Emacs editing mode -and -.Sx Vi editing mode -sections, below. -.It Ev LINENO -The line number of the function or shell script that is currently being -executed. -.It Ev LINES -Set to the number of lines on the terminal or window. -Not yet implemented. -.It Ev OLDPWD -The previous working directory. -Unset if -.Ic cd -has not successfully changed directories since the shell started or the -shell doesn't know where it is. -.It Ev OPTARG -When using -.Ic getopts , -it contains the argument for a parsed option, if it requires one. -.It Ev OPTIND -The index of the last argument processed when using -.Ic getopts . -Assigning 1 to this parameter causes -.Ic getopts -to process arguments from the beginning the next time it is invoked. -.It Ev PATH -A colon separated list of directories that are searched when looking for -commands and .'d files. -An empty string resulting from a leading or trailing -colon, or two adjacent colons, is treated as a -.Dq \&. , -the current directory. -This is dangerous. -.It Ev PGRP -The current process group, see -.Xr getpgrp 2 . -.It Ev POSIXLY_CORRECT -If set, this parameter causes the -.Ic posix -option to be enabled. -See -.Sx POSIX mode -below. -.It Ev PPID -The process ID of the shell's parent (read-only). -.It Ev PS1 -The primary prompt for interactive shells. -Parameter, command and arithmetic substitutions are performed, and -.Ql \&! -is replaced with the current command number (see the -.Ic fc -command below). -A literal -.Ql \&! -can be put in the prompt by placing -.Ql !! -in -.Ev PS1 . -Note that since the command-line editors try to figure out how long the prompt -is (so they know how far it is to the edge of the screen), escape codes in -the prompt tend to mess things up. -You can tell the shell not to count certain -sequences (such as escape codes) by prefixing your prompt with a non-printing -character (such as control-A) followed by a carriage return and then delimiting -the escape codes with this non-printing character. -If you don't have any non-printing characters, you're out of luck. -By the way, don't blame me for -this hack; it's in the original -.Xr ksh88 1 . -Default is -.Dq \&$\ \& -for non-root users, -.Dq \&#\ \& -for root. -.Pp -Since Backslashes and other special characters may be -interpreted by the shell, to set -.Ev PS1 -either escape the backslash itself or use double quotes. -The latter is more practical. -This is a more complex example which embeds the current working -directory, in reverse video, into the prompt string, avoiding to -directly enter special characters (for example with -.Ic ^^ -in the emacs editing mode): -.Bd -literal -offset indent -x=$(print \e\e001) -PS1="$x$(print \e\er)$x$(tput so)$x\e$PWD$x$(tput se)$x> " -.Ed -.It Ev PS2 -Secondary prompt string, by default -.Dq \&>\ \& , -used when more input is needed to complete a command. -.It Ev PS3 -Prompt used by the -.Ic select -statement when reading a menu selection. -Default is -.Dq \&#\&?\ \& . -.It Ev PS4 -Used to prefix commands that are printed during execution tracing (see the -.Ic set Fl x -command below). -Parameter, command and arithmetic substitutions are performed -before it is printed. -Default is -.Dq \&+\ \& . -.It Ev PWD -The current working directory. -May be unset or -.Dv NULL -if the shell doesn't know where it is. -.It Ev RANDOM -A pseudo-random number generator. -Every time -.Ev RANDOM -is referenced, it is assigned a random number in the range -0\-32767. -Until the variable is written to, the -.Xr arc4random 3 -function is being used, after a write or if the function is not available, -.Xr random 3 -or, if that does not exist, -.Xr rand 3 , -is being used. -On startup and fork, the value is seeded and the initial state of -not being written to initialised. -If a feedback function is provided, changed values are propagated -back to the arcfour random number generator. -.It Ev REPLY -Default parameter for the -.Ic read -command if no names are given. -Also used in -.Ic select -loops to store the value that is read from standard input. -.It Ev SECONDS -The number of seconds since the shell started or, if the parameter has been -assigned an integer value, the number of seconds since the assignment plus the -value that was assigned. -.It Ev TMOUT -If set to a positive integer in an interactive shell, it specifies the maximum -number of seconds the shell will wait for input after printing the primary -prompt -.Pq Ev PS1 . -If the time is exceeded, the shell exits. -.It Ev TMPDIR -The directory shell temporary files are created in. -If this parameter is not -set or does not contain the absolute path of a writable directory, temporary -files are created in -.Pa /tmp . -.It Ev VISUAL -If set, this parameter controls the command-line editing mode for interactive -shells. -If the last component of the path specified in this parameter contains -the string -.Dq vi , -.Dq emacs -or -.Dq gmacs , -the -.Xr vi , -.Xr emacs -or -.Xr gmacs -(Gosling emacs) editing mode is enabled, respectively. -.El -.Ss Tilde expansion -Tilde expansion, which is done in parallel with parameter substitution, is done -on words starting with an unquoted -.Ql ~ . -The characters following the tilde, up to the first -.Ql / , -if any, are assumed to be a login name. -If the login name is empty, -.Ql + -or -.Ql - , -the value of the -.Ev HOME , -.Ev PWD -or -.Ev OLDPWD -parameter is substituted, respectively. -Otherwise, the password file is -searched for the login name, and the tilde expression is substituted with the -user's home directory. -If the login name is not found in the password file or -if any quoting or parameter substitution occurs in the login name, no -substitution is performed. -.Pp -In parameter assignments (those preceding a simple-command or those occurring -in the arguments of -.Ic alias , -.Ic export , -.Ic readonly -and -.Ic typeset ) , -tilde expansion is done after any unquoted colon -.Pq Sq \&: , -and login names are also delimited by colons. -.Pp -The home directory of previously expanded login names are cached and re-used. -The -.Ic alias -d -command may be used to list, change and add to this cache (e.g., -.Ic alias -d fac=/usr/local/facilities; cd ~fac/bin ) . -.Ss Brace expansion (alteration) -Brace expressions, which take the form -.Bd -unfilled -offset indent -.Sm off -.Xo Ar prefix Ic { Ar str No 1,..., -.Ar str No N Ic } Ar suffix -.Xc -.Sm on -.Ed -.Pp -are expanded to N words, each of which is the concatenation of -.Ar prefix , -.Ar str Ns i -and -.Ar suffix -(e.g., -.Dq a{c,b{X,Y},d}e -expands to four words: -.Dq ace , -.Dq abXe , -.Dq abYe -and -.Dq ade ) . -As noted in the example, brace expressions can be nested and the resulting -words are not sorted. -Brace expressions must contain an unquoted comma -.Pq Sq \&, -for expansion to occur (i.e., -.Ic {} -and -.Ic {foo} -are not expanded). -Brace expansion is carried out after parameter substitution -and before file name generation. -.Ss File name patterns -A file name pattern is a word containing one or more unquoted -.Ql \&? -or -.Ql * -characters or -.Dq [..] -sequences. -Once brace expansion has been performed, the shell replaces file -name patterns with the sorted names of all the files that match the pattern -(if no files match, the word is left unchanged). -The pattern elements have the following meaning: -.Bl -tag -width Ds -.It Ic \&? -Matches any single character. -.It Ic \&* -Matches any sequence of characters. -.It Ic \&[ Ns .. Ns Ic \&] -Matches any of the characters inside the brackets. -Ranges of characters can be -specified by separating two characters by a -.Ql - -(e.g., -.Dq [a0-9] -matches the letter -.Dq a -or any digit). -In order to represent itself, a -.Ql - -must either be quoted or the first or last character in the character list. -Similarly, a -.Ql \&] -must be quoted or the first character in the list if it is to represent itself -instead of the end of the list. -Also, a -.Ql \&! -appearing at the start of the list has special meaning (see below), so to -represent itself it must be quoted or appear later in the list. -.It Ic \&[\&! Ns .. Ns Ic \&] -Like -.Ic \&[ Ns .. Ns Ic \&] , -except it matches any character not inside the brackets. -.Sm off -.It Xo Ic \&*( Ar pattern Ic \&| No \ ...\ \& -.Ic \&| Ar pattern Ic \&) -.Xc -.Sm on -Matches any string of characters that matches zero or more occurrences of the -specified patterns. -Example: The pattern -.Ic \&*(foo\&|bar) -matches the strings -.Dq , -.Dq foo , -.Dq bar , -.Dq foobarfoo , -etc. -.Sm off -.It Xo Ic \&+( Ar pattern Ic \&| No \ ...\ \& -.Ic \&| Ar pattern Ic \&) -.Xc -.Sm on -Matches any string of characters that matches one or more occurrences of the -specified patterns. -Example: The pattern -.Ic \&+(foo\&|bar) -matches the strings -.Dq foo , -.Dq bar , -.Dq foobar , -etc. -.Sm off -.It Xo Ic \&?( Ar pattern Ic \&| No \ ...\ \& -.Ic \&| Ar pattern Ic \&) -.Xc -.Sm on -Matches the empty string or a string that matches one of the specified -patterns. -Example: The pattern -.Ic \&?(foo\&|bar) -only matches the strings -.Dq , -.Dq foo -and -.Dq bar . -.Sm off -.It Xo Ic \&@( Ar pattern Ic \&| No \ ...\ \& -.Ic \&| Ar pattern Ic \&) -.Xc -.Sm on -Matches a string that matches one of the specified patterns. -Example: The pattern -.Ic \&@(foo\&|bar) -only matches the strings -.Dq foo -and -.Dq bar . -.Sm off -.It Xo Ic \&!( Ar pattern Ic \&| No \ ...\ \& -.Ic \&| Ar pattern Ic \&) -.Xc -.Sm on -Matches any string that does not match one of the specified patterns. -Examples: The pattern -.Ic \&!(foo\&|bar) -matches all strings except -.Dq foo -and -.Dq bar ; -the pattern -.Ic \&!(\&*) -matches no strings; the pattern -.Ic \&!(\&?)\&* -matches all strings (think about it). -.El -.Pp -Note that -.Nm -currently never matches -.Dq \&. -and -.Dq \&.\&. , -but the original -.Xr ksh , -Bourne -.Xr sh -and GNU -.Xr bash -do. -.Pp -Note that none of the above pattern elements match either a period -.Pq Sq \&. -at the start of a file name or a slash -.Pq Sq / , -even if they are explicitly used in a -.Ic \&[ Ns .. Ns Ic \&] -sequence; also, the names -.Dq \&. -and -.Dq \&.\&. -are never matched, even by the pattern -.Dq \&.\&* . -.Pp -If the -.Ic markdirs -option is set, any directories that result from file name generation are marked -with a trailing -.Ql / . -.Pp -The -.Tn POSIX -character classes (i.e., -.Ic \&[\&: Ns Ar class-name Ns Ic \&:\&] -inside a -.Ic \&[ Ns .. Ns Ic \&] -expression) are not yet implemented. -.Ss Input/output redirection -When a command is executed, its standard input, standard output and standard -error (file descriptors 0, 1 and 2, respectively) are normally inherited from -the shell. -Three exceptions to this are commands in pipelines, for which -standard input and/or standard output are those set up by the pipeline, -asynchronous commands created when job control is disabled, for which standard -input is initially set to be from -.Pa /dev/null , -and commands for which any of the following redirections have been specified: -.Bl -tag -width Ds -.It Ic \&> Ar file -Standard output is redirected to -.Ar file . -If -.Ar file -does not exist, it is created; if it does exist, is a regular file and the -.Ic noclobber -option is set, an error occurs; otherwise, the file is truncated. -Note that this means the command -.Ic cmd < foo > foo -will open -.Ar foo -for reading and then truncate it when it opens it for writing, before -.Ar cmd -gets a chance to actually read -.Ar foo . -.It Ic \&>\&| Ar file -Same as -.Ic \&> , -except the file is truncated, even if the -.Ic noclobber -option is set. -.It Ic \&>\&> Ar file -Same as -.Ic \&> , -except if -.Ar file -exists it is appended to instead of being truncated. -Also, the file is opened -in append mode, so writes always go to the end of the file (see -.Xr open 2 ) . -.It Ic \&< Ar file -Standard input is redirected from -.Ar file , -which is opened for reading. -.It Ic \&<\&> Ar file -Same as -.Ic \&< , -except the file is opened for reading and writing. -.It Ic \&<\&< Ar marker -After reading the command line containing this kind of redirection (called a -.Dq here document ) , -the shell copies lines from the command source into a temporary file until a -line matching -.Ar marker -is read. -When the command is executed, standard input is redirected from the -temporary file. -If -.Ar marker -contains no quoted characters, the contents of the temporary file are processed -as if enclosed in double quotes each time the command is executed, so -parameter, command and arithmetic substitutions are performed, along with -backslash -.Pq Sq \e -escapes for -.Ql $ , -.Ql ` , -.Ql \e -and -.Ql \enewline . -If multiple here documents are used on the same command line, they are saved in -order. -.It Ic \&<\&<\&- Ar marker -Same as -.Ic \&<\&< , -except leading tabs are stripped from lines in the here document. -.It Ic \&<\&& Ar fd -Standard input is duplicated from file descriptor -.Ar fd . -.Ar fd -can be a single digit, indicating the number of an existing file descriptor; -the letter -.Ql p , -indicating the file descriptor associated with the output of the current -co-process; or the character -.Ql - , -indicating standard input is to be closed. -.It Ic \&>\&& Ar fd -Same as -.Ic \&<\&& , -except the operation is done on standard output. -.El -.Pp -In any of the above redirections, the file descriptor that is redirected (i.e., -standard input or standard output) can be explicitly given by preceding the -redirection with a single digit. -Parameter, command and arithmetic -substitutions, tilde substitutions and, if the shell is interactive, -file name generation are all performed on the -.Ar file , -.Ar marker -and -.Ar fd -arguments of redirections. -Note, however, that the results of any file name -generation are only used if a single file is matched; if multiple files match, -the word with the expanded file name generation characters is used. -Note -that in restricted shells, redirections which can create files cannot be used. -.Pp -For simple-commands, redirections may appear anywhere in the command; for -compound-commands -.Po -.Ic if -statements, etc. -.Pc , -any redirections must appear at the end. -Redirections are processed after -pipelines are created and in the order they are given, so -.Pp -.Dl Ic cat /foo/bar 2\*(Gt&1 \*(Gt /dev/null \&| cat -n -.Pp -will print an error with a line number prepended to it. -.Ss Arithmetic expressions -Integer arithmetic expressions can be used with the -.Ic let -command, inside -.Ic $(( Ns .. Ns Ic )) -expressions, inside array references (e.g., -.Sm off -.Ar name Ic \&[ Ar expr Ic \&] ) , -.Sm on -as numeric arguments to the -.Ic test -command and as the value of an assignment to an integer parameter. -.Pp -Expressions may contain alpha-numeric parameter identifiers, array references -and integer constants and may be combined with the following C operators -(listed and grouped in increasing order of precedence): -.Pp -Unary operators: -.Bl -item -offset indent -compact -.It -.Ic \&+ \&- \&! \&~ \&+\&+ \&-\&- -.El -.Pp -Binary operators: -.Bl -item -offset indent -compact -.It -.Ic \&, -.It -.Ic = \&*= /= %= \&+= \&-= \&<\&<= -.Ic \&>\&>= \&&= ^= \&|= -.It -.Ic \&|\&| -.It -.Ic \&&\&& -.It -.Ic \&| -.It -.Ic ^ -.It -.Ic \&& -.It -.Ic == \&!= -.It -.Ic \&< \&<= \&>= \&> -.It -.Ic \&<\&< \&>\&> -.It -.Ic \&+ \&- -.It -.Ic \&* / % -.El -.Pp -Ternary operators: -.Bl -item -offset indent -compact -.It -.Ic \&?\&: -(precedence is immediately higher than assignment) -.El -.Pp -Grouping operators: -.Bl -item -offset indent -compact -.It -.Ic \&( \&) -.El -.Pp -Integer constants may be specified with arbitrary bases using the notation -.Ar base Ns Ic \&# Ns Ar number , -where -.Ar base -is a decimal integer specifying the base and -.Ar number -is a number in the specified base. -.Pp -The operators are evaluated as follows: -.Bl -tag -width Ds -offset indent -.It unary Ic \&+ -Result is the argument (included for completeness). -.It unary Ic \&- -Negation. -.It Ic \&! -Logical -.Tn NOT ; -the result is 1 if argument is zero, 0 if not. -.It Ic \&~ -Arithmetic (bit-wise) -.Tn NOT . -.It Ic \&+\&+ -Increment; must be applied to a parameter (not a literal or other expression). -The parameter is incremented by 1. -When used as a prefix operator, the result -is the incremented value of the parameter; when used as a postfix operator, the -result is the original value of the parameter. -.It Ic \&-\&- -Similar to -.Ic \&+\&+ , -except the parameter is decremented by 1. -.It Ic \&, -Separates two arithmetic expressions; the left-hand side is evaluated first, -then the right. -The result is the value of the expression on the right-hand side. -.It Ic = -Assignment; variable on the left is set to the value on the right. -.It Xo Ic \&*= /= \&+= \&-= \&<\&<= -.Ic \&>\&>= \&&= ^= \&|= -.Xc -Assignment operators. -.Ao Ar var Ac -.Ao Ar op Ac = -.Ao Ar expr Ac -is the same as -.Ao Ar var Ac = -.Ao Ar var Ac -.Ao Ar op Ac -.Ic \&( -.Ao Ar expr Ac -.Ic \&) . -.It Ic \&|\&| -Logical -.Tn OR ; -the result is 1 if either argument is non-zero, 0 if not. -The right argument is evaluated only if the left argument is zero. -.It Ic \&&\&& -Logical -.Tn AND ; -the result is 1 if both arguments are non-zero, 0 if not. -The right argument is evaluated only if the left argument is non-zero. -.It Ic \&| -Arithmetic (bit-wise) -.Tn OR . -.It Ic ^ -Arithmetic (bit-wise) -.Tn XOR -(exclusive-OR). -.It Ic \&& -Arithmetic (bit-wise) -.Tn AND . -.It Ic == -Equal; the result is 1 if both arguments are equal, 0 if not. -.It Ic \&!= -Not equal; the result is 0 if both arguments are equal, 1 if not. -.It Ic \&< -Less than; the result is 1 if the left argument is less than the right, 0 if -not. -.It Ic \&<= \&>= \&> -Less than or equal, greater than or equal, greater than. -See -.Ic \&< . -.It Ic \&<\&< \&>\&> -Shift left (right); the result is the left argument with its bits shifted left -(right) by the amount given in the right argument. -.It Ic \&+ \&- \&* / -Addition, subtraction, multiplication and division. -.It Ic % -Remainder; the result is the remainder of the division of the left argument by -the right. -The sign of the result is unspecified if either argument is negative. -.It Xo Ao Ar arg1 Ac Ic \ \&? -.Ao Ar arg2 Ac Ic \ \&: Ao Ar arg3 Ac -.Xc -.No If Ao Ar arg1 Ac -is non-zero, the result is -.Ao Ar arg2 Ac , -otherwise -.Ao Ar arg3 Ac . -.El -.Ss Co-processes -A co-process, which is a pipeline created with the -.Ic \&|\&& -operator, is an asynchronous process that the shell can both write to (using -.Ic print -p ) -and read from (using -.Ic read -p ) . -The input and output of the co-process can also be manipulated using -.Ic \&>\&&p -and -.Ic \&<\&&p -redirections, respectively. -Once a co-process has been started, another can't -be started until the co-process exits or the co-process's input has been -redirected using an -.Ic exec Ar n Ns Ic \&>\&&p -redirection. -If a co-process's input is redirected in this way, the next -co-process to be started will share the output with the first co-process, -unless the output of the initial co-process has been redirected using an -.Ic exec Ar n Ns Ic \&<\&&p -redirection. -.Pp -Some notes concerning co-processes: -.Bl -bullet -.It -The only way to close the co-process's input (so the co-process reads an -end-of-file) is to redirect the input to a numbered file descriptor and then -close that file descriptor (e.g., -.Ic exec 3\&>\&&p\&; exec 3\&>\&&\&- ) . -.It -In order for co-processes to share a common output, the shell must keep the -write portion of the output pipe open. -This means that end-of-file will not be -detected until all co-processes sharing the co-process's output have exited -(when they all exit, the shell closes its copy of the pipe). -This can be -avoided by redirecting the output to a numbered file descriptor (as this also -causes the shell to close its copy). -Note that this behaviour is slightly -different from the original Korn shell which closes its copy of the write -portion of the co-process output when the most recently started co-process -(instead of when all sharing co-processes) exits. -.It -.Ic print -p -will ignore -.Dv SIGPIPE -signals during writes if the signal is not being trapped or ignored; the same -is true if the co-process input has been duplicated to another file descriptor -and -.Ic print -u Ns Ar n -is used. -.El -.Ss Functions -Functions are defined using either Korn shell -.Ic function Ar name -syntax or the Bourne/POSIX shell -.Fn name -syntax (see below for the difference between the two forms). -Functions are like -.Li .-scripts -in that they are executed in the current environment. -However, unlike -.Li .-scripts , -shell arguments (i.e., positional parameters -.Ic $1 , $2 , -etc.) are never visible inside them. -When the shell is determining the location of a command, functions -are searched after special built-in commands, before regular and -non-regular built-ins and before the -.Ev PATH -is searched. -.Pp -An existing function may be deleted using -.Ic unset Fl f Ar function-name . -A list of functions can be obtained using -.Ic typeset \&+f -and the function definitions can be listed using -.Ic typeset \&-f . -The -.Ic autoload -command (which is an alias for -.Ic typeset \&-fu ) -may be used to create undefined functions; when an undefined function is -executed, the shell searches the path specified in the -.Ev FPATH -parameter for a file with the same name as the function, which, if found, is -read and executed. -If after executing the file the named function is found to -be defined, the function is executed; otherwise, the normal command search is -continued (i.e., the shell searches the regular built-in command table and -.Ev PATH ) . -Note that if a command is not found using -.Ev PATH , -an attempt is made to autoload a function using -.Ev FPATH -(this is an undocumented feature of the original Korn shell). -.Pp -Functions can have two attributes, -.Dq trace -and -.Dq export , -which can be set with -.Ic typeset \&-ft -and -.Ic typeset \&-fx , -respectively. -When a traced function is executed, the shell's -.Ic xtrace -option is turned on for the function's duration; otherwise, the -.Ic xtrace -option is turned off. -The -.Dq export -attribute of functions is currently not used. -In the original Korn shell, -exported functions are visible to shell scripts that are executed. -.Pp -Since functions are executed in the current shell environment, parameter -assignments made inside functions are visible after the function completes. -If this is not the desired effect, the -.Ic typeset -command can be used inside a function to create a local parameter. -Note that special parameters (e.g. -.Ic \&$$ , $\&! ) -can't be scoped in this way. -.Pp -The exit status of a function is that of the last command executed in the -function. -A function can be made to finish immediately using the -.Ic return -command; this may also be used to explicitly specify the exit status. -.Pp -Functions defined with the -.Ic function -reserved word are treated differently in the following ways from functions -defined with the -.Ic \&(\&) -notation: -.Bl -bullet -.It -The -.Ic $0 -parameter is set to the name of the function (Bourne-style functions leave -.Ic $0 -untouched). -.It -Parameter assignments preceding function calls are not kept in the shell -environment (executing Bourne-style functions will keep assignments). -.It -.Ev OPTIND -is saved/reset and restored on entry and exit from the function so -.Ic getopts -can be used properly both inside and outside the function (Bourne-style -functions leave -.Dv OPTIND -untouched, so using -.Ic getopts -inside a function interferes with using -.Ic getopts -outside the function). -In the future, the following differences will also be added: -.Bl -bullet -offset indent -.It -A separate trap/signal environment will be used during the execution of -functions. -This will mean that traps set inside a function will not affect the -shell's traps and signals that are not ignored in the shell (but may be -trapped) will have their default effect in a function. -.It -The EXIT trap, if set in a function, will be executed after the function -returns. -.El -.El -.Ss POSIX mode -The shell is intended to be -.Tn POSIX -compliant; however, in some cases, -.Tn POSIX -behaviour is contrary either to the original Korn shell behaviour or to user -convenience. -How the shell behaves in these cases is determined by the state of the -.Ic posix -option -.Pq Ic set Fl o Ic posix . -If it is on, the -.Tn POSIX -behaviour is followed; otherwise, it is not. -The -.Ic posix -option is set automatically when the shell starts up if the environment -contains the -.Dv POSIXLY_CORRECT -parameter. -.Pp -The following is a list of things that are affected by the state of the -.Ic posix -option: -.Bl -bullet -Reading of -.Ev $ENV . -If not in -.Ic posix -mode, the -.Ev ENV -parameter is not expanded and included when the shell starts. -.It -Occurrences of -.Ic \e\&" -inside double quoted -.Ic `\&.\&.` -command substitutions. -In -.Tn POSIX -mode, the -.Ic \e\&" -is interpreted when the command is interpreted; in -.Pf non- Tn POSIX -mode, the -backslash is stripped before the command substitution is interpreted. -For example, -.Ic echo \&"`echo \e\&"hi\e\&"`\&" -produces -.Dq \&"hi\&" -in -.Tn POSIX -mode, -.Dq hi -in -.Pf non- Tn POSIX -mode. -To avoid problems, use the -.Ic $(...)\& -form of command substitution. -.It -.Ic kill -l -output. -In -.Tn POSIX -mode, signal names are listed one per line; in -.Pf non- Tn POSIX -mode, -signal numbers, names and descriptions are printed in columns. -In the future, a new option -.Pq Fl v No perhaps -will be added to distinguish the two behaviours. -.It -.Ic fg -exit status. -In -.Tn POSIX -mode, the exit status is 0 if no errors occur; in -.Pf non- Tn POSIX -mode, the exit status is that of the last foregrounded job. -.It -.Ic eval -exit status. -If -.Ic eval -gets to see an empty command (i.e., -.Ic eval "`false`" ) , -its exit status in -.Tn POSIX -mode will be 0. -In -.Pf non- Tn POSIX -mode, it will be the exit status of the last command substitution that was -done in the processing of the arguments to -.Ic eval -(or 0 if there were no command substitutions). -.It -.Ic getopts . -In -.Tn POSIX -mode, options must start with a -.Ql - ; -in -.Pf non- Tn POSIX -mode, options can start with either -.Ql - -or -.Ql + . -.It -Brace expansion (also known as alternation). -In -.Tn POSIX -mode, brace expansion is -disabled; in -.Pf non- Tn POSIX -mode, brace expansion is enabled. -Note that -.Ic set Fl o Ic posix -(or setting the -.Ev POSIXLY_CORRECT -parameter) automatically turns the -.Ic braceexpand -option off; however, it can be explicitly turned on later. -.It -.Ic set \&- . -In -.Tn POSIX -mode, this does not clear the -.Ic verbose -or -.Ic xtrace -options; in -.Pf non- Tn POSIX -mode, it does. -.It -.Ic set -exit status. -In -.Tn POSIX -mode, the exit status of -.Ic set -is 0 if there are no errors; in -.Pf non- Tn POSIX -mode, the exit status is that of any -command substitutions performed in generating the -.Ic set -command. -For example, -.Ic set \&-\&- `false`; echo $?\& -prints 0 in -.Tn POSIX -mode, 1 in -.Pf non- Tn POSIX -mode. -This construct is used in most shell scripts that use the old -.Xr getopt 1 -command. -.It -Argument expansion of the -.Ic alias , -.Ic export , -.Ic readonly -and -.Ic typeset -commands. -In -.Tn POSIX -mode, normal argument expansion is done; in -.Pf non- Tn POSIX -mode, -field splitting, file globbing, brace expansion and (normal) tilde expansion -are turned off, while assignment tilde expansion is turned on. -.It -Signal specification. -In -.Tn POSIX -mode, signals can be specified as digits, only -if signal numbers match -.Tn POSIX -values (i.e., HUP=1, INT=2, QUIT=3, ABRT=6, -KILL=9, ALRM=14 and TERM=15); in -.Pf non- Tn POSIX -mode, signals can always be digits. -.It -Alias expansion. -In -.Tn POSIX -mode, alias expansion is only carried out when -reading command words; in -.Pf non- Tn POSIX -mode, alias expansion is carried out on any -word following an alias that ended in a space. -For example, the following -.Ic for -loop -.Pp -.Bl -item -offset indent -compact -.It -.Ic alias a='for ' i='j' -.It -.Xo -.Ic a i in 1 2; do print i=$i j=$j; -.Ic done -.Xc -.El -.Pp -uses parameter -.Ic i -in -.Tn POSIX -mode, -.Ic j -in -.Pf non- Tn POSIX -mode. -.It -Test. -In -.Tn POSIX -mode, the expression -.Sq Fl t -(preceded by some number of -.Sq Ic \&! -arguments) is always true as it is a non-zero length string; in -.Pf non- Tn POSIX -mode, it tests if file descriptor 1 is a -.Xr tty 4 -(i.e., the -.Ar fd -argument to the -.Fl t -test may be left out and defaults to 1). -.El -.Ss Strict Bourne shell mode -When the -.Ic sh -option is enabled (see the -.Ic set -command), -.Nm -will behave like -.Xr sh 1 -in the following ways: -.Bl -bullet -.It -The parameter -.Ic $_ -is not set to -.Bl -dash -.It -the expanded alias' full program path after entering commands -that are tracked aliases -.It -the last argument on the command line after entering external -commands -.El -.It -File descriptors are left untouched when executing -.Ic exec -with no arguments. -.It -Backslash-escaped special characters are not substituted in -.Ev PS1 . -.It -Sequences of -.Sq Li (( Ns Ar ... Ns Li )) -are not interpreted as arithmetic expressions. -.El -.Ss Command execution -After evaluation of command-line arguments, redirections and parameter -assignments, the type of command is determined: a special built-in, a -function, a regular built-in or the name of a file to execute found using the -.Ev PATH -parameter. -The checks are made in the above order. -Special built-in commands differ from other commands in that the -.Ev PATH -parameter is not used to find them, and an error during their execution can -cause a non-interactive shell to exit and parameter assignments that are -specified before the command are kept after the command completes. -Just to confuse things, if the -.Ic posix -option is turned off (see the -.Ic set -command below), some special commands are very special in that no field -splitting, file globbing, brace expansion, nor tilde expansion is performed -on arguments that look like assignments. -Regular built-in commands are different only in that the -.Ev PATH -parameter is not used to find them. -.Pp -The original -.Nm ksh -and -.Tn POSIX -differ somewhat in which commands are considered -special or regular: -.Pp -.Tn POSIX -special commands -.Pp -.Ic \&. , \&: , break , continue , -.Ic eval , exec , exit , export , -.Ic readonly , return , set , shift , -.Ic trap , unset -.Pp -Additional -.Nm -special commands -.Pp -.Ic builtin , times , typeset -.Pp -Very special commands -.Pq Pf non- Tn POSIX -.Pp -.Ic alias , readonly , set , typeset -.Pp -.Tn POSIX -regular commands -.Pp -.Ic alias , bg , cd , command , -.Ic false , fc , fg , getopts , -.Ic jobs , kill , read , true , -.Ic umask , unalias , wait -.Pp -Additional -.Nm -regular commands -.Pp -.Ic \&[ , echo , let , print , -.Ic pwd , test , ulimit , whence -.Pp -In the future, the additional -.Nm -special and regular commands may be treated -differently from the -.Tn POSIX -special and regular commands. -.Pp -Once the type of the command has been determined, any command-line parameter -assignments are performed and exported for the duration of the command. -.Pp -The following describes the special and regular built-in commands: -.Bl -tag -width Ds -.It Ic \&. Ar file Op Ar arg1 ... -Execute the commands in -.Ar file -in the current environment. -The file is searched for in the directories of -.Ev PATH . -If arguments are given, the positional parameters may be used to access them -while -.Ar file -is being executed. -If no arguments are given, the positional parameters are -those of the environment the command is used in. -.It Ic \&: Op Ar ... -The null command. -Exit status is set to zero. -.It Xo Ic alias -.Op Fl d | Ic +-t Op Fl r -.Op Ic +-px -.Op Ic +- -.Oo Ar name -.Op Ns = Ns Ar value -.Ar ... Oc -.Xc -Without arguments, -.Ic alias -lists all aliases. -For any name without a value, the existing alias is listed. -Any name with a value defines an alias (see -.Sx Aliases -above). -.Pp -When listing aliases, one of two formats is used. -Normally, aliases are listed as -.Ar name Ns = Ns Ar value , -where -.Ar value -is quoted. -If options were preceded with -.Ql + -or a lone -.Ql + -is given on the command line, only -.Ar name -is printed. -In addition, if the -.Fl p -option is used, each alias is prefixed with the string -.Dq alias\ \& . -.Pp -The -.Fl x -option sets -.Po Ic \&+x -\ clears -.Pc -the export attribute of an alias or, if no names are given, lists the aliases -with the export attribute (exporting an alias has no effect). -.Pp -The -.Fl t -option indicates that tracked aliases are to be listed/set (values specified on -the command line are ignored for tracked aliases). -The -.Fl r -option indicates that all tracked aliases are to be reset. -.Pp -The -.Fl d -option causes directory aliases, which are used in tilde expansion, to be -listed or set (see -.Sx Tilde expansion -above). -.It Ic bg Op Ar job ... -Resume the specified stopped job(s) in the background. -If no jobs are specified, -.Ic %\&+ -is assumed. -This command is only available on systems which support job control (see -.Sx Job control -below for more information). -.It Ic bind Op Fl l -The current bindings or, if the -.Fl l -flag is given, -the names of the functions to which keys may be bound, are listed. -See -.Sx Emacs editing mode -for more information. -.It Xo Ic bind Op Fl m -.Ar string Ns = Ns Op Ar substitute -.Ar ... -.Xc -.It Xo Ic bind -.Ar string Ns = Ns Op Ar editing-command -.Ar ... -.Xc -The specified editing command is bound to the given -.Ar string , -which should consist of a control character (which may be written using caret -notation, i.e., ^X), optionally preceded by one of the two prefix characters. -If the -.Fl m -flag is given, the specified input -.Ar string -will afterwards be immediately replaced by the given -.Ar substitute -string, which may contain editing commands. -.Pp -Future input of the -.Ar string -will cause the editing command to be immediately invoked. -Note that although only two prefix characters (usually -.Tn ESC -and ^X) are supported, some -multi-character sequences can be supported. -The following binds the arrow keys on an -.Tn ANSI -terminal or -.Xr xterm 1 -(these are in the default bindings). -Of course some escape sequences won't work out quite this nicely. -.Pp -.Bl -item -compact -.It -.Ic bind '^[['=prefix-2 -.It -.Ic bind '^XA'=up-history -.It -.Ic bind '^XB'=down-history -.It -.Ic bind '^XC'=forward-char -.It -.Ic bind '^XD'=backward-char -.El -.It Ic break Op Ar level -Exit the -.Ar level Ns th -inner-most -.Ic for , -.Ic select , -.Ic until -or -.Ic while -loop. -.Ar level -defaults to 1. -.It Ic builtin Ar command Op Ar arg1 ... -Execute the built-in command -.Ar command . -.It Xo Ic cd Op Fl LP -.Op Ar dir -.Xc -Set the working directory to -.Ar dir . -If the parameter -.Ev CDPATH -is set, it lists the search path for the directory containing -.Ar dir . -A -.Dv NULL -path means the current directory. -If -.Ar dir -is found in any component of the -.Ev CDPATH -search path other than the -.Dv NULL -path, the name of the new working directory will be written to standard output. -If -.Ar dir -is missing, the home directory -.Ev HOME -is used. -If -.Ar dir -is -.Ql - , -the previous working directory is used (see -.Ev OLDPWD -parameter). -If the -.Fl L -option (logical path) is used or if the -.Ic physical -option (see the -.Ic set -command below) isn't set, references to -.Dq \&.\&. -in -.Ar dir -are relative to the path used to get to the directory. -If the -.Fl P -option (physical path) is used or if the -.Ic physical -option is set, -.Dq \&.\&. -is relative to the filesystem directory tree. -The -.Ev PWD -and -.Ev OLDPWD -parameters are updated to reflect the current and old working directory, -respectively. -.It Xo Ic cd Op Fl LP -.Ar old new -.Xc -The string -.Ar new -is substituted for -.Ar old -in the current directory, and the shell attempts to change to the new -directory. -.It Xo Ic command Op Fl pvV -.Ar cmd Op Ar arg1 ... -.Xc -If neither the -.Fl v -nor -.Fl V -option is given, -.Ar cmd -is executed exactly as if -.Ic command -had not been specified, with two exceptions. -First, -.Ar cmd -cannot be a shell function, and second, special built-in commands lose their -specialness (i.e., redirection and utility errors do not cause the shell to -exit, and command assignments are not permanent). -If the -.Fl p -option is given, a default search path is used instead of the current value of -.Ev PATH -(the actual value of the default path is system dependent: on -.Tn POSIX Ns ish -systems, it is the value returned by -.Ic getconf CS_PATH ) . -.Pp -If the -.Fl v -option is given, instead of executing -.Ar cmd , -information about what would be executed is given (and the same is done for -.Ar arg1 ... ) . -For special and regular built-in commands and functions, their names are simply -printed; for aliases, a command that defines them is printed; and for commands -found by searching the -.Ev PATH -parameter, the full path of the command is printed. -If no command is found -(i.e., the path search fails), nothing is printed and -.Ic command -exits with a non-zero status. -The -.Fl V -option is like the -.Fl v -option, except it is more verbose. -.It Ic continue Op Ar level -Jumps to the beginning of the -.Ar level Ns th -inner-most -.Ic for , -.Ic select , -.Ic until -or -.Ic while -loop. -.Ar level -defaults to 1. -.It Xo Ic echo Op Fl neE -.Op Ar arg ... -.Xc -Prints its arguments (separated by spaces) followed by a newline, to the -standard output. -The newline is suppressed if any of the arguments contain the -backslash sequence -.Ql \ec . -See the -.Ic print -command below for a list of other backslash sequences that are recognized. -.Pp -The options are provided for compatibility with -.Bx -shell scripts. -The -.Fl n -option suppresses the trailing newline, -.Fl e -enables backslash interpretation (a no-op, since this is normally done), and -.Fl E -suppresses backslash interpretation. -.It Ic eval Ar command ... -The arguments are concatenated (with spaces between them) to form a single -string which the shell then parses and executes in the current environment. -.It Xo Ic exec -.Op Ar command Op Ar arg ... -.Xc -The command is executed without forking, replacing the shell process. -.Pp -If no command is given except for I/O redirection, the I/O redirection is -permanent and the shell is -not replaced. -Any file descriptors greater than 2 which are opened or -.Xr dup 2 Ns 'd -in this way are not made available to other executed commands (i.e., commands -that are not built-in to the shell). -Note that the Bourne shell differs here; -it does pass these file descriptors on. -.It Ic exit Op Ar status -The shell exits with the specified exit status. -If -.Ar status -is not specified, the exit status is the current value of the -.Ic $\&? -parameter. -.It Xo Ic export Op Fl p -.Op Ar parameter Ns Op \&= Ns Ar value -.Xc -Sets the export attribute of the named parameters. -Exported parameters are passed in the environment to executed commands. -If values are specified, the named parameters are also assigned. -.Pp -If no parameters are specified, the names of all parameters with the export -attribute are printed one per line, unless the -.Fl p -option is used, in which case -.Ic export -commands defining all exported parameters, including their values, are printed. -.It Ic false -A command that exits with a non-zero status. -.It Xo Ic fc -.Oo Fl e Ar editor \*(Ba -.Fl l Op Fl n Oc -.Op Fl r -.Op Ar first Op Ar last -.Xc -.Ar first -and -.Ar last -select commands from the history. -Commands can be selected by history number -or a string specifying the most recent command starting with that string. -The -.Fl l -option lists the command on stdout, and -.Fl n -inhibits the default command numbers. -The -.Fl r -option reverses the order of the list. -Without -.Fl l , -the selected commands are edited by the editor specified with the -.Fl e -option, or if no -.Fl e -is specified, the editor specified by the -.Ev FCEDIT -parameter (if this parameter is not set, -.Pa /bin/ed -is used), and then executed by the shell. -.It Xo Ic fc -.Oo Fl e No \&- \&| Fl s Oc -.Op Fl g -.Op Ar old Ns = Ns Ar new -.Op Ar prefix -.Xc -Re-execute the selected command (the previous command by default) after -performing the optional substitution of -.Ar old -with -.Ar new . -If -.Fl g -is specified, all occurrences of -.Ar old -are replaced with -.Ar new . -This command is usually accessed with the predefined -.Ic alias r='fc -e -' . -.It Ic fg Op Ar job ... -Resume the specified job(s) in the foreground. -If no jobs are specified, -.Ic %\&+ -is assumed. -This command is only available on systems which support job control (see -.Sx Job control -below for more information). -.It Xo Ic getopts Ar optstring name -.Op Ar arg ... -.Xc -Used by shell procedures to parse the specified arguments (or positional -parameters, if no arguments are given) and to check for legal options. -.Ar optstring -contains the option letters that -.Ic getopts -is to recognize. -If a letter is followed by a colon, the option is expected to -have an argument. -Options that do not take arguments may be grouped in a single argument. -If an option takes an argument and the option character is not the -last character of the argument it is found in, the remainder of the argument is -taken to be the option's argument; otherwise, the next argument is the option's -argument. -.Pp -Each time -.Ic getopts -is invoked, it places the next option in the shell parameter -.Ar name -and the index of the next argument to be processed in the shell parameter -.Ev OPTIND . -If the option was introduced with a -.Ql + , -the option placed in -.Ar name -is prefixed with a -.Ql + . -When an option requires an argument, -.Ic getopts -places it in the shell parameter -.Ev OPTARG . -When an illegal option or a missing option argument is encountered, a question -mark or a colon is placed in -.Ar name -(indicating an illegal option or missing argument, respectively) and -.Ev OPTARG -is set to the option character that caused the problem. -If -.Ar optstring -does not begin with a colon, a question mark is placed in -.Ar name , -.Ev OPTARG -is unset and an error message is printed to standard error. -.Pp -When the end of the options is encountered, -.Ic getopts -exits with a non-zero exit status. -Options end at the first (non-option -argument) argument that does not start with a -.Ql - -or when a -.Ql -- -argument is encountered. -.Pp -Option parsing can be reset by setting -.Ev OPTIND -to 1 (this is done automatically whenever the shell or a shell procedure is -invoked). -.Pp -Warning: Changing the value of the shell parameter -.Ev OPTIND -to a value other than 1 or parsing different sets of arguments without -resetting -.Ev OPTIND -may lead to unexpected results. -.It Xo Ic hash Op Fl r -.Op Ar name ... -.Xc -Without arguments, any hashed executable command pathnames are listed. -The -.Fl r -option causes all hashed commands to be removed from the hash table. -Each -.Ar name -is searched as if it were a command name and added to the hash table if it is -an executable command. -.It Xo Ic jobs Op Fl lpn -.Op Ar job ... -.Xc -Display information about the specified job(s); if no jobs are specified, all -jobs are displayed. -The -.Fl n -option causes information to be displayed only for jobs that have changed -state since the last notification. -If the -.Fl l -option is used, the process ID of each process in a job is also listed. -The -.Fl p -option causes only the process group of each job to be printed. -See -.Sx Job control -below for the format of -.Ar job -and the displayed job. -.It Xo Ic kill -.Oo Fl s Ar signame \*(Ba -.Fl Ar signum | Fl Ar signame Oc { -.Ar job | -.Ar pid | -.Ar pgrp No } Ar ... -.Xc -Send the specified signal to the specified jobs, process IDs or process -groups. -If no signal is specified, the -.Dv TERM -signal is sent. -If a job is specified, the signal is sent to the job's process group. -See -.Sx Job control -below for the format of -.Ar job . -.It Ic kill -l Op Ar exit-status ... -Print the name of the signal that killed a process which exited with the -specified -.Ar exit-status Ns es. -If no arguments are specified, a list of all the signals, their numbers and -a short description of them are printed. -.It Ic let Op Ar expression ... -Each expression is evaluated (see -.Sx Arithmetic expressions -above). -If all expressions are successfully evaluated, the exit status is 0 (1) -if the last expression evaluated to non-zero (zero). -If an error occurs during -the parsing or evaluation of an expression, the exit status is greater than 1. -Since expressions may need to be quoted, -.Ic (( Ar expr Ic )) -is syntactic sugar for -.Ic let \&" Ns Ar expr Ns Ic \&" . -.It Xo Ic print -.Oo Fl nprsu Ns Ar n \*(Ba -.Fl R Op Fl en Oc -.Op Ar argument ... -.Xc -.Ic print -prints its arguments on the standard output, separated by spaces and -terminated with a newline. -The -.Fl n -option suppresses the newline. -By default, certain C escapes are translated. -These include -.Ql \eb , -.Ql \ef , -.Ql \en , -.Ql \er , -.Ql \et , -.Ql \ev -and -.Ql \e0### -.Po -.Ql # -is an octal digit, of which there may be 0 to 3 -.Pc . -.Ql \ec -is equivalent to using the -.Fl n -option. -.Ql \e -expansion may be inhibited with the -.Fl r -option. -The -.Fl s -option prints to the history file instead of standard output, the -.Fl u -option prints to file descriptor -.Ar n -.Po -.Ar n -defaults to 1 if omitted -.Pc , -and the -.Fl p -option prints to the co-process (see -.Sx Co-processes -above). -.Pp -The -.Fl R -option is used to emulate, to some degree, the -.Bx -.Xr echo 1 -command, which does not process -.Ql \e -sequences unless the -.Fl e -option is given. -As above, the -.Fl n -option suppresses the trailing newline. -.It Ic pwd Op Fl LP -Print the present working directory. -If the -.Fl L -option is used or if the -.Ic physical -option (see the -.Ic set -command below) isn't set, the logical path is printed (i.e., the path used to -.Ic cd -to the current directory). -If the -.Fl P -option (physical path) is used or if the -.Ic physical -option is set, the path determined from the filesystem (by following -.Dq \&.\&. -directories to the root directory) is printed. -.It Xo Ic read Oo Fl prsu Ns Ar n -.Oc Op Ar parameter ... -.Xc -Reads a line of input from the standard input, separates the line into fields -using the -.Ev IFS -parameter (see -.Sx Substitution -above) and assigns each field to the specified parameters. -If there are more parameters than fields, the extra parameters are set to -.Dv NULL , -or alternatively, if there are more fields than parameters, the last parameter -is assigned the remaining fields (inclusive of any separating spaces). -If no parameters are specified, the -.Ev REPLY -parameter is used. -If the input line ends in a backslash and the -.Fl r -option was not used, the backslash and the newline are stripped and more input -is read. -If no input is read, -.Ic read -exits with a non-zero status. -.Pp -The first parameter may have a question mark and a string appended to it, in -which case the string is used as a prompt (printed to standard error before -any input is read) if the input is a -.Xr tty 4 -(e.g., -.Ic read nfoo?'number of foos: ' ) . -.Pp -The -.Fl u Ns Ar n -and -.Fl p -options cause input to be read from file descriptor -.Ar n -or the current co-process (see -.Sx Co-processes -above for comments on this), respectively. -If the -.Fl s -option is used, input is saved to the history file. -.It Xo Ic readonly Op Fl p -.Oo Ar parameter -.Op Ns = Ns Ar value -.Ar ... Oc -.Xc -Sets the read-only attribute of the named parameters. -If values are given, -parameters are set to them before setting the attribute. -Once a parameter is -made read-only, it cannot be unset and its value cannot be changed. -.Pp -If no parameters are specified, the names of all parameters with the read-only -attribute are printed one per line, unless the -.Fl p -option is used, in which case -.Ic readonly -commands defining all read-only parameters, including their values, are -printed. -.It Ic return Op Ar status -Returns from a function or -.Ic \&. -script, with exit status -.Ar status . -If no -.Ar status -is given, the exit status of the last executed command is used. -If used outside of a function or -.Ic \&. -script, it has the same effect as -.Ic exit . -Note that -.Nm -treats both profile and -.Ev ENV -files as -.Ic \&. -scripts, while the original Korn shell only treats profiles as -.Ic \&. -scripts. -.It Xo Ic set Op Ic +-abCefhkmnpsuvxX -.Op Ic +-o Ar option -.Op Ic +-A Ar name -.Op Fl \&- -.Op Ar arg ... -.Xc -The -.Ic set -command can be used to set -.Pq Ic \&- -or clear -.Pq Ic \&+ -shell options, set the positional parameters or set an array parameter. -Options can be changed using the -.Ic \&+ Ns Fl o Ar option -syntax, where -.Ar option -is the long name of an option, or using the -.Ic \&+\&- Ns Ar letter -syntax, where -.Ar letter -is the option's single letter name (not all options have a single letter name). -The following table lists both option letters (if they exist) and long names -along with a description of what the option does: -.Bl -tag -width 15n -.It Fl A -Sets the elements of the array parameter -.Ar name -to -.Ar arg ... . -If -.Fl A -is used, the array is reset (i.e., emptied) first; if -.Ic \&+A -is used, the first N elements are set (where N is the number of -.Ar arg Ns s ) , -the rest are left untouched. -.It Fl a Ic allexport -All new parameters are created with the export attribute. -.It Fl b Ic notify -Print job notification messages asynchronously, instead of just before the -prompt. -Only used if job control is enabled -.Pq Fl m . -.It Fl C Ic noclobber -Prevent -.Ic \&> -redirection from overwriting existing files -.Po -.Ic \&>\&| -must be used to force an overwrite -.Pc . -.It Fl e Ic errexit -Exit (after executing the -.Dv ERR -trap) as soon as an error occurs or a command fails (i.e., exits with a -non-zero status). -This does not apply to commands whose exit status is -explicitly tested by a shell construct such as -.Ic if , -.Ic until , -.Ic while , -.Ic \&&\&& -or -.Ic \&|\&| -statements. -.It Fl f Ic noglob -Do not expand file name patterns. -.It Fl h Ic trackall -Create tracked aliases for all executed commands (see -.Sx Aliases -above). -Enabled by default for non-interactive shells. -.It Fl i Ic interactive -Enable interactive mode. -This can only be set/unset when the shell is invoked. -.It Fl k Ic keyword -Parameter assignments are recognized anywhere in a command. -.It Fl l Ic login -The shell is a login shell. -This can only be set/unset when the shell is invoked (see -.Sx Shell startup -above). -.It Fl m Ic monitor -Enable job control (default for interactive shells). -.It Fl n lc noexec -Do not execute any commands. -Useful for checking the syntax of scripts -(ignored if interactive). -.It Fl p Ic privileged -Set automatically if, when the shell starts, the read UID or GID does not match -the effective UID (EUID) or GID (EGID), respectively. -See -.Sx Shell startup -above for a description of what this means. -.It Fl r Ic restricted -Enable restricted mode. -This option can only be used when the shell is invoked. -See -.Sx Shell startup -above for a description of what this means. -.It Fl s Ic stdin -If used where the shell is invoked, commands are read from standard input. -Set automatically if the shell is invoked with no arguments. -.Pp -When -.Fl s -is used with the -.Ic set -command it causes the specified arguments to be sorted before assigning them to -the positional parameters (or to array -.Ar name , -if -.Fl A -is used). -.It Fl u Ic nounset -Referencing of an unset parameter is treated as an error, unless one of the -.Ql - , -.Ql + -or -.Ql = -modifiers is used. -.It Fl v Ic verbose -Write shell input to standard error as it is read. -.It Fl x Ic xtrace -Print commands and parameter assignments when they are executed, preceded by -the value of -.Ev PS4 . -.It Fl X Ic markdirs -Mark directories with a trailing -.Ql / -during file name generation. -.It Ic bgnice -Background jobs are run with lower priority. -.It Ic braceexpand -Enable brace expansion (a.k.a., alternation). -.It Ic emacs -Enable BRL emacs-like command-line editing (interactive shells only); see -.Sx Emacs editing mode . -.It Ic emacs-usemeta -In emacs command-line editing, use the 8th bit as meta (^[) prefix. -This is the default. -.It Ic gmacs -Enable gmacs-like command-line editing (interactive shells only). -Currently identical to emacs editing except that transpose (^T) acts slightly -differently. -.It Ic ignoreeof -The shell will not (easily) exit when end-of-file is read; -.Ic exit -must be used. -To avoid infinite loops, the shell will exit if -.Dv EOF -is read 13 times in a row. -.It Ic nohup -Do not kill running jobs with a -.Dv SIGHUP -signal when a login shell exists. -Currently set by default, but this will -change in the future to be compatible with the original Korn shell (which -doesn't have this option, but does send the -.Dv SIGHUP -signal). -.It Ic nolog -No effect. -In the original Korn shell, this prevents function definitions from -being stored in the history file. -.It Ic physical -Causes the -.Ic cd -and -.Ic pwd -commands to use -.Dq physical -(i.e., the filesystem's) -.Dq \&.\&. -directories instead of -.Dq logical -directories (i.e., the shell handles -.Dq \&.\&. , -which allows the user to be oblivious of symbolic links to directories). -Clear by default. -Note that setting this option does not affect the current value of the -.Ev PWD -parameter; only the -.Ic cd -command changes -.Ev PWD . -See the -.Ic cd -and -.Ic pwd -commands above for more details. -.It Ic posix -Enable -.Tn POSIX -mode. -See -.Sx POSIX mode -above. -.It Ic sh -Enable strict Bourne shell mode (see -.Sx Strict Bourne shell mode ) . -.It Ic vi -Enable -.Xr vi 1 Ns -like -command-line editing (interactive shells only). -.It Ic viraw -No effect. -In the original Korn shell, unless -.Ic viraw -was set, the vi command-line mode would let the -.Xr tty 4 -driver do the work until -.Tn ESC -(^[) was entered. -.Nm -is always in viraw mode. -.It Ic vi-esccomplete -In vi command-line editing, do command and file name completion when escape -(^[) is entered in command mode. -.It Ic vi-show8 -Prefix characters with the eighth bit set with -.Dq M\&- . -If this option is not set, characters in the range 128\-160 are printed as is, -which may cause problems. -.It Ic vi-tabcomplete -In vi command-line editing, do command and file name completion when tab (^I) -is entered in insert mode. -This is the default. -.El -.Pp -These options can also be used upon invocation of the shell. -The current set of -options (with single letter names) can be found in the parameter -.Dv \&- . -.Ic set Fl o -with no option name will list all the options and whether each is on or off; -.Ic set +o -will print the long names of all options that are currently on. -.Pp -Remaining arguments, if any, are positional parameters and are assigned, in -order, to the positional parameters (i.e. -.Ic $1 , $2 , -etc.). -If options end with -.Ql -- -and there are no remaining arguments, all positional parameters are cleared. -If no options or arguments are given, the values of all names are printed. -For unknown historical reasons, a lone -.Ql - -option is treated specially \(em it clears both the -.Fl x -and -.Fl v -options. -.It Ic shift Op Ar number -The positional parameters -.Ar number Ns +1 , -.Ar number Ns +2 , -etc. are renamed to -.Dq 1 , -.Dq 2 , -etc. -.Ar number -defaults to 1. -.It Ic test Ar expression -.It Ic \&[ Ar expression Ic \&] -.Ic test -evaluates the -.Ar expression -and returns zero status if true, 1 if false, or greater than 1 if there -was an error. -It is normally used as the condition command of -.Ic if -and -.Ic while -statements. -The following basic expressions are available: -.Bl -tag -width 17n -.It Fl r Ar file -.Ar file -exists and is readable. -.It Fl w Ar file -.Ar file -exists and is writable. -.It Fl x Ar file -.Ar file -exists and is executable. -.It Fl a Ar file -.Ar file -exists. -.It Fl e Ar file -.Ar file -exists. -.It Fl f Ar file -.Ar file -is a regular file. -.It Fl d Ar file -.Ar file -is a directory. -.It Fl c Ar file -.Ar file -is a character special device. -.It Fl b Ar file -.Ar file -is a block special device. -.It Fl p Ar file -.Ar file -is a named pipe. -.It Fl u Ar file -.Ar file Ns 's -mode has setuid bit set. -.It Fl g Ar file -.Ar file Ns 's -mode has setgid bit set. -.It Fl k Ar file -.Ar file Ns 's -mode has sticky bit set. -.It Fl s Ar file -.Ar file -is not empty. -.It Fl O Ar file -.Ar file Ns 's -owner is the shell's effective user ID. -.It Fl G Ar file -.Ar file Ns 's -group is the shell's effective group ID. -.It Fl h Ar file -.Ar file -is a symbolic link. -.It Fl H Ar file -.Ar file -is a context dependent directory (only useful on HP-UX). -.It Fl L Ar file -.Ar file -is a symbolic link. -.It Fl S Ar file -.Ar file -is a socket. -.It Fl o Ar option -Shell -.Ar option -is set (see the -.Ic set -command above for a list of options). -As a non-standard extension, if the option starts with a -.Ql \&! , -the test is negated; the test always fails if -.Ar option -doesn't exist (thus -.Ic \&[ -o Ar foo -.Ic -o -o \&! Ns Ar foo Ic \&] -returns true if and only if option -.Ar foo -exists). -.It Ar file1 Fl nt Ar file2 -.Ar file1 -is newer than -.Ar file2 . -.It Ar file1 Fl ot Ar file2 -.Ar file1 -is older than -.Ar file2 . -.It Ar file1 Fl ef Ar file2 -.Ar file1 -is the same file as -.Ar file2 . -.It Fl t Op Ar fd -File descriptor -.Ar fd -is a -.Xr tty 4 -device. -If the -.Ic posix -option is not set, -.Ar fd -may be left out, in which case it is taken to be 1 (the behaviour differs due -to the special -.Tn POSIX -rules described below). -.It Ar string -.Ar string -is not empty (has non-zero length). -Note that there is the potential for problems if -.Ar string -turns out to be an operator (e.g. -.Fl r ) . -It is generally better to use a test like -.No ( Ns Nm sh ) -.Bk -words -.Sm off -.Ic \&[\ X\&" Ar string Ic \&" Ic \ \&] -.Sm on -.Ek -.No or ( Ns Nm ksh ) -.Bk -words -.Ic \&[[ Fl n Ar string Ic \&]] -.Ek -instead (double quotes are used in case -.Ar string -contains spaces or globbing characters). -.It Fl z Ar string -.Ar string -is empty. -.It Fl n Ar string -.Ar string -is not empty. -.It Ar string No = Ar string -Strings are equal. -.It Ar string No == Ar string -Strings are equal. -.It Ar string No \&!= Ar string -Strings are not equal. -.It Ar number Fl eq Ar number -Numbers compare equal. -.It Ar number Fl ne Ar number -Numbers compare not equal. -.It Ar number Fl ge Ar number -Numbers compare greater than or equal. -.It Ar number Fl gt Ar number -Numbers compare greater than. -.It Ar number Fl le Ar number -Numbers compare less than or equal. -.It Ar number Fl \< Ar number -Numbers compare less than. -.El -.Pp -The above basic expressions, in which unary operators have precedence over -binary operators, may be combined with the following operators (listed in -increasing order of precedence): -.Pp -.Bl -tag -width "expr -o expr" -compact -.It Ar expr Fl o Ar expr -Logical -.Tn OR . -.It Ar expr Fl a Ar expr -Logical -.Tn AND . -.It Ic \&! Ar expr -Logical -.Tn NOT . -.It Ic \&( Ar expr Ic \&) -Grouping. -.El -.Pp -On operating systems not supporting -.Pa /dev/fd/ Ns Ar n -devices (where -.Ar n -is a file descriptor number), the -.Ic test -command will attempt to fake it for all tests that operate on files (except the -.Fl e -test). -For example, -.Ic \&[ -w /dev/fd/2 \&] -tests if file descriptor 2 is writable. -.Pp -Note that some special rules are applied (courtesy of -.Tn POSIX ) -if the number of -arguments to -.Ic test -or -.Ic \&[ ... \&] -is less than five; if leading -.Ql \&! -arguments can be stripped such that only one argument remains then a string -length test is performed (again, even if the argument is a unary operator); if -leading -.Ql \&! -arguments can be stripped such that three arguments remain and the second -argument is a binary operator, then the binary operation is performed (even -if the first argument is a unary operator, including an unstripped -.Ql \&! ) . -.Pp -.Sy Note: -A common mistake is to use -.Bk -words -.Ic if \&[ $foo = bar \&] -.Ek -which fails if parameter -.Ic foo -is -.Dv NULL -or unset, if it has embedded spaces (i.e., -.Ev IFS -characters), or if it is a unary operator like -.Sq Ic \&! -or -.Sq Fl n . -Use tests like -.No ( Ns Nm sh ) -.Bk -words -.Ic if \&[ \&"X$foo\&" = Xbar \&] -.Ek -.No or ( Ns Nm ksh ) -.Bk -words -.Ic if \&[[ \&$foo = bar \&]] -.Ek -instead. -.It Xo Ic time Op Fl p -.Op Ar pipeline -.Xc -If a -.Ar pipeline -is given, the times used to execute the pipeline are reported. -If no pipeline -is given, then the user and system time used by the shell itself and all the -commands it has run since it was started is reported. -The times reported are the real time (elapsed time from start to finish), -the user CPU time (time spent running in user mode) and the system CPU time -(time spent running in kernel mode). -Times are reported to standard error; the format of the output is: -.Pp -.Dl " 0m0.00s real 0m0.00s user 0m0.00s system" -.Pp -If the -.Fl p -option is given the output is slightly longer: -.Bd -literal -offset indent -real 0.00 -user 0.00 -sys 0.00 -.Ed -.Pp -It is an error to specify the -.Fl p -option unless -.Ar pipeline -is a simple command. -.Pp -Simple redirections of standard error do not effect the output of the -.Ic time -command: -.Pp -.Dl time sleep 1 2> afile -.Dl { time sleep 1; } 2> afile -.Pp -Times for the first command do not go to -.Dq afile , -but those of the second command do. -.It Ic times -Print the accumulated user and system times used both by the shell -and by processes that the shell started which have exited. -The format of the output is: -.Bd -literal -offset indent -0m0.00s 0m0.00s -0m0.00s 0m0.00s -.Ed -.It Ic trap Op Ar handler signal ... -Sets a trap handler that is to be executed when any of the -specified signals are received. -.Ar handler -is either a -.Dv NULL -string, indicating the signals are to be ignored, a minus sign -.Pq Sq \&- , -indicating that the default action is to be taken for the signals (see -.Xr signal 3 ) -or a string containing shell commands to be evaluated and executed at the -first opportunity (i.e., when the current command completes or before -printing the next -.Ev PS1 -prompt) after receipt of one of the signals. -.Ar signal -is the name of a signal (e.g., -.Dv PIPE -or -.Dv ALRM ) -or the number of the signal (see the -.Ic kill Fl l -command above). -There are two special signals: -.Dv EXIT -(also known as 0), which is executed when the shell is about to exit, and -.Dv ERR , -which is executed after an error occurs (an error is something that would cause -the shell to exit if the -.Fl e -or -.Ic errexit -option were see \(em see the -.Ic set -command above). -.Dv EXIT -handlers are executed in the environment of the last executed command. -Note -that for non-interactive shells, the trap handler cannot be changed for signals -that were ignored when the shell started. -.Pp -With no arguments, -.Ic trap -lists, as a series of -.Ic trap -commands, the current state of the traps that have been set since the shell -started. -Note that the output of -.Ic trap -cannot be usefully piped to another process (an artifact of the fact that -traps are cleared when subprocesses are created). -.Pp -The original Korn shell's -.Dv DEBUG -trap and the handling of -.Dv ERR -and -.Dv EXIT -traps in functions are not yet implemented. -.It Ic true -A command that exits with a zero value. -.It Xo Ic typeset -.Oo Op Ic +-Ulprtux -.Op Fl L Ns Op Ar n -.Op Fl R Ns Op Ar n -.Op Fl Z Ns Op Ar n -.Op Fl i Ns Op Ar n -.No \&| Fl f Op Fl tux Oc -.Oo Ar name -.Op Ns = Ns Ar value -.Ar ... Oc -.Xc -Display or set parameter attributes. -With no -.Ar name -arguments, parameter attributes are displayed; if no options are used, the -current attributes of all parameters are printed as -.Ic typeset -commands; if an option is given (or -.Ql - -with no option letter), all parameters and their values with the specified -attributes are printed; if options are introduced with -.Ql + , -parameter values are not printed. -.Pp -If -.Ar name -arguments are given, the attributes of the named parameters are set -.Pq Ic \&- -or cleared -.Pq Ic \&+ . -Values for parameters may optionally be specified. -If -.Ic typeset -is used inside a function, any newly created parameters are local to the -function. -.Pp -When -.Fl f -is used, -.Ic typeset -operates on the attributes of functions. -As with parameters, if no -.Ar name Ns s -are given, functions are listed with their values (i.e., definitions) unless -options are introduced with -.Ql + , -in which case only the function names are reported. -.Bl -tag -width 3n -.It Fl L Ns Ar n -Left justify attribute. -.Ar n -specifies the field width. -If -.Ar n -is not specified, the current width of a parameter (or the width of its first -assigned value) is used. -Leading whitespace (and zeros, if used with the -.Fl Z -option) is stripped. -If necessary, values are either truncated or space padded -to fit the field width. -.It Fl R Ns Ar n -Right justify attribute. -.Ar n -specifies the field width. -If -.Ar n -is not specified, the current width of a parameter (or the width of its first -assigned value) is used. -Trailing whitespace is stripped. -If necessary, values are either stripped of leading characters or space -padded to make them fit the field width. -.It Fl Z Ns Ar n -Zero fill attribute. -If not combined with -.Fl L , -this is the same as -.Fl R , -except zero padding is used instead of space padding. -.It Fl i Ns Ar n -Integer attribute. -.Ar n -specifies the base to use when displaying the integer (if not specified, the -base given in the first assignment is used). -Parameters with this attribute may -be assigned values containing arithmetic expressions. -.It Fl U -Unsigned integer attribute. -Integers are printed as unsigned values (only -useful when combined with the -.Fl i -option). -This option is not in the original Korn shell. -.It Fl f -Function mode. -Display or set functions and their attributes, instead of parameters. -.It Fl l -Lower case attribute. -All upper case characters in values are converted to lower case. -(In the original Korn shell, this parameter meant -.Dq long integer -when used with the -.Fl i -option.) -.It Fl p -Print complete -.Ic typeset -commands that can be used to re-create the attributes (but not the values) of -parameters. -This is the default action (option exists for ksh93 compatibility). -.It Fl r -Read-only attribute. -Parameters with this attribute may not be assigned to or unset. -Once this attribute is set, it cannot be turned off. -.It Fl t -Tag attribute. -Has no meaning to the shell; provided for application use. -.Pp -For functions, -.Fl t -is the trace attribute. -When functions with the trace attribute are executed, the -.Ic xtrace -.Pq Fl x -shell option is temporarily turned on. -.It Fl u -Upper case attribute. -All lower case characters in values are converted to upper case. -(In the original Korn shell, this parameter meant -.Dq unsigned integer -when used with the -.Fl i -option, which meant upper case letters would never be used for bases greater -than 10. -See the -.Fl U -option.) -.Pp -For functions, -.Fl u -is the undefined attribute. -See -.Sx Functions -above for the implications of this. -.It Fl x -Export attribute. -Parameters (or functions) are placed in the environment of -any executed commands. -Exported functions are not yet implemented. -.El -.It Xo Ic ulimit Op Fl acdfHlmnpsStv -.Op Ar value -.Xc -Display or set process limits. -If no options are used, the file size limit -.Pq Fl f -is assumed. -.Ar value , -if specified, may be either an arithmetic expression or the word -.Dq unlimited . -The limits affect the shell and any processes created by the shell after a -limit is imposed. -Note that some systems may not allow limits to be increased -once they are set. -Also note that the types of limits available are system -dependent \(em some systems have only the -.Fl f -limit. -.Bl -tag -width 5n -.It Fl a -Displays all limits; unless -.Fl H -is used, soft limits are displayed. -.It Fl H -Set the hard limit only (default is to set both hard and soft limits). -.It Fl S -Set the soft limit only (default is to set both hard and soft limits). -.It Fl c Ar n -Impose a size limit of -.Ar n -blocks on the size of core dumps. -.It Fl d Ar n -Impose a size limit of -.Ar n -kibibytes on the size of the data area. -.It Fl f Ar n -Impose a size limit of -.Ar n -blocks on files written by the shell and its child processes (files of any -size may be read). -.It Fl l Ar n -Impose a limit of -.Ar n -kibibytes on the amount of locked (wired) physical memory. -.It Fl m Ar n -Impose a limit of -.Ar n -kibibytes on the amount of physical memory used. -.It Fl n Ar n -Impose a limit of -.Ar n -file descriptors that can be open at once. -.It Fl p Ar n -Impose a limit of -.Ar n -processes that can be run by the user at any one time. -.It Fl s Ar n -Impose a size limit of -.Ar n -kibibytes on the size of the stack area. -.It Fl t Ar n -Impose a time limit of -.Ar n -.Tn CPU -seconds to be used by each process. -.It Fl T Ar n -Impose a real time limit of -.Ar n -.Tn human -seconds to be used by each process. -.It Fl v Ar n -Impose a limit of -.Ar n -kibibytes on the amount of virtual memory used. -.El -.Pp -As far as -.Ic ulimit -is concerned, a block is 512 bytes. -.It Xo Ic umask Op Fl S -.Op Ar mask -.Xc -Display or set the file permission creation mask or umask (see -.Xr umask 2 ) . -If the -.Fl S -option is used, the mask displayed or set is symbolic; otherwise, it is an -octal number. -.Pp -Symbolic masks are like those used by -.Xr chmod 1 . -When used, they describe what permissions may be made available (as opposed to -octal masks in which a set bit means the corresponding bit is to be cleared). -For example, -.Dq ug=rwx,o= -sets the mask so files will not be readable, writable or executable by -.Dq others -and is equivalent (on most systems) to the octal mask -.Dq 007 . -.It Xo Ic unalias Op Fl adt -.Op Ar name1 ... -.Xc -The aliases for the given names are removed. -If the -.Fl a -option is used, all aliases are removed. -If the -.Fl t -or -.Fl d -options are used, the indicated operations are carried out on tracked or -directory aliases, respectively. -.It Xo Ic unset Op Fl fv -.Ar parameter ... -.Xc -Unset the named parameters -.Po -.Fl v , -the default -.Pc -or functions -.Pq Fl f . -The exit status is non-zero if any of the parameters were already unset, zero -otherwise. -.It Ic wait Op Ar job ... -Wait for the specified job(s) to finish. -The exit status of -.Ic wait -is that of the last specified job; if the last job is killed by a signal, the -exit status is 128 + the number of the signal (see -.Ic kill -l Ar exit-status -above); if the last specified job can't be found (because it never existed or -had already finished), the exit status of -.Ic wait -is 127. -See -.Sx Job control -below for the format of -.Ar job . -.Ic wait -will return if a signal for which a trap has been set is received or if a -.Dv SIGHUP , -.Dv SIGINT -or -.Dv SIGQUIT -signal is received. -.Pp -If no jobs are specified, -.Ic wait -waits for all currently running jobs (if any) to finish and exits with a zero -status. -If job monitoring is enabled, the completion status of jobs is printed -(this is not the case when jobs are explicitly specified). -.It Xo Ic whence Op Fl pv -.Op Ar name ... -.Xc -For each -.Ar name , -the type of command is listed (reserved word, built-in, alias, -function, tracked alias or executable). -If the -.Fl p -option is used, a path search is performed even if -.Ar name -is a reserved word, alias, etc. -Without the -.Fl v -option, -.Ic whence -is similar to -.Ic command Fl v -except that -.Ic whence -will find reserved words and won't print aliases as alias commands. -With the -.Fl v -option, -.Ic whence -is the same as -.Ic command Fl V . -Note that for -.Ic whence , -the -.Fl p -option does not affect the search path used, as it does for -.Ic command . -If the type of one or more of the names could not be determined, the exit -status is non-zero. -.El -.Ss Job control -Job control refers to the shell's ability to monitor and control jobs, which -are processes or groups of processes created for commands or pipelines. -At a minimum, the shell keeps track of the status of the background (i.e., -asynchronous) jobs that currently exist; this information can be displayed -using the -.Ic jobs -commands. -If job control is fully enabled (using -.Ic set Fl m -or -.Ic set Fl o Ic monitor ) , -as it is for interactive shells, the processes of a job are placed in their -own process group. -Foreground jobs can be stopped by typing the suspend -character from the terminal (normally ^Z), jobs can be restarted in either the -foreground or background using the -.Ic fg -and -.Ic bg -commands, and the state of the terminal is saved or restored when a foreground -job is stopped or restarted, respectively. -.Pp -Note that only commands that create processes (e.g., asynchronous commands, -subshell commands and non-built-in, non-function commands) can be stopped; -commands like -.Ic read -cannot be. -.Pp -When a job is created, it is assigned a job number. -For interactive shells, this number is printed inside -.Dq \&[..\&] , -followed by the process IDs of the processes in the job when an asynchronous -command is run. -A job may be referred to in the -.Ic bg , -.Ic fg , -.Ic jobs , -.Ic kill -and -.Ic wait -commands either by the process ID of the last process in the command pipeline -(as stored in the -.Ic $\&! -parameter) or by prefixing the job number with a percent sign -.Pq Sq % . -Other percent sequences can also be used to refer to jobs: -.Bl -tag -width 10n -.It Ic %\&+ -The most recently stopped job or, if there are no stopped jobs, the oldest -running job. -.It Ic %% , % -Same as -.Ic %\&+ . -.It Ic %\&- -The job that would be the -.Ic %\&+ -job if the latter did not exist. -.It Ic % Ns Ar n -The job with job number -.Ar n . -.It Ic %\&? Ns Ar string -The job containing the string -.Ar string -(an error occurs if multiple jobs are matched). -.It Ic % Ns Ar string -The job starting with string -.Ar string -(an error occurs if multiple jobs are matched). -.El -.Pp -When a job changes state (e.g., a background job finishes or foreground job is -stopped), the shell prints the following status information: -.Bd -unfilled -offset indent -.Ic \&[ Ar number Ic \&] Ar flag status command -.Ed -.Pp -where -.Bl -tag -width "status" -.It Ar number -is the job number of the job. -.It Ar flag -is the -.Ql + -or -.Ql - -character if the job is the -.Ic %\&+ or -.Ic %\&- -job, respectively, or space if it is neither. -.It Ar status -indicates the current state of the job and can be: -.Bl -tag -width "Running" -.It Cm Running -The job has neither stopped nor exited (note that running does not necessarily -mean consuming -.Tn CPU -time \(em the process could be blocked waiting for some -event). -.It Cm Done Op Ar number -The job exited. -.Ar number -is the exit status of the job, which is omitted if the status is zero. -.It Cm Stopped Op Ar signal -The job was stopped by the indicated -.Ar signal -(if no signal is given, the job was stopped by -.Dv SIGTSTP ) . -.It Ar signal-description Op Dq core dumped -The job was killed by a signal (e.g., memory fault, hangup, etc.; use -.Ic kill -l -for a list of signal descriptions). -The -.Dq core dumped -message indicates the process created a core file. -.El -.It Ar command -is the command that created the process. -If there are multiple processes in -the job, each process will have a line showing its -.Ar command -and possibly its -.Ar status , -if it is different from the status of the previous process. -.El -.Pp -When an attempt is made to exit the shell while there are jobs in the stopped -state, the shell warns the user that there are stopped jobs and does not exit. -If another attempt is immediately made to exit the shell, the stopped jobs are -sent a -.Dv SIGHUP -signal and the shell exits. -Similarly, if the -.Ic nohup -option is not set and there are running jobs when an attempt is made to exit -a login shell, the shell warns the user and does not exit. -If another attempt -is immediately made to exit the shell, the running jobs are sent a -.Dv SIGHUP -signal and the shell exits. -.Ss Interactive input line editing -The shell supports three modes of reading command lines from a -.Xr tty 4 -in an interactive session, which is controlled by the -.Ic emacs , -.Ic gmacs -and -.Ic vi -options (at most one of these can be set at once). -If none of these options are enabled, the shell simply -reads lines using the normal -.Xr tty 4 -driver. -If the -.Ic emacs -or -.Ic gmacs -option is set, the shell allows emacs-like editing of the command; similarly, -if the -.Ic vi -option is set, the shell allows vi-like editing of the command. -These modes are described in detail in the following sections. -.Pp -In these editing modes, if a line is longer than the screen width (see -.Ev COLUMNS -parameter), -a -.Ql > , -.Ql + -or -.Ql < -character is displayed in the last column indicating that there are more -characters after, before and after, or before the current position, -respectively. -The line is scrolled horizontally as necessary. -.Ss Emacs editing mode -When the -.Ic emacs -option is set, interactive input line editing is enabled. -Warning: This mode is -slightly different from the emacs mode in the original Korn shell. -By default, the eight bit is used as Meta, but this can be turned -off by entering -.Ic set +o emacs-usemeta , -contained in the default -.Mx 6 -.Pa /etc/profile . -.Pp -In the emacs mode, various editing commands -(typically bound to one or more control characters) cause immediate actions -without waiting for a newline. -Several editing commands are bound to particular -control characters when the shell is invoked; these bindings can be changed -using the -.Ic bind -command. -.Pp -The following is a list of available editing commands. -Each description starts with the name of the command, an -.Ar n -(if the command can be prefixed with a count) and any keys the command is -bound to by default (written using caret notation, i.e., -.Tn "ASCII ESC" -character is -written as ^[). -A count prefix for a command is entered using the sequence -.Ic ^\&[ Ns Ar n , -where -.Ar n -is a sequence of 1 or more digits; unless otherwise specified, if a count is -omitted, it defaults to 1. -Note that editing command names are used only with the -.Ic bind -command. -Furthermore, many editing commands are useful only on terminals with -a visible cursor. -The default bindings were chosen to resemble corresponding -Emacs key bindings. -The users' -.Xr tty 4 -characters (e.g., -.Dv ERASE ) -are bound to -reasonable substitutes and override the default bindings. -.Bl -tag -width Ds -.It Ic abort ^G -Useful as a response to a request for a -.Ic search-history -pattern in order to abort the search. -.It Ic auto-insert Ar n -Simply causes the character to appear as literal input. -Most ordinary characters are bound to this. -.It Ic backward-char Ar n Ic ^B -Moves the cursor backward -.Ar n -characters. -.It Ic backward-word Ar n Ic ^[B -Moves the cursor backward to the beginning of the word; words consist of -alphanumerics, underscore -.Pq Sq _ -and dollar sign -.Pq Sq \&$ -characters. -.It Ic beginning-of-history ^[< -Moves to the beginning of the history. -.It Ic beginning-of-line ^A -Moves the cursor to the beginning of the edited input line. -.It Ic capitalize-word Ar n Ic ^[c , ^[C -Uppercase the first character in the next -.Ar n -words, leaving the cursor past the end of the last word. -.Pp -If the current line does not begin with a comment character, one is added at -the beginning of the line and the line is entered (as if return had been -pressed); otherwise, the existing comment characters are removed and the cursor -is placed at the beginning of the line. -.It Ic complete ^[^[ -.It Ic complete ^I -Automatically completes as much as is unique of the command name or the file -name containing the cursor. -If the entire remaining command or file name is -unique, a space is printed after its completion, unless it is a directory name -in which case -.Ql / -is appended. -If there is no command or file name with the current partial word -as its prefix, a bell character is output (usually causing a beep to be -sounded). -Currently being considered a bug, -if the file name contains \fB[\fR or \fB]\fR, it cannot be completed, even if -the character is escaped, in \fBemacs\fR mode. -This surprisingly works in \fBvi\fR mode; please submit a fix. -.It Ic complete-command ^X^[ -Automatically completes as much as is unique of the command name having the -partial word up to the cursor as its prefix, as in the -.Ic complete -command above. -.It Ic complete-file ^[^X -Automatically completes as much as is unique of the file name having the -partial word up to the cursor as its prefix, as in the -.Ic complete -command described above. -.It Ic complete-list ^[= -List the possible completions for the current word. -.It Xo Ic delete-char-backward Ar n Ic ERASE , -.Ic ^? , ^H -.Xc -Deletes -.Ar n -characters before the cursor. -.It Ic delete-char-forward Ar n -Deletes -.Ar n -characters after the cursor. -.It Xo Ic delete-word-backward Ar n Ic ^[ERASE , -.Ic ^[^? , ^[^H , ^[h -.Xc -Deletes -.Ar n -words before the cursor. -.It Ic delete-word-forward Ar n Ic ^[d -Deletes characters after the cursor up to the end of -.Ar n -words. -.It Ic down-history Ar n Ic ^N -Scrolls the history buffer forward -.Ar n -lines (later). -Each input line originally starts just after the last entry -in the history buffer, so -.Ic down-history -is not useful until either -.Ic search-history -or -.Ic up-history -has been performed. -.It Ic downcase-word Ar n Ic ^[L , ^[l -Lowercases the next -.Ar n -words. -.It Ic end-of-history ^[> -Moves to the end of the history. -.It Ic end-of-line ^E -Moves the cursor to the end of the input line. -.It Ic eot ^_ -Acts as an end-of-file; this is useful because edit-mode input disables -normal terminal input canonicalization. -.It Ic eot-or-delete Ar n Ic ^D -Acts as -.Ic eot -if alone on a line; otherwise acts as -.Ic delete-char-forward . -.It Ic error -Error (ring the bell). -.It Ic exchange-point-and-mark ^X^X -Places the cursor where the mark is and sets the mark to where the cursor was. -.It Ic expand-file ^[\&* -Appends a -.Ql * -to the current word and replaces the word with the result of performing file -globbing on the word. -If no files match the pattern, the bell is rung. -.It Ic forward-char Ar n Ic ^F -Moves the cursor forward -.Ar n -characters. -.It Ic forward-word Ar n Ic ^[f -Moves the cursor forward to the end of the -.Ar n Ns th -word. -.It Ic goto-history Ar n Ic ^[g -Goes to history number -.Ar n . -.It Ic kill-line KILL -Deletes the entire input line. -.It Ic kill-region ^W -Deletes the input between the cursor and the mark. -.It Ic kill-to-eol Ar n Ic ^K -Deletes the input from the cursor to the end of the line if -.Ar n -is not specified; otherwise deletes characters between the cursor and column -.Ar n . -.It Ic list ^[? -Prints a sorted, columnated list of command named or file names (if any) that -can complete the partial word containing the cursor. -Directory names have -.Ql / -appended to them. -.It Ic list-command ^X? -Prints a sorted, columnated list of command names (if any) that can complete -the partial word containing the cursor. -.It Ic list-file ^X^Y -Prints a sorted, columnated list of file names (if any) that can complete the -partial word containing the cursor. -File type indicators are appended as described under -.Ic list -above. -.It Ic newline ^J , ^M -Causes the current input line to be processed by the shell. -The current cursor position may be anywhere on the line. -.It Ic newline-and-next ^O -Causes the current input line to be processed by the shell, and the next line -from history becomes the current line. -This is only useful after an -.Ic up-history -or -.Ic search-history . -.It Ic no-op QUIT -This does nothing. -.It Ic prefix-1 ^[ -Introduces a 2-character command sequence. -.It Ic prefix-2 ^X -.It Ic prefix-2 ^[[ -Introduces a 2-character command sequence. -.It Ic prev-hist-word Ar n Ic ^[\&. , ^{_ -The last -.Pq Ar n Ns th -word of the previous command is inserted at the cursor. -.It Ic quote ^^ -The following character is taken literally rather than as an editing command. -.It Ic redraw ^L -Reprints the prompt string and the current input line. -.It Ic search-character-backward Ar n Ic ^[^] -Search backward in the current line for the -.Ar n Ns th -occurrence of the next character typed. -.It Ic search-character-forward Ar n Ic ^] -Search forward in the current line for the -.Ar n Ns th -occurrence of the next character typed. -.It Ic search-history ^R -Enter incremental search mode. -The internal history list is searched -backwards for commands matching the input. -An initial -.Ql ^ -in the search string anchors the search. -The abort key will leave search mode. -Other commands will be executed after leaving search mode. -Successive -.Ic search-history -commands continue searching backward to the next previous occurrence of the -pattern. -The history buffer retains only a finite number of lines; the oldest -are discarded as necessary. -.It Ic set-mark-command ^[ Ns -Set the mark at the cursor position. -.It Ic stuff -On systems supporting it, pushes the bound character back onto the terminal -input where it may receive special processing by the terminal handler. -This is useful for the BRL ^T mini-systat feature, for example. -.It Ic stuff-reset -Acts like -.Ic stuff , -then aborts input the same as an interrupt. -.It Ic transpose-chars ^T -If at the end of line or if the -.Ic gmacs -option is set, this exchanges the two previous characters; otherwise, it -exchanges the previous and current characters and moves the cursor one -character to the right. -.It Ic up-history Ar n Ic ^P -Scrolls the history buffer backward -.Ar n -lines (earlier). -.It Ic upcase-word Ar n Ic ^[U , ^[u -Uppercase the next -.Ar n -words. -.It Ic version ^V -Display the version of -.Nm mksh . -The current edit buffer is restored as soon as any -key is pressed (the key is then processed, unless it is a space). -.It Ic yank ^Y -Inserts the most recently killed text string at the current cursor position. -.It Ic yank-pop ^[y -Immediately after a -.Ic yank , -replaces the inserted text string with the next previously killed text string. -.El -.Ss Vi editing mode -The vi command-line editor in -.Nm -has basically the same commands as the vi editor (see -.Xr vi 1 ) , -with the following exceptions: -.Bl -bullet -.It -You start out in insert mode. -.It -There are file name and command completion commands -.Po -.Ic = , \e , \&* , ^X , -.Ic ^E , ^F -and, optionally, -.Ic -.Pc . -.It -The -.Ic _ -command is different (in -.Nm -it is the last argument command, in vi it goes to -the start of the current line). -.It -The -.Ic / -and -.Ic G -commands move in the opposite direction as the -.Ic j -command. -.It -Commands which don't make sense in a single line editor are not available -(e.g., screen movement commands, -.Xr ex 1 Ns -style -colon -.Pq Ic \&: -commands, etc.). -.El -.Pp -Note that the ^X stands for control-X; also , and are used -for escape, space and tab, respectively (no kidding). -.Pp -Like vi, there are two modes \(em -.Dq insert -mode and -.Dq command -mode. -In insert mode, most characters are simply put in the buffer at the -current cursor position as they are typed; however, some characters are -treated specially. -In particular, the following characters are taken from current -.Xr tty 4 -settings (see -.Xr tty 1 ) -and have their usual meaning (normal values are in parentheses): kill (^U), -erase (^?), werase (^W), eof (^D), intr (^C) and quit (^\e). -In addition to -the above, the following characters are also treated specially in insert mode: -.Bl -tag -width 10n -.It Ic ^H -Erases previous character. -.It Ic ^V -Literal next. -The next character typed is not treated specially (can be used -to insert the characters being described here). -.It Ic ^J ^M -End of line. -The current line is read, parsed and executed by the shell. -.It Ic -Puts the editor in command mode (see below). -.It Ic ^E -Command and file name enumeration (see below). -.It Ic ^F -Command and file name completion (see below). -If used twice in a row, the -list of possible completions is displayed; if used a third time, the completion -is undone. -.It Ic ^X -Command and file name expansion (see below). -.It Ic -Optional file name and command completion (see -.Ic ^F -above), enabled with -.Ic set Fl o Ic vi-tabcomplete . -.El -.Pp -In command mode, each character is interpreted as a command. -Characters that -don't correspond to commands, are illegal combinations of commands or are -commands that can't be carried out all cause beeps. -In the following command descriptions, an -.Ar n -indicates the command may be prefixed by a number (e.g., -.Ic 10l -moves right 10 characters); if no number prefix is used, -.Ar n -is assumed to be 1 unless otherwise specified. -The term -.Dq current position -refers to the position between the cursor and the character preceding the -cursor. -A -.Dq word -is a sequence of letters, digits and underscore characters or a sequence of -non-letter, non-digit, non-underscore, non-whitespace characters (e.g., -.Dq ab2\&*\&&^ -contains two words) and a -.Dq big-word -is a sequence of non-whitespace characters. -.Pp -Special -.Nm -vi commands -.Pp -The following commands are not in, or are different from, the normal vi file -editor: -.Bl -tag -width 10n -.It Ar n Ns _ -Insert a space followed by the -.Ar n Ns th -big-word from the last command in the history at the current position and enter -insert mode; if -.Ar n -is not specified, the last word is inserted. -.It Ic \&# -Insert the comment character -.Pq Sq \&# -at the start of the current line and return the line to the shell (equivalent -to -.Ic \&I\&#^J ) . -.It Ar n Ns Ic g -Like -.Ic G , -except if -.Ar n -is not specified, it goes to the most recent remembered line. -.It Ar n Ns Ic v -Edit line -.Ar n -using the vi editor; if -.Ar n -is not specified, the current line is edited. -The actual command executed is -.Ic fc Fl e Ic ${VISUAL:-${EDITOR:-vi}} Ar n . -.It Ic \&* No and Ic ^X -Command or file name expansion is applied to the current big-word (with an -appended -.Ql * , -if the word contains no file globbing characters) \(em the big-word is replaced -with the resulting words. -If the current big-word is the first on the line (or -follows one of the characters -.Ql \&; , -.Ql | , -.Ql & , -.Ql ( -or -.Ql \&) ) -and does not contain a slash -.Pq Sq / -then the command expansion is done; otherwise file name expansion is done. -Command expansion will match the big-word against all aliases, functions and -built-in commands as well as any executable files found by searching the -directories in the -.Ev PATH -parameter. -File name expansion matches the big-word against the files in the -current directory. -After expansion, the cursor is placed just past the last -word and the editor is in insert mode. -.It Xo -.Ar n Ns Ic \e Ns , -.Ar n Ns Ic ^F , -.Ar n Ns Ic , -.No and -.Ar n Ns Ic -.Xc -Command/file name completion. -Replace the current big-word with the -longest unique match obtained after performing command and file name expansion. -.Ic -is only recognized if the -.Ic vi-tabcomplete -option is set, while -.Ic -is only recognized if the -.Ic vi-esccomplete -option is set (see -.Ic set Fl o ) . -If -.Ar n -is specified, the -.Ar n Ns th -possible completion is selected (as reported by the command/file name -enumeration command). -.It Ic \&= No and Ic ^E -Command/file name enumeration. -List all the commands or files that match the current big-word. -.It Ic ^V -Display the version of -.Nm -until another key is pressed (this key is ignored). -.It Ic @ Ns Ar c -Macro expansion. -Execute the commands found in the alias -.Ar c . -.El -.Pp -Intra-line movement commands: -.Bl -tag -width Ds -.It Xo Ar n Ns Ic h No and -.Ar n Ns Ic ^H -.Xc -Move left -.Ar n -characters. -.It Xo Ar n Ns Ic l No and -.Ar n Ns Ic -.Xc -Move right -.Ar n -characters. -.It Ic \&0 -Move to column 0. -.It Ic ^ -Move to the first non-whitespace character. -.It Ar n Ns Ic \&| -Move to column -.Ar n . -.It Ic $ -Move to the last character. -.It Ar n Ns Ic b -Move back -.Ar n -words. -.It Ar n Ns Ic B -Move back -.Ar n -big-words. -.It Ar n Ns Ic e -Move forward to the end of the word, -.Ar n -times. -.It Ar n Ns Ic E -Move forward to the end of the big-word, -.Ar n -times. -.It Ar n Ns Ic w -Move forward -.Ar n -words. -.It Ar n Ns Ic W -Move forward -.Ar n -big-words. -.It Ic % -Find match. -The editor looks forward for the nearest parenthesis, bracket or -brace and then moves the cursor to the matching parenthesis, bracket or brace. -.It Ar n Ns Ic f Ns Ar c -Move forward to the -.Ar n Ns th -occurrence of the character -.Ar c . -.It Ar n Ns Ic F Ns Ar c -Move backward to the -.Ar n Ns th -occurrence of the character -.Ar c . -.It Ar n Ns Ic t Ns Ar c -Move forward to just before the -.Ar n Ns th -occurrence of the character -.Ar c . -.It Ar n Ns Ic T Ns Ar c -Move backward to just before the -.Ar n Ns th -occurrence of the character -.Ar c . -.It Ar n Ns Ic \&; -Repeats the last -.Ic f , F , t -or -.Ic T -command. -.It Ar n Ns Ic \&, -Repeats the last -.Ic f , F , t -or -.Ic T -command, but moves in the opposite direction. -.El -.Pp -Inter-line movement commands: -.Bl -tag -width Ds -.It Xo -.Ar n Ns Ic j , -.Ar n Ns Ic + -.No and -.Ar n Ns Ic ^N -.Xc -Move to the -.Ar n Ns th -next line in the history. -.It Xo -.Ar n Ns Ic k , -.Ar n Ns Ic - -.No and -.Ar n Ns Ic ^P -.Xc -Move to the -.Ar n Ns th -previous line in the history. -.It Ar n Ns Ic G -Move to line -.Ar n -in the history; if -.Ar n -is not specified, the number of the first remembered line is used. -.It Ar n Ns Ic g -Like -.Ic G , -except if -.Ar n -is not specified, it goes to the most recent remembered line. -.It Ar n Ns Ic / Ns Ar string -Search backward through the history for the -.Ar n Ns th -line containing -.Ar string ; -if -.Ar string -starts with -.Ql ^ , -the remainder of the string must appear at the start of the history line for -it to match. -.It Ar n Ns Ic \&? Ns Ar string -Same as -.Ic / , -except it searches forward through the history. -.It Ar n Ns Ic n -Search for the -.Ar n Ns th -occurrence of the last search string; the directory of the search is the same -as the last search. -.It Ar n Ns Ic N -Search for the -.Ar n Ns th -occurrence of the last search string; the directory of the search is the -opposite of the last search. -.El -.Pp -Edit commands -.Bl -tag -width Ds -.It Ar n Ns Ic a -Append text -.Ar n -times; goes into insert mode just after the current position. -The append is -only replicated if command mode is re-entered (i.e., is used). -.It Ar n Ns Ic A -Same as -.Ic a , -except it appends at the end of the line. -.It Ar n Ns Ic i -Insert text -.Ar n -times; goes into insert mode at the current position. -The insertion is only -replicated if command mode is re-entered (i.e., is used). -.It Ar n Ns Ic I -Same as -.Ic i , -except the insertion is done just before the first non-blank character. -.It Ar n Ns Ic s -Substitute the next -.Ar n -characters (i.e., delete the characters and go into insert mode). -.It Ic S -Substitute whole line. -All characters from the first non-blank character to the -end of the line are deleted and insert mode is entered. -.It Ar n Ns Ic c Ns Ar move-cmd -Change from the current position to the position resulting from -.Ar n move-cmd Ns s -(i.e., delete the indicated region and go into insert mode); if -.Ar move-cmd -is -.Ic c , -the line starting from the first non-blank character is changed. -.It Ic C -Change from the current position to the end of the line (i.e., delete to the -end of the line and go into insert mode). -.It Ar n Ns Ic x -Delete the next -.Ar n -characters. -.It Ar n Ns Ic X -Delete the previous -.Ar n -characters. -.It Ic D -Delete to the end of the line. -.It Ar n Ns Ic d Ns Ar move-cmd -Delete from the current position to the position resulting from -.Ar n move-cmd Ns s ; -.Ar move-cmd -is a movement command (see above) or -.Ic d , -in which case the current line is deleted. -.It Ar n Ns Ic r Ns Ar c -Replace the next -.Ar n -characters with the character -.Ar c . -.It Ar n Ns Ic R -Replace. -Enter insert mode but overwrite existing characters instead of -inserting before existing characters. -The replacement is repeated -.Ar n -times. -.It Ar n Ns Ic \&~ -Change the case of the next -.Ar n -characters. -.It Ar n Ns Ic y Ns Ar move-cmd -Yank from the current position to the position resulting from -.Ar n move-cmd Ns s -into the yank buffer; if -.Ar move-cmd -is -.Ic y , -the whole line is yanked. -.It Ic Y -Yank from the current position to the end of the line. -.It Ar n Ns Ic p -Paste the contents of the yank buffer just after the current position, -.Ar n -times. -.It Ar n Ns Ic P -Same as -.Ic p , -except the buffer is pasted at the current position. -.El -.Pp -Miscellaneous vi commands -.Bl -tag -width Ds -.It Ic ^J No and Ic ^M -The current line is read, parsed and executed by the shell. -.It Ic ^L No and Ic ^R -Redraw the current line. -.It Ar n Ns Ic \&. -Redo the last edit command -.Ar n -times. -.It Ic u -Undo the last edit command. -.It Ic U -Undo all changes that have been made to the current line. -.It Ar intr No and Ar quit -The interrupt and quit terminal characters cause the current line to be -deleted and a new prompt to be printed. -.El -.Sh FILES -.Bl -tag -width "/etc/suid_profile" -compact -.It Pa ~/.profile -.It Pa /etc/profile -.It Pa /etc/suid_profile -.El -.Sh SEE ALSO -.Xr arc4random 1 , -.Xr awk 1 , -.Xr csh 1 , -.Xr ed 1 , -.Xr getconf 1 , -.Xr getopt 1 , -.Xr sed 1 , -.Xr sh 1 , -.Xr stty 1 , -.Xr vi 1 , -.Xr dup 2 , -.Xr execve 2 , -.Xr getgid 2 , -.Xr getuid 2 , -.Xr open 2 , -.Xr pipe 2 , -.Xr wait 2 , -.Xr getopt 3 , -.Xr rand 3 , -.Xr random 3 , -.Xr signal 3 , -.Xr srand 3 , -.Xr srandom 3 , -.Xr system 3 , -.Xr tty 4 , -.Xr environ 7 -.Pp -.Pa http://docsrv.sco.com:507/en/man/html.C/sh.C.html -.Pp -.Rs -.%A Morris Bolsky -.%A David Korn -.%T "The KornShell Command and Programming Language" -.%D 1983 -.%O "ISBN 0-13-516972-0" -.Re -.Rs -.%A Stephen G. Kochan -.%A Patrick H. Wood -.%T "UNIX Shell Programming" -.%O "Hayden" -.Re -.Rs -.%A "IEEE Inc." -.%T "IEEE Standard for Information Technology - Portable Operating System Interface (POSIX) - Part 2: Shell and Utilities" -.%D 1993 -.%O "ISBN 1-55937-266-9" -.Re -.Sh VERSION -This page documents version @(#)PD KSH v5.2.14 99/07/13.2 of the public -domain Korn shell, as delivered with -.Mx . -.Sh NOTES -.Nm sh -is implemented as a run-time option of -.Nm mksh , -with only those -.Nm ksh -features whose syntax or semantics are incompatible with a traditional Bourne -shell disabled. -Since this leaves some -.Nm ksh -extensions exposed, caution should be used where backwards compatibility with -traditional Bourne or -.Tn POSIX -compliant shells is an issue. -.Pp -.Nm mksh -is the name for the portable version of -.Mx -.Nm ksh -installed as -.Pa /bin/mksh -on other, foreign, operating systems. -Visit -.Pa http://wiki.mirbsd.de/MirbsdKsh -to obtain the latest version of -.Nm mksh . -.Sh AUTHORS -The -.Pa CONTRIBUTORS -file in the source distribution contains an almost complete list of people and -their part in the shell's development. -.Sh BUGS -Send bug reports for -.Nm -to the -.Mx -development team at -.Aq miros\-discuss@66h.42h.de . -Please include the version of -.Nm -.Po -.Ic print $KSH_VERSION -shows it -.Pc , -the machine, operating system and compiler you are using and how you built -.Nm -(it is recommended to use the provided -.Pa Build.sh -mechanism) -and a description of how to repeat the bug (a small shell script that -demonstrates the bug is best). -The following, if relevant (if you are not sure, include them), can also be -helpful: options you are using (both -.Pa options.h -and -.Ic set Fl o Ic options ) -and a copy of your -.Pa config.h -and -.Pa config.log -(the files generated by the -.Pa configure -script). -.Pp -By the way, the most frequently reported bug is: -.Bd -literal -offset indent -$ print hi | read a; print $a # Does not show hi -.Ed -.Pp -The -.Nm pdksh -author is aware of this and there is no need to report it. -For another known bug, refer to the comment above the -.Em complete-command -paragraph in the section -.Sx Emacs editing mode -above. diff --git a/ksh_stat.h b/ksh_stat.h deleted file mode 100644 index 1621790..0000000 --- a/ksh_stat.h +++ /dev/null @@ -1,71 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: ksh_stat.h,v 1.3 1996/10/01 02:05:39 downsj Exp $ */ - -#ifndef KSH_STAT_H -#define KSH_STAT_H - -/* Wrapper around the ugly sys/stat includes/ifdefs */ - -/* assumes already included */ -#include - -#ifndef HAVE_LSTAT -# define lstat(path, buf) stat(path, buf) -#endif /* HAVE_LSTAT */ - -#if defined(S_ISSOCK) && defined(S_IFIFO) -# if S_ISSOCK (S_IFIFO) -# define STAT_MACROS_BROKEN -# endif -#endif - -#ifdef STAT_MACROS_BROKEN -# undef S_ISREG -# undef S_ISDIR -# undef S_ISCHR -# undef S_ISBLK -# undef S_ISFIFO -# undef S_ISSOCK -# undef S_ISLNK -#endif /* STAT_MACROS_BROKEN */ - -#if !defined(S_ISREG) && defined(S_IFREG) -# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) -#endif /* S_ISREG */ -#if !defined(S_ISDIR) && defined(S_IFDIR) -# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) -#endif /* S_ISDIR */ -#if !defined(S_ISCHR) && defined(S_IFCHR) -# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) -#endif /* S_ISCHR */ -#if !defined(S_ISBLK) && defined(S_IFBLK) -# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) -#endif /* S_ISBLK */ -#if !defined(S_ISFIFO) && defined(S_IFIFO) -# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) -#endif /* S_ISFIFO */ -#if !defined(S_ISLNK) && defined(S_IFLNK) -# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) -#endif /* S_ISLNK */ -#if !defined(S_ISSOCK) && defined(S_IFSOCK) -# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) -#endif /* S_ISSOCK */ -#if !defined(S_ISCDF) && defined(S_CDF) -# define S_ISCDF(m) (S_ISDIR(m) && ((m) & S_CDF)) -#endif /* S_ISSOCK */ - -#ifndef S_ISVTX -# define S_ISVTX 01000 /* sticky bit */ -#endif /* S_ISVTX */ - -#ifndef S_IXUSR -# define S_IXUSR 00100 /* user execute bit */ -#endif /* S_IXUSR */ -#ifndef S_IXGRP -# define S_IXGRP 00010 /* user execute bit */ -#endif /* S_IXGRP */ -#ifndef S_IXOTH -# define S_IXOTH 00001 /* user execute bit */ -#endif /* S_IXOTH */ - -#endif /* ndef KSH_STAT_H */ diff --git a/ksh_wait.h b/ksh_wait.h deleted file mode 100644 index 43b7e89..0000000 --- a/ksh_wait.h +++ /dev/null @@ -1,57 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: ksh_wait.h,v 1.3 1997/06/19 13:58:43 kstailey Exp $ */ - -#ifndef KSH_WAIT_H -#define KSH_WAIT_H - -/* Wrapper around the ugly sys/wait includes/ifdefs */ - -#ifdef HAVE_SYS_WAIT_H -# include -#endif - -#ifndef POSIX_SYS_WAIT -/* Get rid of system macros (which probably use union wait) */ -# undef WIFCORED -# undef WIFEXITED -# undef WEXITSTATUS -# undef WIFSIGNALED -# undef WTERMSIG -# undef WIFSTOPPED -# undef WSTOPSIG -#endif /* POSIX_SYS_WAIT */ - -typedef int WAIT_T; - -#ifndef WIFCORED -# define WIFCORED(s) ((s) & 0x80) -#endif -#define WSTATUS(s) (s) - -#ifndef WIFEXITED -# define WIFEXITED(s) (((s) & 0xff) == 0) -#endif -#ifndef WEXITSTATUS -# define WEXITSTATUS(s) (((s) >> 8) & 0xff) -#endif -#ifndef WIFSIGNALED -# define WIFSIGNALED(s) (((s) & 0xff) != 0 && ((s) & 0xff) != 0x7f) -#endif -#ifndef WTERMSIG -# define WTERMSIG(s) ((s) & 0x7f) -#endif -#ifndef WIFSTOPPED -# define WIFSTOPPED(s) (((s) & 0xff) == 0x7f) -#endif -#ifndef WSTOPSIG -# define WSTOPSIG(s) (((s) >> 8) & 0xff) -#endif - -#if !defined(HAVE_WAITPID) && defined(HAVE_WAIT3) - /* always used with p == -1 */ -# define ksh_waitpid(p, s, o) wait3((s), (o), NULL) -#else /* !HAVE_WAITPID && HAVE_WAIT3 */ -# define ksh_waitpid(p, s, o) waitpid((p), (s), (o)) -#endif /* !HAVE_WAITPID && HAVE_WAIT3 */ - -#endif /* ndef KSH_WAIT_H */ diff --git a/lex.c b/lex.c deleted file mode 100644 index 0f7ae3b..0000000 --- a/lex.c +++ /dev/null @@ -1,1301 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: lex.c,v 1.30 2004/12/22 18:57:28 otto Exp $ */ - -/* - * lexical analysis and source input - */ - -#include "sh.h" -#include - -__RCSID("$MirOS$"); - -/* Structure to keep track of the lexing state and the various pieces of info - * needed for each particular state. - */ -typedef struct lex_state Lex_state; -struct lex_state { - int ls_state; - union { - /* $(...) */ - struct scsparen_info { - int nparen; /* count open parenthesis */ - int csstate; /* XXX remove */ -#define ls_scsparen ls_info.u_scsparen - } u_scsparen; - - /* $((...)) */ - struct sasparen_info { - int nparen; /* count open parenthesis */ - int start; /* marks start of $(( in output str */ -#define ls_sasparen ls_info.u_sasparen - } u_sasparen; - - /* ((...)) */ - struct sletparen_info { - int nparen; /* count open parenthesis */ -#define ls_sletparen ls_info.u_sletparen - } u_sletparen; - - /* `...` */ - struct sbquote_info { - int indquotes; /* true if in double quotes: "`...`" */ -#define ls_sbquote ls_info.u_sbquote - } u_sbquote; - - Lex_state *base; /* used to point to next state block */ - } ls_info; -}; - -typedef struct State_info State_info; -struct State_info { - Lex_state *base; - Lex_state *end; -}; - - -static void readhere(struct ioword *iop); -static int getsc__(void); -static void getsc_line(Source *s); -static int getsc_bn(void); -static char *get_brace_var(XString *wsp, char *wp); -static int arraysub(char **strp); -static const char *ungetsc(int c); -static void gethere(void); -static Lex_state *push_state_(State_info *si, Lex_state *old_end); -static Lex_state *pop_state_(State_info *si, Lex_state *old_end); - -static int backslash_skip; -static int ignore_backslash_newline; - -/* optimized getsc_bn() */ -#define getsc() (*source->str != '\0' && *source->str != '\\' \ - && !backslash_skip ? *source->str++ : getsc_bn()) -/* optimized getsc__() */ -#define getsc_() ((*source->str != '\0') ? *source->str++ : getsc__()) - -#define STATE_BSIZE 32 - -#define PUSH_STATE(s) do { \ - if (++statep == state_info.end) \ - statep = push_state_(&state_info, statep); \ - state = statep->ls_state = (s); \ - } while (0) - -#define POP_STATE() do { \ - if (--statep == state_info.base) \ - statep = pop_state_(&state_info, statep); \ - state = statep->ls_state; \ - } while (0) - - - -/* - * Lexical analyzer - * - * tokens are not regular expressions, they are LL(1). - * for example, "${var:-${PWD}}", and "$(size $(whence ksh))". - * hence the state stack. - */ - -int -yylex(int cf) -{ - Lex_state states[STATE_BSIZE], *statep; - State_info state_info; - int c, state; - XString ws; /* expandable output word */ - char *wp; /* output word pointer */ - char *sp, *dp; - int c2; - - - Again: - states[0].ls_state = -1; - states[0].ls_info.base = NULL; - statep = &states[1]; - state_info.base = states; - state_info.end = &states[STATE_BSIZE]; - - Xinit(ws, wp, 64, ATEMP); - - backslash_skip = 0; - ignore_backslash_newline = 0; - - if (cf&ONEWORD) - state = SWORD; - else if (cf&LETEXPR) { - *wp++ = OQUOTE; /* enclose arguments in (double) quotes */ - state = SLETPAREN; - statep->ls_sletparen.nparen = 0; - } else { /* normal lexing */ - state = (cf & HEREDELIM) ? SHEREDELIM : SBASE; - while ((c = getsc()) == ' ' || c == '\t') - ; - if (c == '#') { - ignore_backslash_newline++; - while ((c = getsc()) != '\0' && c != '\n') - ; - ignore_backslash_newline--; - } - ungetsc(c); - } - if (source->flags & SF_ALIAS) { /* trailing ' ' in alias definition */ - source->flags &= ~SF_ALIAS; - /* In POSIX mode, a trailing space only counts if we are - * parsing a simple command - */ - if (!Flag(FPOSIX) || (cf & CMDWORD)) - cf |= ALIAS; - } - - /* Initial state: one of SBASE SHEREDELIM SWORD SASPAREN */ - statep->ls_state = state; - - /* collect non-special or quoted characters to form word */ - while (!((c = getsc()) == 0 - || ((state == SBASE || state == SHEREDELIM) - && ctype(c, C_LEX1)))) - { - Xcheck(ws, wp); - switch (state) { - case SBASE: - if (c == '[' && (cf & (VARASN|ARRAYVAR))) { - *wp = EOS; /* temporary */ - if (is_wdvarname(Xstring(ws, wp), false)) - { - char *p, *tmp; - - if (arraysub(&tmp)) { - *wp++ = CHAR; - *wp++ = c; - for (p = tmp; *p; ) { - Xcheck(ws, wp); - *wp++ = CHAR; - *wp++ = *p++; - } - afree(tmp, ATEMP); - break; - } else { - Source *s; - - s = pushs(SREREAD, - source->areap); - s->start = s->str - = s->u.freeme = tmp; - s->next = source; - source = s; - } - } - *wp++ = CHAR; - *wp++ = c; - break; - } - /* fall through.. */ - Sbase1: /* includes *(...|...) pattern (*+?@!) */ - if (c == '*' || c == '@' || c == '+' || c == '?' - || c == '!') - { - c2 = getsc(); - if (c2 == '(' /*)*/ ) { - *wp++ = OPAT; - *wp++ = c; - PUSH_STATE(SPATTERN); - break; - } - ungetsc(c2); - } - /* fall through.. */ - Sbase2: /* doesn't include *(...|...) pattern (*+?@!) */ - switch (c) { - case '\\': - c = getsc(); - if (c) /* trailing \ is lost */ - *wp++ = QCHAR, *wp++ = c; - break; - case '\'': - *wp++ = OQUOTE; - ignore_backslash_newline++; - PUSH_STATE(SSQUOTE); - break; - case '"': - *wp++ = OQUOTE; - PUSH_STATE(SDQUOTE); - break; - default: - goto Subst; - } - break; - - Subst: - switch (c) { - case '\\': - c = getsc(); - switch (c) { - case '"': case '\\': - case '$': case '`': - *wp++ = QCHAR, *wp++ = c; - break; - default: - Xcheck(ws, wp); - if (c) { /* trailing \ is lost */ - *wp++ = CHAR, *wp++ = '\\'; - *wp++ = CHAR, *wp++ = c; - } - break; - } - break; - case '$': - c = getsc(); - if (c == '(') /*)*/ { - c = getsc(); - if (c == '(') /*)*/ { - PUSH_STATE(SASPAREN); - statep->ls_sasparen.nparen = 2; - statep->ls_sasparen.start = - Xsavepos(ws, wp); - *wp++ = EXPRSUB; - } else { - ungetsc(c); - PUSH_STATE(SCSPAREN); - statep->ls_scsparen.nparen = 1; - statep->ls_scsparen.csstate = 0; - *wp++ = COMSUB; - } - } else if (c == '{') /*}*/ { - *wp++ = OSUBST; - *wp++ = '{'; /*}*/ - wp = get_brace_var(&ws, wp); - c = getsc(); - /* allow :# and :% (ksh88 compat) */ - if (c == ':') { - *wp++ = CHAR, *wp++ = c; - c = getsc(); - } - /* If this is a trim operation, - * treat (,|,) specially in STBRACE. - */ - if (c == '#' || c == '%') { - ungetsc(c); - PUSH_STATE(STBRACE); - } else { - ungetsc(c); - PUSH_STATE(SBRACE); - } - } else if (ctype(c, C_ALPHA)) { - *wp++ = OSUBST; - *wp++ = 'X'; - do { - Xcheck(ws, wp); - *wp++ = c; - c = getsc(); - } while (ctype(c, C_ALPHA|C_DIGIT)); - *wp++ = '\0'; - *wp++ = CSUBST; - *wp++ = 'X'; - ungetsc(c); - } else if (ctype(c, C_DIGIT|C_VAR1)) { - Xcheck(ws, wp); - *wp++ = OSUBST; - *wp++ = 'X'; - *wp++ = c; - *wp++ = '\0'; - *wp++ = CSUBST; - *wp++ = 'X'; - } else { - *wp++ = CHAR, *wp++ = '$'; - ungetsc(c); - } - break; - case '`': - PUSH_STATE(SBQUOTE); - *wp++ = COMSUB; - /* Need to know if we are inside double quotes - * since sh/at&t-ksh translate the \" to " in - * "`..\"..`". - * This is not done in posix mode (section - * 3.2.3, Double Quotes: "The backquote shall - * retain its special meaning introducing the - * other form of command substitution (see - * 3.6.3). The portion of the quoted string - * from the initial backquote and the - * characters up to the next backquote that - * is not preceded by a backslash (having - * escape characters removed) defines that - * command whose output replaces `...` when - * the word is expanded." - * Section 3.6.3, Command Substitution: - * "Within the backquoted style of command - * substitution, backslash shall retain its - * literal meaning, except when followed by - * $ ` \."). - */ - statep->ls_sbquote.indquotes = 0; - if (!Flag(FPOSIX)) { - Lex_state *s = statep; - Lex_state *base = state_info.base; - while (1) { - for (; s != base; s--) { - if (s->ls_state == SDQUOTE) { - statep->ls_sbquote.indquotes = 1; - break; - } - } - if (s != base) - break; - if (!(s = s->ls_info.base)) - break; - base = s-- - STATE_BSIZE; - } - } - break; - default: - *wp++ = CHAR, *wp++ = c; - } - break; - - case SSQUOTE: - if (c == '\'') { - POP_STATE(); - *wp++ = CQUOTE; - ignore_backslash_newline--; - } else - *wp++ = QCHAR, *wp++ = c; - break; - - case SDQUOTE: - if (c == '"') { - POP_STATE(); - *wp++ = CQUOTE; - } else - goto Subst; - break; - - case SCSPAREN: /* $( .. ) */ - /* todo: deal with $(...) quoting properly - * kludge to partly fake quoting inside $(..): doesn't - * really work because nested $(..) or ${..} inside - * double quotes aren't dealt with. - */ - switch (statep->ls_scsparen.csstate) { - case 0: /* normal */ - switch (c) { - case '(': - statep->ls_scsparen.nparen++; - break; - case ')': - statep->ls_scsparen.nparen--; - break; - case '\\': - statep->ls_scsparen.csstate = 1; - break; - case '"': - statep->ls_scsparen.csstate = 2; - break; - case '\'': - statep->ls_scsparen.csstate = 4; - ignore_backslash_newline++; - break; - } - break; - - case 1: /* backslash in normal mode */ - case 3: /* backslash in double quotes */ - --statep->ls_scsparen.csstate; - break; - - case 2: /* double quotes */ - if (c == '"') - statep->ls_scsparen.csstate = 0; - else if (c == '\\') - statep->ls_scsparen.csstate = 3; - break; - - case 4: /* single quotes */ - if (c == '\'') { - statep->ls_scsparen.csstate = 0; - ignore_backslash_newline--; - } - break; - } - if (statep->ls_scsparen.nparen == 0) { - POP_STATE(); - *wp++ = 0; /* end of COMSUB */ - } else - *wp++ = c; - break; - - case SASPAREN: /* $(( .. )) */ - /* todo: deal with $((...); (...)) properly */ - /* XXX should nest using existing state machine - * (embed "..", $(...), etc.) */ - if (c == '(') - statep->ls_sasparen.nparen++; - else if (c == ')') { - statep->ls_sasparen.nparen--; - if (statep->ls_sasparen.nparen == 1) { - /*(*/ - if ((c2 = getsc()) == ')') { - POP_STATE(); - *wp++ = 0; /* end of EXPRSUB */ - break; - } else { - char *s; - - ungetsc(c2); - /* mismatched parenthesis - - * assume we were really - * parsing a $(..) expression - */ - s = Xrestpos(ws, wp, - statep->ls_sasparen.start); - memmove(s + 1, s, wp - s); - *s++ = COMSUB; - *s = '('; /*)*/ - wp++; - statep->ls_scsparen.nparen = 1; - statep->ls_scsparen.csstate = 0; - state = statep->ls_state - = SCSPAREN; - - } - } - } - *wp++ = c; - break; - - case SBRACE: - /*{*/ - if (c == '}') { - POP_STATE(); - *wp++ = CSUBST; - *wp++ = /*{*/ '}'; - } else - goto Sbase1; - break; - - case STBRACE: - /* Same as SBRACE, except (,|,) treated specially */ - /*{*/ - if (c == '}') { - POP_STATE(); - *wp++ = CSUBST; - *wp++ = /*{*/ '}'; - } else if (c == '|') { - *wp++ = SPAT; - } else if (c == '(') { - *wp++ = OPAT; - *wp++ = ' '; /* simile for @ */ - PUSH_STATE(SPATTERN); - } else - goto Sbase1; - break; - - case SBQUOTE: - if (c == '`') { - *wp++ = 0; - POP_STATE(); - } else if (c == '\\') { - switch (c = getsc()) { - case '\\': - case '$': case '`': - *wp++ = c; - break; - case '"': - if (statep->ls_sbquote.indquotes) { - *wp++ = c; - break; - } - /* fall through.. */ - default: - if (c) { /* trailing \ is lost */ - *wp++ = '\\'; - *wp++ = c; - } - break; - } - } else - *wp++ = c; - break; - - case SWORD: /* ONEWORD */ - goto Subst; - - case SLETPAREN: /* LETEXPR: (( ... )) */ - /*(*/ - if (c == ')') { - if (statep->ls_sletparen.nparen > 0) - --statep->ls_sletparen.nparen; - /*(*/ - else if ((c2 = getsc()) == ')') { - c = 0; - *wp++ = CQUOTE; - goto Done; - } else - ungetsc(c2); - } else if (c == '(') - /* parenthesis inside quotes and backslashes - * are lost, but at&t ksh doesn't count them - * either - */ - ++statep->ls_sletparen.nparen; - goto Sbase2; - - case SHEREDELIM: /* <<,<<- delimiter */ - /* XXX chuck this state (and the next) - use - * the existing states ($ and \`..` should be - * stripped of their specialness after the - * fact). - */ - /* here delimiters need a special case since - * $ and `..` are not to be treated specially - */ - if (c == '\\') { - c = getsc(); - if (c) { /* trailing \ is lost */ - *wp++ = QCHAR; - *wp++ = c; - } - } else if (c == '\'') { - PUSH_STATE(SSQUOTE); - *wp++ = OQUOTE; - ignore_backslash_newline++; - } else if (c == '"') { - state = statep->ls_state = SHEREDQUOTE; - *wp++ = OQUOTE; - } else { - *wp++ = CHAR; - *wp++ = c; - } - break; - - case SHEREDQUOTE: /* " in <<,<<- delimiter */ - if (c == '"') { - *wp++ = CQUOTE; - state = statep->ls_state = SHEREDELIM; - } else { - if (c == '\\') { - switch (c = getsc()) { - case '\\': case '"': - case '$': case '`': - break; - default: - if (c) { /* trailing \ lost */ - *wp++ = CHAR; - *wp++ = '\\'; - } - break; - } - } - *wp++ = CHAR; - *wp++ = c; - } - break; - - case SPATTERN: /* in *(...|...) pattern (*+?@!) */ - if ( /*(*/ c == ')') { - *wp++ = CPAT; - POP_STATE(); - } else if (c == '|') { - *wp++ = SPAT; - } else if (c == '(') { - *wp++ = OPAT; - *wp++ = ' '; /* simile for @ */ - PUSH_STATE(SPATTERN); - } else - goto Sbase1; - break; - } - } -Done: - Xcheck(ws, wp); - if (statep != &states[1]) - /* XXX figure out what is missing */ - yyerror("no closing quote\n"); - - /* This done to avoid tests for SHEREDELIM wherever SBASE tested */ - if (state == SHEREDELIM) - state = SBASE; - - dp = Xstring(ws, wp); - if ((c == '<' || c == '>') && state == SBASE - && ((c2 = Xlength(ws, wp)) == 0 - || (c2 == 2 && dp[0] == CHAR && digit(dp[1])))) - { - struct ioword *iop = - (struct ioword *) alloc(sizeof(*iop), ATEMP); - - if (c2 == 2) - iop->unit = dp[1] - '0'; - else - iop->unit = c == '>'; /* 0 for <, 1 for > */ - - c2 = getsc(); - /* <<, >>, <> are ok, >< is not */ - if (c == c2 || (c == '<' && c2 == '>')) { - iop->flag = c == c2 ? - (c == '>' ? IOCAT : IOHERE) : IORDWR; - if (iop->flag == IOHERE) { - if ((c2 = getsc()) == '-') - iop->flag |= IOSKIP; - else - ungetsc(c2); - } - } else if (c2 == '&') - iop->flag = IODUP | (c == '<' ? IORDUP : 0); - else { - iop->flag = c == '>' ? IOWRITE : IOREAD; - if (c == '>' && c2 == '|') - iop->flag |= IOCLOB; - else - ungetsc(c2); - } - - iop->name = NULL; - iop->delim = NULL; - iop->heredoc = NULL; - Xfree(ws, wp); /* free word */ - yylval.iop = iop; - return REDIR; - } - - if (wp == dp && state == SBASE) { - Xfree(ws, wp); /* free word */ - /* no word, process LEX1 character */ - switch (c) { - default: - return c; - - case '|': - case '&': - case ';': - if ((c2 = getsc()) == c) - c = (c == ';') ? BREAK : - (c == '|') ? LOGOR : - (c == '&') ? LOGAND : - YYERRCODE; - else if (c == '|' && c2 == '&') - c = COPROC; - else - ungetsc(c2); - return c; - - case '\n': - gethere(); - if (cf & CONTIN) - goto Again; - return c; - - case '(': /*)*/ - if (!Flag(FSH)) { - if ((c2 = getsc()) == '(') /*)*/ - /* XXX need to handle ((...); (...)) */ - c = MDPAREN; - else - ungetsc(c2); - } - return c; - /*(*/ - case ')': - return c; - } - } - - *wp++ = EOS; /* terminate word */ - yylval.cp = Xclose(ws, wp); - if (state == SWORD || state == SLETPAREN) - /* ONEWORD? */ - return LWORD; - ungetsc(c); /* unget terminator */ - - /* copy word to unprefixed string ident */ - for (sp = yylval.cp, dp = ident; dp < ident+IDENT && (c = *sp++) == CHAR; ) - *dp++ = *sp++; - /* Make sure the ident array stays '\0' padded */ - memset(dp, 0, (ident+IDENT) - dp + 1); - if (c != EOS) - *ident = '\0'; /* word is not unquoted */ - - if (*ident != '\0' && (cf&(KEYWORD|ALIAS))) { - struct tbl *p; - int h = hash(ident); - - /* { */ - if ((cf & KEYWORD) && (p = tsearch(&keywords, ident, h)) - && (!(cf & ESACONLY) || p->val.i == ESAC || p->val.i == '}')) - { - afree(yylval.cp, ATEMP); - return p->val.i; - } - if ((cf & ALIAS) && (p = tsearch(&aliases, ident, h)) - && (p->flag & ISSET)) - { - Source *s; - - for (s = source; s->type == SALIAS; s = s->next) - if (s->u.tblp == p) - return LWORD; - /* push alias expansion */ - s = pushs(SALIAS, source->areap); - s->start = s->str = p->val.s; - s->u.tblp = p; - s->next = source; - source = s; - afree(yylval.cp, ATEMP); - goto Again; - } - } - - return LWORD; -} - -static void -gethere(void) -{ - struct ioword **p; - - for (p = heres; p < herep; p++) - readhere(*p); - herep = heres; -} - -/* - * read "<delim, 0); - - if (!(iop->flag & IOEVAL)) - ignore_backslash_newline++; - - Xinit(xs, xp, 256, ATEMP); - - for (;;) { - eofp = eof; - skiptabs = iop->flag & IOSKIP; - xpos = Xsavepos(xs, xp); - while ((c = getsc()) != 0) { - if (skiptabs) { - if (c == '\t') - continue; - skiptabs = 0; - } - if (c != *eofp) - break; - Xcheck(xs, xp); - Xput(xs, xp, c); - eofp++; - } - /* Allow EOF here so commands with out trailing newlines - * will work (eg, ksh -c '...', $(...), etc). - */ - if (*eofp == '\0' && (c == 0 || c == '\n')) { - xp = Xrestpos(xs, xp, xpos); - break; - } - ungetsc(c); - while ((c = getsc()) != '\n') { - if (c == 0) - yyerror("here document '%s' unclosed\n", eof); - Xcheck(xs, xp); - Xput(xs, xp, c); - } - Xcheck(xs, xp); - Xput(xs, xp, c); - } - Xput(xs, xp, '\0'); - iop->heredoc = Xclose(xs, xp); - - if (!(iop->flag & IOEVAL)) - ignore_backslash_newline--; -} - -void -yyerror(const char *fmt, ...) -{ - va_list va; - - /* pop aliases and re-reads */ - while (source->type == SALIAS || source->type == SREREAD) - source = source->next; - source->str = null; /* zap pending input */ - - error_prefix(true); - SH_VA_START(va, fmt); - shf_vfprintf(shl_out, fmt, va); - va_end(va); - errorf(null); -} - -/* - * input for yylex with alias expansion - */ - -Source * -pushs(int type, Area *areap) -{ - Source *s; - - s = (Source *) alloc(sizeof(Source), areap); - s->type = type; - s->str = null; - s->start = NULL; - s->line = 0; - s->errline = 0; - s->file = NULL; - s->flags = 0; - s->next = NULL; - s->areap = areap; - if (type == SFILE || type == SSTDIN) { - char *dummy; - Xinit(s->xs, dummy, 256, s->areap); - } else - memset(&s->xs, 0, sizeof(s->xs)); - return s; -} - -static int -getsc__(void) -{ - Source *s = source; - int c; - - while ((c = *s->str++) == 0) { - s->str = NULL; /* return 0 for EOF by default */ - switch (s->type) { - case SEOF: - s->str = null; - return 0; - - case SSTDIN: - case SFILE: - getsc_line(s); - break; - - case SWSTR: - break; - - case SSTRING: - break; - - case SWORDS: - s->start = s->str = *s->u.strv++; - s->type = SWORDSEP; - break; - - case SWORDSEP: - if (*s->u.strv == NULL) { - s->start = s->str = newline; - s->type = SEOF; - } else { - s->start = s->str = space; - s->type = SWORDS; - } - break; - - case SALIAS: - if (s->flags & SF_ALIASEND) { - /* pass on an unused SF_ALIAS flag */ - source = s->next; - source->flags |= s->flags & SF_ALIAS; - s = source; - } else if (*s->u.tblp->val.s - && isspace(strchr(s->u.tblp->val.s, 0)[-1])) - { - source = s = s->next; /* pop source stack */ - /* Note that this alias ended with a space, - * enabling alias expansion on the following - * word. - */ - s->flags |= SF_ALIAS; - } else { - /* At this point, we need to keep the current - * alias in the source list so recursive - * aliases can be detected and we also need - * to return the next character. Do this - * by temporarily popping the alias to get - * the next character and then put it back - * in the source list with the SF_ALIASEND - * flag set. - */ - source = s->next; /* pop source stack */ - source->flags |= s->flags & SF_ALIAS; - c = getsc__(); - if (c) { - s->flags |= SF_ALIASEND; - s->ugbuf[0] = c; s->ugbuf[1] = '\0'; - s->start = s->str = s->ugbuf; - s->next = source; - source = s; - } else { - s = source; - /* avoid reading eof twice */ - s->str = NULL; - break; - } - } - continue; - - case SREREAD: - if (s->start != s->ugbuf) /* yuck */ - afree(s->u.freeme, ATEMP); - source = s = s->next; - continue; - } - if (s->str == NULL) { - s->type = SEOF; - s->start = s->str = null; - return '\0'; - } - if (s->flags & SF_ECHO) { - shf_puts(s->str, shl_out); - shf_flush(shl_out); - } - } - return c; -} - -static void -getsc_line(Source *s) -{ - char *xp = Xstring(s->xs, xp); - int interactive = Flag(FTALKING) && s->type == SSTDIN; - int have_tty = interactive && (s->flags & SF_TTY); - - /* Done here to ensure nothing odd happens when a timeout occurs */ - XcheckN(s->xs, xp, LINE); - *xp = '\0'; - s->start = s->str = xp; - - if (have_tty && ksh_tmout) { - ksh_tmout_state = TMOUT_READING; - alarm(ksh_tmout); - } - if (have_tty && (Flag(FVI) || Flag(FEMACS) || Flag(FGMACS))) { - int nread; - - nread = x_read(xp, LINE); - if (nread < 0) /* read error */ - nread = 0; - xp[nread] = '\0'; - xp += nread; - } else { - if (interactive) { - pprompt(prompt, 0); - } else - s->line++; - - while (1) { - char *p = shf_getse(xp, Xnleft(s->xs, xp), s->u.shf); - - if (!p && shf_error(s->u.shf) - && shf_errno(s->u.shf) == EINTR) - { - shf_clearerr(s->u.shf); - if (trap) - runtraps(0); - continue; - } - if (!p || (xp = p, xp[-1] == '\n')) - break; - /* double buffer size */ - xp++; /* move past null so doubling works... */ - XcheckN(s->xs, xp, Xlength(s->xs, xp)); - xp--; /* ...and move back again */ - } - /* flush any unwanted input so other programs/builtins - * can read it. Not very optimal, but less error prone - * than flushing else where, dealing with redirections, - * etc.. - * todo: reduce size of shf buffer (~128?) if SSTDIN - */ - if (s->type == SSTDIN) - shf_flush(s->u.shf); - } - /* XXX: temporary kludge to restore source after a - * trap may have been executed. - */ - source = s; - if (have_tty && ksh_tmout) - { - ksh_tmout_state = TMOUT_EXECUTING; - alarm(0); - } - s->start = s->str = Xstring(s->xs, xp); - strip_nuls(Xstring(s->xs, xp), Xlength(s->xs, xp)); - /* Note: if input is all nulls, this is not eof */ - if (Xlength(s->xs, xp) == 0) { /* EOF */ - if (s->type == SFILE) - shf_fdclose(s->u.shf); - s->str = NULL; - } else if (interactive) { - char *p = Xstring(s->xs, xp); - if (cur_prompt == PS1) - while (*p && ctype(*p, C_IFS) && ctype(*p, C_IFSWS)) - p++; - if (*p) { -#ifdef EASY_HISTORY - if (cur_prompt == PS2) - histappend(Xstring(s->xs, xp), 1); - else -#endif /* EASY_HISTORY */ - { - s->line++; - histsave(s->line, s->str, 1); - } - } - } - if (interactive) - set_prompt(PS2, NULL); -} - -void -set_prompt(int to, Source *s) -{ - cur_prompt = to; - - switch (to) { - case PS1: /* command */ - /* Substitute ! and !! here, before substitutions are done - * so ! in expanded variables are not expanded. - * NOTE: this is not what at&t ksh does (it does it after - * substitutions, POSIX doesn't say which is to be done. - */ - { - struct shf *shf; - char * volatile ps1; - Area *saved_atemp; - - ps1 = str_val(global("PS1")); - shf = shf_sopen(NULL, strlen(ps1) * 2, - SHF_WR | SHF_DYNAMIC, NULL); - while (*ps1) { - if (*ps1 != '!' || *++ps1 == '!') - shf_putchar(*ps1++, shf); - else - shf_fprintf(shf, "%d", - s ? s->line + 1 : 0); - } - ps1 = shf_sclose(shf); - saved_atemp = ATEMP; - newenv(E_ERRH); - if (ksh_sigsetjmp(e->jbuf, 0)) { - prompt = safe_prompt; - /* Don't print an error - assume it has already - * been printed. Reason is we may have forked - * to run a command and the child may be - * unwinding its stack through this code as it - * exits. - */ - } else - prompt = str_save(substitute(ps1, 0), - saved_atemp); - quitenv(NULL); - } - break; - - case PS2: /* command continuation */ - prompt = str_val(global("PS2")); - break; - } -} - -/* See also related routine, promptlen() in edit.c */ -void -pprompt(const char *cp, int ntruncate) -{ - shf_puts(cp + ntruncate, shl_out); - shf_flush(shl_out); -} - -/* Read the variable part of a ${...} expression (ie, up to but not including - * the :[-+?=#%] or close-brace. - */ -static char * -get_brace_var(XString *wsp, char *wp) -{ - enum parse_state { - PS_INITIAL, PS_SAW_HASH, PS_IDENT, - PS_NUMBER, PS_VAR1, PS_END - } - state; - char c; - - state = PS_INITIAL; - while (1) { - c = getsc(); - /* State machine to figure out where the variable part ends. */ - switch (state) { - case PS_INITIAL: - if (c == '#') { - state = PS_SAW_HASH; - break; - } - /* fall through.. */ - case PS_SAW_HASH: - if (letter(c)) - state = PS_IDENT; - else if (digit(c)) - state = PS_NUMBER; - else if (ctype(c, C_VAR1)) - state = PS_VAR1; - else - state = PS_END; - break; - case PS_IDENT: - if (!letnum(c)) { - state = PS_END; - if (c == '[') { - char *tmp, *p; - - if (!arraysub(&tmp)) - yyerror("missing ]\n"); - *wp++ = c; - for (p = tmp; *p; ) { - Xcheck(*wsp, wp); - *wp++ = *p++; - } - afree(tmp, ATEMP); - c = getsc(); /* the ] */ - } - } - break; - case PS_NUMBER: - if (!digit(c)) - state = PS_END; - break; - case PS_VAR1: - state = PS_END; - break; - case PS_END: /* keep gcc happy */ - break; - } - if (state == PS_END) { - *wp++ = '\0'; /* end of variable part */ - ungetsc(c); - break; - } - Xcheck(*wsp, wp); - *wp++ = c; - } - return wp; -} - -/* - * Save an array subscript - returns true if matching bracket found, false - * if eof or newline was found. - * (Returned string double null terminated) - */ -static int -arraysub(char **strp) -{ - XString ws; - char *wp; - char c; - int depth = 1; /* we are just past the initial [ */ - - Xinit(ws, wp, 32, ATEMP); - - do { - c = getsc(); - Xcheck(ws, wp); - *wp++ = c; - if (c == '[') - depth++; - else if (c == ']') - depth--; - } while (depth > 0 && c && c != '\n'); - - *wp++ = '\0'; - *strp = Xclose(ws, wp); - - return depth == 0 ? 1 : 0; -} - -/* Unget a char: handles case when we are already at the start of the buffer */ -static const char * -ungetsc(int c) -{ - if (backslash_skip) - backslash_skip--; - /* Don't unget eof... */ - if (source->str == null && c == '\0') - return source->str; - if (source->str > source->start) - source->str--; - else { - Source *s; - - s = pushs(SREREAD, source->areap); - s->ugbuf[0] = c; s->ugbuf[1] = '\0'; - s->start = s->str = s->ugbuf; - s->next = source; - source = s; - } - return source->str; -} - - -/* Called to get a char that isn't a \newline sequence. */ -static int -getsc_bn(void) -{ - int c, c2; - - if (ignore_backslash_newline) - return getsc_(); - - if (backslash_skip == 1) { - backslash_skip = 2; - return getsc_(); - } - - backslash_skip = 0; - - while (1) { - c = getsc_(); - if (c == '\\') { - if ((c2 = getsc_()) == '\n') - /* ignore the \newline; get the next char... */ - continue; - ungetsc(c2); - backslash_skip = 1; - } - return c; - } -} - -static Lex_state * -push_state_(State_info *si, Lex_state *old_end) -{ - Lex_state *new = alloc(sizeof(Lex_state) * STATE_BSIZE, ATEMP); - - new[0].ls_info.base = old_end; - si->base = &new[0]; - si->end = &new[STATE_BSIZE]; - return &new[1]; -} - -static Lex_state * -pop_state_(State_info *si, Lex_state *old_end) -{ - Lex_state *old_base = si->base; - - si->base = old_end->ls_info.base - STATE_BSIZE; - si->end = old_end->ls_info.base; - - afree(old_base, ATEMP); - - return si->base + STATE_BSIZE - 1; -} diff --git a/lex.h b/lex.h deleted file mode 100644 index 1ebe592..0000000 --- a/lex.h +++ /dev/null @@ -1,131 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: lex.h,v 1.8 2004/11/02 22:09:24 deraadt Exp $ */ -/* $From: lex.h,v 1.4 1994/05/31 13:34:34 michael Exp $ */ - -#ifndef LEX_H -#define LEX_H - -/* - * Source input, lexer and parser - */ - -#define IDENT 64 - -typedef struct source Source; -struct source { - const char *str; /* input pointer */ - int type; /* input type */ - const char *start; /* start of current buffer */ - union { - char **strv; /* string [] */ - struct shf *shf; /* shell file */ - struct tbl *tblp; /* alias (SALIAS) */ - char *freeme; /* also for SREREAD */ - } u; - char ugbuf[2]; /* buffer for ungetsc() (SREREAD) and - * alias (SALIAS) */ - int line; /* line number */ - int errline; /* line the error occurred on (0 if not set) */ - const char *file; /* input file name */ - int flags; /* SF_* */ - Area *areap; - XString xs; /* input buffer */ - Source *next; /* stacked source */ -}; - -/* Source.type values */ -#define SEOF 0 /* input EOF */ -#define SFILE 1 /* file input */ -#define SSTDIN 2 /* read stdin */ -#define SSTRING 3 /* string */ -#define SWSTR 4 /* string without \n */ -#define SWORDS 5 /* string[] */ -#define SWORDSEP 6 /* string[] separator */ -#define SALIAS 7 /* alias expansion */ -#define SREREAD 8 /* read ahead to be re-scanned */ - -/* Source.flags values */ -#define SF_ECHO BIT(0) /* echo input to shlout */ -#define SF_ALIAS BIT(1) /* faking space at end of alias */ -#define SF_ALIASEND BIT(2) /* faking space at end of alias */ -#define SF_TTY BIT(3) /* type == SSTDIN & it is a tty */ - -/* - * states while lexing word - */ -#define SBASE 0 /* outside any lexical constructs */ -#define SWORD 1 /* implicit quoting for substitute() */ -#define SLETPAREN 2 /* inside (( )), implicit quoting */ -#define SSQUOTE 3 /* inside '' */ -#define SDQUOTE 4 /* inside "" */ -#define SBRACE 5 /* inside ${} */ -#define SCSPAREN 6 /* inside $() */ -#define SBQUOTE 7 /* inside `` */ -#define SASPAREN 8 /* inside $(( )) */ -#define SHEREDELIM 9 /* parsing <<,<<- delimiter */ -#define SHEREDQUOTE 10 /* parsing " in <<,<<- delimiter */ -#define SPATTERN 11 /* parsing *(...|...) pattern (*+?@!) */ -#define STBRACE 12 /* parsing ${..[#%]..} */ - -typedef union { - int i; - char *cp; - char **wp; - struct op *o; - struct ioword *iop; -} YYSTYPE; - -/* If something is added here, add it to tokentab[] in syn.c as well */ -#define LWORD 256 -#define LOGAND 257 /* && */ -#define LOGOR 258 /* || */ -#define BREAK 259 /* ;; */ -#define IF 260 -#define THEN 261 -#define ELSE 262 -#define ELIF 263 -#define FI 264 -#define CASE 265 -#define ESAC 266 -#define FOR 267 -#define SELECT 268 -#define WHILE 269 -#define UNTIL 270 -#define DO 271 -#define DONE 272 -#define IN 273 -#define FUNCTION 274 -#define TIME 275 -#define REDIR 276 -#define MDPAREN 277 /* (( )) */ -#define BANG 278 /* ! */ -#define DBRACKET 279 /* [[ .. ]] */ -#define COPROC 280 /* |& */ -#define YYERRCODE 300 - -/* flags to yylex */ -#define CONTIN BIT(0) /* skip new lines to complete command */ -#define ONEWORD BIT(1) /* single word for substitute() */ -#define ALIAS BIT(2) /* recognize alias */ -#define KEYWORD BIT(3) /* recognize keywords */ -#define LETEXPR BIT(4) /* get expression inside (( )) */ -#define VARASN BIT(5) /* check for var=word */ -#define ARRAYVAR BIT(6) /* parse x[1 & 2] as one word */ -#define ESACONLY BIT(7) /* only accept esac keyword */ -#define CMDWORD BIT(8) /* parsing simple command (alias related) */ -#define HEREDELIM BIT(9) /* parsing <<,<<- delimiter */ - -#define HERES 10 /* max << in line */ - -EXTERN Source *source; /* yyparse/yylex source */ -EXTERN YYSTYPE yylval; /* result from yylex */ -EXTERN struct ioword *heres [HERES], **herep; -EXTERN char ident [IDENT+1]; - -#define HISTORYSIZE 511 /* size of saved history */ - -EXTERN char **history; /* saved commands */ -EXTERN char **histptr; /* last history item */ -EXTERN int histsize; /* history size */ - -#endif /* ndef LEX_H */ diff --git a/main.c b/main.c deleted file mode 100644 index 82b798b..0000000 --- a/main.c +++ /dev/null @@ -1,751 +0,0 @@ -/** $MirOS: src/bin/ksh/main.c,v 1.2 2005/05/21 16:36:53 tg Exp $ */ -/* $OpenBSD: main.c,v 1.35 2004/12/22 18:57:28 otto Exp $ */ - -/* - * startup, main loop, environments and error handling - */ - -#define EXTERN /* define EXTERNs in sh.h */ - -#include "sh.h" -#include "ksh_stat.h" -#include - -/* - * shell version - */ - -__RCSID("$MirOS: src/bin/ksh/main.c,v 1.2 2005/05/21 16:36:53 tg Exp $"); - -const char ksh_version[] = - "@(#)PD KSH v5.2.14 MirOS R20u in " -#ifdef MIRBSD_NATIVE - "native " -#endif - "KSH mode" -#ifndef MIRBSD_NATIVE - " as mksh" -#endif - ; - -/* - * global data - */ - -extern char **environ; - -static void reclaim(void); -static void remove_temps(struct temp *tp); -static int is_restricted(char *name); - -/* - * shell initialization - */ - -static const char initifs[] = "IFS= \t\n"; - -static const char initsubs[] = "${PS2=> } ${PS3=#? } ${PS4=+ }"; - -static const char *const initcoms [] = { - "typeset", "-x", "SHELL", "PATH", "HOME", NULL, - "typeset", "-r", "KSH_VERSION", NULL, - "typeset", "-i", "PGRP=0", "PPID", NULL, - "typeset", "-i", "OPTIND=1", NULL, - "eval", "typeset -i RANDOM SECONDS=\"${SECONDS-0}\" TMOUT=\"${TMOUT-0}\"", NULL, - "alias", - /* Standard ksh aliases */ - "hash=alias -t", /* not "alias -t --": hash -r needs to work */ - "type=whence -v", -#ifdef JOBS - "stop=kill -STOP", - "suspend=kill -STOP $$", -#endif - "autoload=typeset -fu", - "functions=typeset -f", - "history=fc -l", - "integer=typeset -i", - "nohup=nohup ", - "local=typeset", - "r=fc -e -", - /* Aliases that are builtin commands in at&t */ - "login=exec login", - NULL, - /* this is what at&t ksh seems to track, with the addition of emacs */ - "alias", "-tU", - "cat", "cc", "chmod", "cp", "date", "ed", "emacs", "grep", "ls", - "mail", "make", "mv", "pr", "rm", "sed", "sh", "vi", "who", - NULL, -#ifdef EXTRA_INITCOMS - EXTRA_INITCOMS, NULL, -#endif /* EXTRA_INITCOMS */ - NULL -}; - -int -main(int argc, char *argv[]) -{ - int i; - int argi; - Source *s; - struct block *l; - int restricted, errexit; - char **wp; - struct env env; - pid_t ppid; - -#ifdef MEM_DEBUG - chmem_set_defaults("ct", 1); - /* chmem_push("+c", 1); */ -#endif /* MEM_DEBUG */ - - /* make sure argv[] is sane */ - if (!*argv) { - static const char *empty_argv[] = {"mksh", NULL}; - - argv = (char **) empty_argv; - argc = 1; - } - kshname = *argv; - - ainit(&aperm); /* initialize permanent Area */ - - /* set up base environment */ - memset(&env, 0, sizeof(env)); - env.type = E_NONE; - ainit(&env.area); - e = &env; - newblock(); /* set up global l->vars and l->funs */ - - /* Do this first so output routines (eg, errorf, shellf) can work */ - initio(); - - argi = parse_args(argv, OF_FIRSTTIME, NULL); - if (argi < 0) - exit(1); - - initvar(); - - initctypes(); - - inittraps(); - - coproc_init(); - - /* set up variable and command dictionaries */ - tinit(&taliases, APERM, 0); - tinit(&aliases, APERM, 0); - tinit(&homedirs, APERM, 0); - - /* define shell keywords */ - initkeywords(); - - /* define built-in commands */ - tinit(&builtins, APERM, 64); /* must be 2^n (currently 40 builtins) */ - for (i = 0; shbuiltins[i].name != NULL; i++) - builtin(shbuiltins[i].name, shbuiltins[i].func); - for (i = 0; kshbuiltins[i].name != NULL; i++) - builtin(kshbuiltins[i].name, kshbuiltins[i].func); - - init_histvec(); - - def_path = DEFAULT__PATH; -#if defined(HAVE_CONFSTR) && defined(_CS_PATH) - { - size_t len = confstr(_CS_PATH, NULL, 0); - char *new; - - if (len > 0) { - confstr(_CS_PATH, new = alloc(len + 1, APERM), len + 1); - def_path = new; - } - } -#endif /* HAVE_CONFSTR && _CS_PATH */ - - /* Set PATH to def_path (will set the path global variable). - * (import of environment below will probably change this setting). - */ - { - struct tbl *vp = global("PATH"); - /* setstr can't fail here */ - setstr(vp, def_path, KSH_RETURN_ERROR); - } - - - /* Turn on nohup by default for now - will change to off - * by default once people are aware of its existence - * (at&t ksh does not have a nohup option - it always sends - * the hup). - */ - Flag(FNOHUP) = 1; - - /* Turn on brace expansion by default. At&t ksh's that have - * alternation always have it on. BUT, posix doesn't have - * brace expansion, so set this before setting up FPOSIX - * (change_flag() clears FBRACEEXPAND when FPOSIX is set). - */ - Flag(FBRACEEXPAND) = 1; - - /* Check to see if we're /bin/sh. */ - if (!strcmp(&kshname[strlen(kshname) - 3], "/sh") - || !strcmp(kshname, "sh") || !strcmp(kshname, "-sh")) - Flag(FSH) = 1; - - /* Set edit mode to emacs by default, may be overridden - * by the environment or the user. Also, we want tab completion - * on in vi by default. */ - change_flag(FEMACS, OF_SPECIAL, 1); - Flag(FVITABCOMPLETE) = 1; - - /* import environment */ - if (environ != NULL) - for (wp = environ; *wp != NULL; wp++) - typeset(*wp, IMPORT|EXPORT, 0, 0, 0); - - kshpid = procpid = getpid(); - typeset(initifs, 0, 0, 0, 0); /* for security */ - - /* assign default shell variable values */ - substitute(initsubs, 0); - - /* Figure out the current working directory and set $PWD */ - { - struct stat s_pwd, s_dot; - struct tbl *pwd_v = global("PWD"); - char *pwd = str_val(pwd_v); - char *pwdx = pwd; - - /* Try to use existing $PWD if it is valid */ - if (!ISABSPATH(pwd) - || stat(pwd, &s_pwd) < 0 || stat(".", &s_dot) < 0 - || s_pwd.st_dev != s_dot.st_dev - || s_pwd.st_ino != s_dot.st_ino) - pwdx = NULL; - set_current_wd(pwdx); - if (current_wd[0]) - simplify_path(current_wd); - /* Only set pwd if we know where we are or if it had a - * bogus value - */ - if (current_wd[0] || pwd != null) - /* setstr can't fail here */ - setstr(pwd_v, current_wd, KSH_RETURN_ERROR); - } - ppid = getppid(); - rnd_seed( (*((long *)kshname)) ^ ((long)time(NULL) * kshpid * ppid) ); - setint(global("PPID"), (long) ppid); - /* Note: don't setint here, it loops into rnd_put() */ - rnd_get(); - if (!Flag(FSH)) - setstr(global("KSH_VERSION"), ksh_version, KSH_RETURN_ERROR); - - /* execute initialization statements */ - for (wp = (char**) initcoms; *wp != NULL; wp++) { - shcomexec(wp); - for (; *wp != NULL; wp++) - ; - } - - - ksheuid = geteuid(); - safe_prompt = ksheuid ? "$ " : "# "; - { - struct tbl *vp = global("PS1"); - - /* Set PS1 if it isn't set, or we are root and prompt doesn't - * contain a #. - */ - if (!(vp->flag & ISSET) - || (!ksheuid && !strchr(str_val(vp), '#'))) - /* setstr can't fail here */ - setstr(vp, safe_prompt, KSH_RETURN_ERROR); - } - - /* Set this before parsing arguments */ - Flag(FPRIVILEGED) = getuid() != ksheuid || getgid() != getegid(); - - /* this to note if monitor is set on command line (see below) */ - Flag(FMONITOR) = 127; - argi = parse_args(argv, OF_CMDLINE, NULL); - if (argi < 0) - exit(1); - - if (Flag(FCOMMAND)) { - s = pushs(SSTRING, ATEMP); - if (!(s->start = s->str = argv[argi++])) - errorf("-c requires an argument"); - if (argv[argi]) - kshname = argv[argi++]; - } else if (argi < argc && !Flag(FSTDIN)) { - s = pushs(SFILE, ATEMP); - s->file = argv[argi++]; - s->u.shf = shf_open(s->file, O_RDONLY, 0, SHF_MAPHI|SHF_CLEXEC); - if (s->u.shf == NULL) { - exstat = 127; /* POSIX */ - errorf("%s: %s", s->file, strerror(errno)); - } - kshname = s->file; - } else { - Flag(FSTDIN) = 1; - s = pushs(SSTDIN, ATEMP); - s->file = ""; - s->u.shf = shf_fdopen(0, SHF_RD | can_seek(0), - NULL); - if (isatty(0) && isatty(2)) { - Flag(FTALKING) = Flag(FTALKING_I) = 1; - /* The following only if isatty(0) */ - s->flags |= SF_TTY; - s->u.shf->flags |= SHF_INTERRUPT; - s->file = NULL; - } - } - - /* This bizarreness is mandated by POSIX */ - { - struct stat s_stdin; - - if (fstat(0, &s_stdin) >= 0 && S_ISCHR(s_stdin.st_mode) && - Flag(FTALKING)) - reset_nonblock(0); - } - - /* initialize job control */ - i = Flag(FMONITOR) != 127; - Flag(FMONITOR) = 0; - j_init(i); - /* Do this after j_init(), as tty_fd is not initialized 'til then */ - if (Flag(FTALKING)) - x_init(); - - l = e->loc; - l->argv = &argv[argi - 1]; - l->argc = argc - argi; - l->argv[0] = (char *) kshname; - getopts_reset(1); - - /* Disable during .profile/ENV reading */ - restricted = Flag(FRESTRICTED); - Flag(FRESTRICTED) = 0; - errexit = Flag(FERREXIT); - Flag(FERREXIT) = 0; - - /* Do this before profile/$ENV so that if it causes problems in them, - * user will know why things broke. - */ - if (!current_wd[0] && Flag(FTALKING)) - warningf(false, "Cannot determine current working directory"); - - if (Flag(FLOGIN)) { - include(KSH_SYSTEM_PROFILE, 0, NULL, 1); - if (!Flag(FPRIVILEGED)) - include(substitute("$HOME/.profile", 0), 0, - NULL, 1); - } - - if (Flag(FPRIVILEGED)) - include("/etc/suid_profile", 0, NULL, 1); - else { - char *env_file; - - /* include $ENV */ - env_file = str_val(global("ENV")); - -#ifdef DEFAULT_ENV - /* If env isn't set, include default environment */ - if (env_file == null) - env_file = DEFAULT_ENV; -#endif /* DEFAULT_ENV */ - env_file = substitute(env_file, DOTILDE); - if (*env_file != '\0') - include(env_file, 0, NULL, 1); - } - - if (is_restricted(argv[0]) || is_restricted(str_val(global("SHELL")))) - restricted = 1; - if (restricted) { - static const char *const restr_com[] = { - "typeset", "-r", "PATH", - "ENV", "SHELL", - NULL - }; - shcomexec((char **) restr_com); - /* After typeset command... */ - Flag(FRESTRICTED) = 1; - } - if (errexit) - Flag(FERREXIT) = 1; - - if (Flag(FTALKING)) { - hist_init(s); - alarm_init(); - } else - Flag(FTRACKALL) = 1; /* set after ENV */ - - shell(s, true); /* doesn't return */ - return 0; -} - -int -include(const char *name, int argc, char **argv, int intr_ok) -{ - Source *volatile s = NULL; - struct shf *shf; - char **volatile old_argv; - volatile int old_argc; - int i; - - shf = shf_open(name, O_RDONLY, 0, SHF_MAPHI|SHF_CLEXEC); - if (shf == NULL) - return -1; - - if (argv) { - old_argv = e->loc->argv; - old_argc = e->loc->argc; - } else { - old_argv = NULL; - old_argc = 0; - } - newenv(E_INCL); - i = ksh_sigsetjmp(e->jbuf, 0); - if (i) { - quitenv(s ? s->u.shf : NULL); - if (old_argv) { - e->loc->argv = old_argv; - e->loc->argc = old_argc; - } - switch (i) { - case LRETURN: - case LERROR: - return exstat & 0xff; /* see below */ - case LINTR: - /* intr_ok is set if we are including .profile or $ENV. - * If user ^C's out, we don't want to kill the shell... - */ - if (intr_ok && (exstat - 128) != SIGTERM) - return 1; - /* fall through... */ - case LEXIT: - case LLEAVE: - case LSHELL: - unwind(i); - /*NOREACHED*/ - default: - internal_errorf(1, "include: %d", i); - /*NOREACHED*/ - } - } - if (argv) { - e->loc->argv = argv; - e->loc->argc = argc; - } - s = pushs(SFILE, ATEMP); - s->u.shf = shf; - s->file = str_save(name, ATEMP); - i = shell(s, false); - quitenv(s->u.shf); - if (old_argv) { - e->loc->argv = old_argv; - e->loc->argc = old_argc; - } - return i & 0xff; /* & 0xff to ensure value not -1 */ -} - -int -command(const char *comm) -{ - Source *s; - - s = pushs(SSTRING, ATEMP); - s->start = s->str = comm; - return shell(s, false); -} - -/* - * run the commands from the input source, returning status. - */ -int -shell(Source *volatile s, volatile int toplevel) - /* input source */ - -{ - struct op *t; - volatile int wastty = s->flags & SF_TTY; - volatile int attempts = 13; - volatile int interactive = Flag(FTALKING) && toplevel; - Source *volatile old_source = source; - int i; - - newenv(E_PARSE); - if (interactive) - really_exit = 0; - i = ksh_sigsetjmp(e->jbuf, 0); - if (i) { - switch (i) { - case LINTR: /* we get here if SIGINT not caught or ignored */ - case LERROR: - case LSHELL: - if (interactive) { - if (i == LINTR) - shellf(newline); - /* Reset any eof that was read as part of a - * multiline command. - */ - if (Flag(FIGNOREEOF) && s->type == SEOF - && wastty) - s->type = SSTDIN; - /* Used by exit command to get back to - * top level shell. Kind of strange since - * interactive is set if we are reading from - * a tty, but to have stopped jobs, one only - * needs FMONITOR set (not FTALKING/SF_TTY)... - */ - /* toss any input we have so far */ - s->start = s->str = null; - break; - } - /* fall through... */ - case LEXIT: - case LLEAVE: - case LRETURN: - source = old_source; - quitenv(NULL); - unwind(i); /* keep on going */ - /*NOREACHED*/ - default: - source = old_source; - quitenv(NULL); - internal_errorf(1, "shell: %d", i); - /*NOREACHED*/ - } - } - - while (1) { - if (trap) - runtraps(0); - - if (s->next == NULL) { - if (Flag(FVERBOSE)) - s->flags |= SF_ECHO; - else - s->flags &= ~SF_ECHO; - } - - if (interactive) { - j_notify(); - set_prompt(PS1, s); - } - - t = compile(s); - if (t != NULL && t->type == TEOF) { - if (wastty && Flag(FIGNOREEOF) && --attempts > 0) { - shellf("Use 'exit' to leave ksh\n"); - s->type = SSTDIN; - } else if (wastty && !really_exit - && j_stopped_running()) - { - really_exit = 1; - s->type = SSTDIN; - } else { - /* this for POSIX, which says EXIT traps - * shall be taken in the environment - * immediately after the last command - * executed. - */ - if (toplevel) - unwind(LEXIT); - break; - } - } - - if (t && (!Flag(FNOEXEC) || (s->flags & SF_TTY))) - exstat = execute(t, 0); - - if (t != NULL && t->type != TEOF && interactive && really_exit) - really_exit = 0; - - reclaim(); - } - quitenv(NULL); - source = old_source; - return exstat; -} - -/* return to closest error handler or shell(), exit if none found */ -void -unwind(int i) -{ - /* ordering for EXIT vs ERR is a bit odd (this is what at&t ksh does) */ - if (i == LEXIT || (Flag(FERREXIT) && (i == LERROR || i == LINTR) - && sigtraps[SIGEXIT_].trap)) - { - runtrap(&sigtraps[SIGEXIT_]); - i = LLEAVE; - } else if (Flag(FERREXIT) && (i == LERROR || i == LINTR)) { - runtrap(&sigtraps[SIGERR_]); - i = LLEAVE; - } - while (1) { - switch (e->type) { - case E_PARSE: - case E_FUNC: - case E_INCL: - case E_LOOP: - case E_ERRH: - ksh_siglongjmp(e->jbuf, i); - /*NOTREACHED*/ - - case E_NONE: - if (i == LINTR) - e->flags |= EF_FAKE_SIGDIE; - /* Fall through... */ - - default: - quitenv(NULL); - } - } -} - -void -newenv(int type) -{ - struct env *ep; - - ep = (struct env *) alloc(sizeof(*ep), ATEMP); - ep->type = type; - ep->flags = 0; - ainit(&ep->area); - ep->loc = e->loc; - ep->savefd = NULL; - ep->oenv = e; - ep->temps = NULL; - e = ep; -} - -void -quitenv(struct shf *shf) -{ - struct env *ep = e; - int fd; - - if (ep->oenv && ep->oenv->loc != ep->loc) - popblock(); - if (ep->savefd != NULL) { - for (fd = 0; fd < NUFILE; fd++) - /* if ep->savefd[fd] < 0, means fd was closed */ - if (ep->savefd[fd]) - restfd(fd, ep->savefd[fd]); - if (ep->savefd[2]) /* Clear any write errors */ - shf_reopen(2, SHF_WR, shl_out); - } - - /* Bottom of the stack. - * Either main shell is exiting or cleanup_parents_env() was called. - */ - if (ep->oenv == NULL) { - if (ep->type == E_NONE) { /* Main shell exiting? */ - if (Flag(FTALKING)) - hist_finish(); - j_exit(); - if (ep->flags & EF_FAKE_SIGDIE) { - int sig = exstat - 128; - - /* ham up our death a bit (at&t ksh - * only seems to do this for SIGTERM) - * Don't do it for SIGQUIT, since we'd - * dump a core.. - */ - if ((sig == SIGINT || sig == SIGTERM) && - getpgrp() == kshpid) { - setsig(&sigtraps[sig], SIG_DFL, - SS_RESTORE_CURR|SS_FORCE); - kill(0, sig); - } - } -#ifdef MEM_DEBUG - chmem_allfree(); -#endif /* MEM_DEBUG */ - } - if (shf) - shf_close(shf); - reclaim(); - exit(exstat); - } - if (shf) - shf_close(shf); - reclaim(); - - e = e->oenv; - afree(ep, ATEMP); -} - -/* Called after a fork to cleanup stuff left over from parents environment */ -void -cleanup_parents_env(void) -{ - struct env *ep; - int fd; - - /* Don't clean up temporary files - parent will probably need them. - * Also, can't easily reclaim memory since variables, etc. could be - * anywhere. - */ - - /* close all file descriptors hiding in savefd */ - for (ep = e; ep; ep = ep->oenv) { - if (ep->savefd) { - for (fd = 0; fd < NUFILE; fd++) - if (ep->savefd[fd] > 0) - close(ep->savefd[fd]); - afree(ep->savefd, &ep->area); - ep->savefd = NULL; - } - } - e->oenv = NULL; -} - -/* Called just before an execve cleanup stuff temporary files */ -void -cleanup_proc_env(void) -{ - struct env *ep; - - for (ep = e; ep; ep = ep->oenv) - remove_temps(ep->temps); -} - -/* remove temp files and free ATEMP Area */ -static void -reclaim(void) -{ - remove_temps(e->temps); - e->temps = NULL; - afreeall(&e->area); -} - -static void -remove_temps(struct temp *tp) -{ - for (; tp != NULL; tp = tp->next) - if (tp->pid == procpid) - unlink(tp->name); -} - -/* Returns true if name refers to a restricted shell */ -static int -is_restricted(char *name) -{ - char *p; - - if ((p = ksh_strrchr_dirsep(name))) - name = p; - /* accepts rsh, rksh, rpdksh, pdrksh, etc. */ - return (p = strchr(name, 'r')) && strstr(p, "sh") - && !strstr(p-2, "mirbsdksh") && !strstr(p-2, "mirosksh"); -} - -void -aerror(Area *ap GCC_FUNC_ATTR(unused), const char *msg) -{ - internal_errorf(1, "alloc: %s", msg); - errorf(null); /* this is never executed - keeps gcc quiet */ - /*NOTREACHED*/ -} diff --git a/misc.c b/misc.c deleted file mode 100644 index 444b14c..0000000 --- a/misc.c +++ /dev/null @@ -1,1329 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: misc.c,v 1.20 2003/10/22 07:40:38 jmc Exp $ */ - -/* - * Miscellaneous functions - */ - -#include "sh.h" -#include /* for FILECHCONV */ -#include -#include -#include "ksh_stat.h" - -__RCSID("$MirOS$"); - -#ifndef UCHAR_MAX -# define UCHAR_MAX 0xFF -#endif - -short ctypes [UCHAR_MAX+1]; /* type bits for unsigned char */ - -static int do_gmatch(const unsigned char *s, const unsigned char *p, - const unsigned char *se, const unsigned char *pe, - int isfile); -static const unsigned char *cclass(const unsigned char *p, int sub); -static int parse_T(char *); - -/* - * Fast character classes - */ -void -setctypes(const char *s, int t) -{ - unsigned i; - - if (t & C_IFS) { - for (i = 0; i < UCHAR_MAX+1; i++) - ctypes[i] &= ~C_IFS; - ctypes[0] |= C_IFS; /* include \0 in C_IFS */ - } - while (*s != 0) - ctypes[(unsigned char) *s++] |= t; -} - -void -initctypes(void) -{ - int c; - - for (c = 'a'; c <= 'z'; c++) - ctypes[c] |= C_ALPHA; - for (c = 'A'; c <= 'Z'; c++) - ctypes[c] |= C_ALPHA; - ctypes['_'] |= C_ALPHA; - setctypes("0123456789", C_DIGIT); - setctypes(" \t\n|&;<>()", C_LEX1); /* \0 added automatically */ - setctypes("*@#!$-?", C_VAR1); - setctypes(" \t\n", C_IFSWS); - setctypes("=-+?", C_SUBOP1); - setctypes("#%", C_SUBOP2); - setctypes(" \n\t\"#$&'()*;<>?[\\`|", C_QUOTE); -} - -/* convert unsigned long to base N string */ - -char * -ulton(long unsigned int n, int base) -{ - char *p; - static char buf [20]; - - p = &buf[sizeof(buf)]; - *--p = '\0'; - do { - *--p = "0123456789ABCDEF"[n%base]; - n /= base; - } while (n != 0); - return p; -} - -char * -str_save(const char *s, Area *ap) -{ - size_t len; - char *p; - - if (!s) - return NULL; - len = strlen(s)+1; - p = alloc(len, ap); - strlcpy(p, s, len+1); - return (p); -} - -/* Allocate a string of size n+1 and copy upto n characters from the possibly - * null terminated string s into it. Always returns a null terminated string - * (unless n < 0). - */ -char * -str_nsave(const char *s, int n, Area *ap) -{ - char *ns; - - if (n < 0) - return 0; - ns = alloc(n + 1, ap); - ns[0] = '\0'; - return strncat(ns, s, n); -} - -/* called from expand.h:XcheckN() to grow buffer */ -char * -Xcheck_grow_(XString *xsp, char *xp, size_t more) -{ - char *old_beg = xsp->beg; - - xsp->len += more > xsp->len ? more : xsp->len; - xsp->beg = aresize(xsp->beg, xsp->len + 8, xsp->areap); - xsp->end = xsp->beg + xsp->len; - return xsp->beg + (xp - old_beg); -} - -const struct option options[] = { - /* Special cases (see parse_args()): -A, -o, -s. - * Options are sorted by their longnames - the order of these - * entries MUST match the order of sh_flag F* enumerations in sh.h. - */ - { "allexport", 'a', OF_ANY }, - { "braceexpand", 0, OF_ANY }, /* non-standard */ - { "bgnice", 0, OF_ANY }, - { NULL, 'c', OF_CMDLINE }, - { "emacs", 0, OF_ANY }, - { "emacs-usemeta", 0, OF_ANY }, /* non-standard */ - { "errexit", 'e', OF_ANY }, - { "gmacs", 0, OF_ANY }, - { "ignoreeof", 0, OF_ANY }, - { "interactive",'i', OF_CMDLINE }, - { "keyword", 'k', OF_ANY }, - { "login", 'l', OF_CMDLINE }, - { "markdirs", 'X', OF_ANY }, -#ifdef JOBS - { "monitor", 'm', OF_ANY }, -#else /* JOBS */ - { NULL, 'm', 0 }, /* so FMONITOR not ifdef'd */ -#endif /* JOBS */ - { "noclobber", 'C', OF_ANY }, - { "noexec", 'n', OF_ANY }, - { "noglob", 'f', OF_ANY }, - { "nohup", 0, OF_ANY }, - { "nolog", 0, OF_ANY }, /* no effect */ -#ifdef JOBS - { "notify", 'b', OF_ANY }, -#endif /* JOBS */ - { "nounset", 'u', OF_ANY }, - { "physical", 0, OF_ANY }, /* non-standard */ - { "posix", 0, OF_ANY }, /* non-standard */ - { "privileged", 'p', OF_ANY }, - { "restricted", 'r', OF_CMDLINE }, - { "sh", 0, OF_ANY }, /* non-standard */ - { "stdin", 's', OF_CMDLINE }, /* pseudo non-standard */ - { "trackall", 'h', OF_ANY }, - { "verbose", 'v', OF_ANY }, - { "vi", 0, OF_ANY }, - { "viraw", 0, OF_ANY }, /* no effect */ - { "vi-show8", 0, OF_ANY }, /* non-standard */ - { "vi-tabcomplete", 0, OF_ANY }, /* non-standard */ - { "vi-esccomplete", 0, OF_ANY }, /* non-standard */ - { "xtrace", 'x', OF_ANY }, - /* Anonymous flags: used internally by shell only - * (not visible to user) - */ - { NULL, 0, OF_INTERNAL }, /* FTALKING_I */ -}; - -/* - * translate -o option into F* constant (also used for test -o option) - */ -int -option(const char *n) -{ - unsigned i; - - for (i = 0; i < NELEM(options); i++) - if (options[i].name && strcmp(options[i].name, n) == 0) - return i; - - return -1; -} - -struct options_info { - int opt_width; - struct { - const char *name; - int flag; - } opts[NELEM(options)]; -}; - -static char *options_fmt_entry(void *arg, int i, char *buf, int buflen); -static void printoptions(int verbose); - -/* format a single select menu item */ -static char * -options_fmt_entry(void *arg, int i, char *buf, int buflen) -{ - struct options_info *oi = (struct options_info *) arg; - - shf_snprintf(buf, buflen, "%-*s %s", - oi->opt_width, oi->opts[i].name, - Flag(oi->opts[i].flag) ? "on" : "off"); - return buf; -} - -static void -printoptions(int verbose) -{ - unsigned i; - - if (verbose) { - struct options_info oi; - int n, len; - - /* verbose version */ - shprintf("Current option settings\n"); - - for (i = n = oi.opt_width = 0; i < NELEM(options); i++) - if (options[i].name) { - len = strlen(options[i].name); - oi.opts[n].name = options[i].name; - oi.opts[n++].flag = i; - if (len > oi.opt_width) - oi.opt_width = len; - } - print_columns(shl_stdout, n, options_fmt_entry, &oi, - oi.opt_width + 5, 1); - } else { - /* short version ala ksh93 */ - shprintf("set"); - for (i = 0; i < NELEM(options); i++) - if (Flag(i) && options[i].name) - shprintf(" -o %s", options[i].name); - shprintf(newline); - } -} - -char * -getoptions(void) -{ - unsigned i; - char m[(int) FNFLAGS + 1]; - char *cp = m; - - for (i = 0; i < NELEM(options); i++) - if (options[i].c && Flag(i)) - *cp++ = options[i].c; - *cp = 0; - return str_save(m, ATEMP); -} - -/* change a Flag(*) value; takes care of special actions */ -void -change_flag(enum sh_flag f, int what, int newval) - /* flag to change */ - /* what is changing the flag (command line vs set) */ - -{ - int oldval; - - oldval = Flag(f); - Flag(f) = newval; -#ifdef JOBS - if (f == FMONITOR) { - if (what != OF_CMDLINE && newval != oldval) - j_change(); - } else -#endif /* JOBS */ - if (f == FVI || f == FEMACS || f == FGMACS) { - if (newval) { - Flag(FVI) = 0; - Flag(FEMACS) = Flag(FGMACS) = 0; - Flag(f) = newval; - } - } else - /* Turning off -p? */ - if (f == FPRIVILEGED && oldval && !newval) { - seteuid(ksheuid = getuid()); - setuid(ksheuid); - setegid(getgid()); - setgid(getgid()); - } else if (f == FPOSIX && newval) { - Flag(FBRACEEXPAND) = 0; - } - /* Changing interactive flag? */ - if (f == FTALKING) { - if ((what == OF_CMDLINE || what == OF_SET) - && procpid == kshpid) - Flag(FTALKING_I) = newval; - } -} - -/* parse command line & set command arguments. returns the index of - * non-option arguments, -1 if there is an error. - */ -int -parse_args(char **argv, int what, int *setargsp) - - /* OF_CMDLINE or OF_SET */ - -{ - static char cmd_opts[NELEM(options) + 5]; /* o:T:\0 */ - static char set_opts[NELEM(options) + 5]; /* Ao;s\0 */ - char *opts; - char *array = NULL; - Getopt go; - int i, optc, set, sortargs = 0, arrayset = 0; - unsigned u; - - /* First call? Build option strings... */ - if (cmd_opts[0] == '\0') { - char *p, *q; - - /* see cmd_opts[] declaration */ - strlcpy(cmd_opts, "o:T:", sizeof cmd_opts); - p = cmd_opts + strlen(cmd_opts); - /* see set_opts[] declaration */ - strlcpy(set_opts, "A:o;s", sizeof set_opts); - q = set_opts + strlen(set_opts); - for (u = 0; u < NELEM(options); u++) { - if (options[u].c) { - if (options[u].flags & OF_CMDLINE) - *p++ = options[u].c; - if (options[u].flags & OF_SET) - *q++ = options[u].c; - } - } - *p = '\0'; - *q = '\0'; - } - - if (what == OF_CMDLINE) { - char *p; - /* Set FLOGIN before parsing options so user can clear - * flag using +l. - */ - Flag(FLOGIN) = (argv[0][0] == '-' - || ((p = ksh_strrchr_dirsep(argv[0])) - && *++p == '-')); - opts = cmd_opts; - } else if (what == OF_FIRSTTIME) { - opts = cmd_opts; - } else - opts = set_opts; - ksh_getopt_reset(&go, GF_ERROR|GF_PLUSOPT); - while ((optc = ksh_getopt(argv, &go, opts)) != EOF) { - set = (go.info & GI_PLUS) ? 0 : 1; - switch (optc) { - case 'A': - if (what == OF_FIRSTTIME) - break; - arrayset = set ? 1 : -1; - array = go.optarg; - break; - - case 'T': - if (what != OF_FIRSTTIME) - break; - if (parse_T(go.optarg)) - return -1; - change_flag(FTALKING, OF_CMDLINE, 1); - break; - - case 'o': - if (what == OF_FIRSTTIME) - break; - if (go.optarg == NULL) { - /* lone -o: print options - * - * Note that on the command line, -o requires - * an option (ie, can't get here if what is - * OF_CMDLINE). - */ - printoptions(set); - break; - } - i = option(go.optarg); - if (i >= 0 && set == Flag(i)) - /* Don't check the context if the flag - * isn't changing - makes "set -o interactive" - * work if you're already interactive. Needed - * if the output of "set +o" is to be used. - */ - ; - else if (i >= 0 && (options[i].flags & what)) - change_flag((enum sh_flag) i, what, set); - else { - bi_errorf("%s: bad option", go.optarg); - return -1; - } - break; - - case '?': - return -1; - - default: - if (what == OF_FIRSTTIME) - break; - /* -s: sort positional params (at&t ksh stupidity) */ - if (what == OF_SET && optc == 's') { - sortargs = 1; - break; - } - for (u = 0; u < NELEM(options); u++) - if (optc == options[u].c - && (what & options[u].flags)) - { - change_flag((enum sh_flag) u, what, - set); - break; - } - if (u == NELEM(options)) { - internal_errorf(1, "parse_args: '%c'", optc); - return -1; /* not reached */ - } - } - } - if (!(go.info & GI_MINUSMINUS) && argv[go.optind] - && (argv[go.optind][0] == '-' || argv[go.optind][0] == '+') - && argv[go.optind][1] == '\0') - { - /* lone - clears -v and -x flags */ - if (argv[go.optind][0] == '-' && !Flag(FPOSIX)) - Flag(FVERBOSE) = Flag(FXTRACE) = 0; - /* set skips lone - or + option */ - go.optind++; - } - if (setargsp) - /* -- means set $#/$* even if there are no arguments */ - *setargsp = !arrayset && ((go.info & GI_MINUSMINUS) - || argv[go.optind]); - - if (arrayset && (!*array || *skip_varname(array, false))) { - bi_errorf("%s: is not an identifier", array); - return -1; - } - if (sortargs) { - for (i = go.optind; argv[i]; i++) - ; - qsortp((void **) &argv[go.optind], (size_t) (i - go.optind), - xstrcmp); - } - if (arrayset) { - set_array(array, arrayset, argv + go.optind); - for (; argv[go.optind]; go.optind++) - ; - } - - return go.optind; -} - -/* parse a decimal number: returns 0 if string isn't a number, 1 otherwise */ -int -getn(const char *as, int *ai) -{ - char *p; - long n; - - n = strtol(as, &p, 10); - - if (!*as || *p || INT_MIN >= n || n >= INT_MAX) - return 0; - - *ai = (int)n; - return 1; -} - -/* getn() that prints error */ -int -bi_getn(const char *as, int *ai) -{ - int rv = getn(as, ai); - - if (!rv) - bi_errorf("%s: bad number", as); - return rv; -} - -/* -------- gmatch.c -------- */ - -/* - * int gmatch(string, pattern) - * char *string, *pattern; - * - * Match a pattern as in sh(1). - * pattern character are prefixed with MAGIC by expand. - */ - -int -gmatch(const char *s, const char *p, int isfile) -{ - const char *se, *pe; - - if (s == NULL || p == NULL) - return 0; - se = s + strlen(s); - pe = p + strlen(p); - /* isfile is false iff no syntax check has been done on - * the pattern. If check fails, just to a strcmp(). - */ - if (!isfile && !has_globbing(p, pe)) { - size_t len = pe - p + 1; - char tbuf[64]; - char *t = len <= sizeof(tbuf) ? tbuf - : (char *) alloc(len, ATEMP); - debunk(t, p, len); - return !strcmp(t, s); - } - return do_gmatch((const unsigned char *) s, (const unsigned char *) se, - (const unsigned char *) p, (const unsigned char *) pe, - isfile); -} - -/* Returns if p is a syntacticly correct globbing pattern, false - * if it contains no pattern characters or if there is a syntax error. - * Syntax errors are: - * - [ with no closing ] - * - imbalanced $(...) expression - * - [...] and *(...) not nested (eg, [a$(b|]c), *(a[b|c]d)) - */ -/*XXX -- if no magic, - if dest given, copy to dst - return ? -- if magic && (no globbing || syntax error) - debunk to dst - return ? -- return ? -*/ -int -has_globbing(const char *xp, const char *xpe) -{ - const unsigned char *p = (const unsigned char *) xp; - const unsigned char *pe = (const unsigned char *) xpe; - int c; - int nest = 0, bnest = 0; - int saw_glob = 0; - int in_bracket = 0; /* inside [...] */ - - for (; p < pe; p++) { - if (!ISMAGIC(*p)) - continue; - if ((c = *++p) == '*' || c == '?') - saw_glob = 1; - else if (c == '[') { - if (!in_bracket) { - saw_glob = 1; - in_bracket = 1; - if (ISMAGIC(p[1]) && p[2] == NOT) - p += 2; - if (ISMAGIC(p[1]) && p[2] == ']') - p += 2; - } - /* XXX Do we need to check ranges here? POSIX Q */ - } else if (c == ']') { - if (in_bracket) { - if (bnest) /* [a*(b]) */ - return 0; - in_bracket = 0; - } - } else if ((c & 0x80) && strchr("*+?@! ", c & 0x7f)) { - saw_glob = 1; - if (in_bracket) - bnest++; - else - nest++; - } else if (c == '|') { - if (in_bracket && !bnest) /* *(a[foo|bar]) */ - return 0; - } else if (c == /*(*/ ')') { - if (in_bracket) { - if (!bnest--) /* *(a[b)c] */ - return 0; - } else if (nest) - nest--; - } - /* else must be a MAGIC-MAGIC, or MAGIC-!, MAGIC--, MAGIC-] - MAGIC-{, MAGIC-,, MAGIC-} */ - } - return saw_glob && !in_bracket && !nest; -} - -/* Function must return either 0 or 1 (assumed by code for 0x80|'!') */ -static int -do_gmatch(const unsigned char *s, const unsigned char *se, const unsigned char *p, const unsigned char *pe, int isfile) -{ - int sc, pc; - const unsigned char *prest, *psub, *pnext; - const unsigned char *srest; - - if (s == NULL || p == NULL) - return 0; - while (p < pe) { - pc = *p++; - sc = s < se ? *s : '\0'; - s++; - if (isfile) { - sc = FILECHCONV(sc); - pc = FILECHCONV(pc); - } - if (!ISMAGIC(pc)) { - if (sc != pc) - return 0; - continue; - } - switch (*p++) { - case '[': - if (sc == 0 || (p = cclass(p, sc)) == NULL) - return 0; - break; - - case '?': - if (sc == 0) - return 0; - break; - - case '*': - if (p == pe) - return 1; - s--; - do { - if (do_gmatch(s, se, p, pe, isfile)) - return 1; - } while (s++ < se); - return 0; - - /* - * [*+?@!](pattern|pattern|..) - * ${..%..}, etc. - */ - case 0x80|'+': /* matches one or more times */ - case 0x80|'*': /* matches zero or more times */ - if (!(prest = pat_scan(p, pe, 0))) - return 0; - s--; - /* take care of zero matches */ - if (p[-1] == (0x80 | '*') - && do_gmatch(s, se, prest, pe, isfile)) - return 1; - for (psub = p; ; psub = pnext) { - pnext = pat_scan(psub, pe, 1); - for (srest = s; srest <= se; srest++) { - if (do_gmatch(s, srest, - psub, pnext - 2, isfile) - && (do_gmatch(srest, se, - prest, pe, isfile) - || (s != srest - && do_gmatch(srest, se, - p - 2, pe, isfile)))) - return 1; - } - if (pnext == prest) - break; - } - return 0; - - case 0x80|'?': /* matches zero or once */ - case 0x80|'@': /* matches one of the patterns */ - case 0x80|' ': /* simile for @ */ - if (!(prest = pat_scan(p, pe, 0))) - return 0; - s--; - /* Take care of zero matches */ - if (p[-1] == (0x80 | '?') - && do_gmatch(s, se, prest, pe, isfile)) - return 1; - for (psub = p; ; psub = pnext) { - pnext = pat_scan(psub, pe, 1); - srest = prest == pe ? se : s; - for (; srest <= se; srest++) { - if (do_gmatch(s, srest, - psub, pnext - 2, isfile) - && do_gmatch(srest, se, - prest, pe, isfile)) - return 1; - } - if (pnext == prest) - break; - } - return 0; - - case 0x80|'!': /* matches none of the patterns */ - if (!(prest = pat_scan(p, pe, 0))) - return 0; - s--; - for (srest = s; srest <= se; srest++) { - int matched = 0; - - for (psub = p; ; psub = pnext) { - pnext = pat_scan(psub, pe, 1); - if (do_gmatch(s, srest, - psub, pnext - 2, isfile)) - { - matched = 1; - break; - } - if (pnext == prest) - break; - } - if (!matched && do_gmatch(srest, se, - prest, pe, isfile)) - return 1; - } - return 0; - - default: - if (sc != p[-1]) - return 0; - break; - } - } - return s == se; -} - -static const unsigned char * -cclass(const unsigned char *p, int sub) -{ - int c, d, not, found = 0; - const unsigned char *orig_p = p; - - if ((not = (ISMAGIC(*p) && *++p == NOT))) - p++; - do { - c = *p++; - if (ISMAGIC(c)) { - c = *p++; - if ((c & 0x80) && !ISMAGIC(c)) { - c &= 0x7f;/* extended pattern matching: *+?@! */ - /* XXX the ( char isn't handled as part of [] */ - if (c == ' ') /* simile for @: plain (..) */ - c = '(' /*)*/; - } - } - if (c == '\0') - /* No closing ] - act as if the opening [ was quoted */ - return sub == '[' ? orig_p : NULL; - if (ISMAGIC(p[0]) && p[1] == '-' - && (!ISMAGIC(p[2]) || p[3] != ']')) - { - p += 2; /* MAGIC- */ - d = *p++; - if (ISMAGIC(d)) { - d = *p++; - if ((d & 0x80) && !ISMAGIC(d)) - d &= 0x7f; - } - /* POSIX says this is an invalid expression */ - if (c > d) - return NULL; - } else - d = c; - if (c == sub || (c <= sub && sub <= d)) - found = 1; - } while (!(ISMAGIC(p[0]) && p[1] == ']')); - - return (found != not) ? p+2 : NULL; -} - -/* Look for next ) or | (if match_sep) in *(foo|bar) pattern */ -const unsigned char * -pat_scan(const unsigned char *p, const unsigned char *pe, int match_sep) -{ - int nest = 0; - - for (; p < pe; p++) { - if (!ISMAGIC(*p)) - continue; - if ((*++p == /*(*/ ')' && nest-- == 0) - || (*p == '|' && match_sep && nest == 0)) - return ++p; - if ((*p & 0x80) && strchr("*+?@! ", *p & 0x7f)) - nest++; - } - return NULL; -} - - -/* -------- qsort.c -------- */ - -/* - * quick sort of array of generic pointers to objects. - */ -static void qsort1(void **base, void **lim, int (*f)(void *, void *)); - -void -qsortp(void **base, size_t n, int (*f) (void *, void *)) - /* base address */ - /* elements */ - /* compare function */ -{ - qsort1(base, base + n, f); -} - -#define swap2(a, b) {\ - void *t; t = *(a); *(a) = *(b); *(b) = t;\ -} -#define swap3(a, b, c) {\ - void *t; t = *(a); *(a) = *(c); *(c) = *(b); *(b) = t;\ -} - -static void -qsort1(void **base, void **lim, int (*f) (void *, void *)) -{ - void **i, **j; - void **lptr, **hptr; - size_t n; - int c; - - top: - n = (lim - base) / 2; - if (n == 0) - return; - hptr = lptr = base+n; - i = base; - j = lim - 1; - - for (;;) { - if (i < lptr) { - if ((c = (*f)(*i, *lptr)) == 0) { - lptr --; - swap2(i, lptr); - continue; - } - if (c < 0) { - i += 1; - continue; - } - } - - begin: - if (j > hptr) { - if ((c = (*f)(*hptr, *j)) == 0) { - hptr ++; - swap2(hptr, j); - goto begin; - } - if (c > 0) { - if (i == lptr) { - hptr ++; - swap3(i, hptr, j); - i = lptr += 1; - goto begin; - } - swap2(i, j); - j -= 1; - i += 1; - continue; - } - j -= 1; - goto begin; - } - - if (i == lptr) { - if (lptr-base >= lim-hptr) { - qsort1(hptr+1, lim, f); - lim = lptr; - } else { - qsort1(base, lptr, f); - base = hptr+1; - } - goto top; - } - - lptr -= 1; - swap3(j, lptr, i); - j = hptr -= 1; - } -} - -int -xstrcmp(void *p1, void *p2) -{ - return (strcmp((char *)p1, (char *)p2)); -} - -/* Initialize a Getopt structure */ -void -ksh_getopt_reset(Getopt *go, int flags) -{ - go->optind = 1; - go->optarg = NULL; - go->p = 0; - go->flags = flags; - go->info = 0; - go->buf[1] = '\0'; -} - - -/* getopt() used for shell built-in commands, the getopts command, and - * command line options. - * A leading ':' in options means don't print errors, instead return '?' - * or ':' and set go->optarg to the offending option character. - * If GF_ERROR is set (and option doesn't start with :), errors result in - * a call to bi_errorf(). - * - * Non-standard features: - * - ';' is like ':' in options, except the argument is optional - * (if it isn't present, optarg is set to 0). - * Used for 'set -o'. - * - ',' is like ':' in options, except the argument always immediately - * follows the option character (optarg is set to the null string if - * the option is missing). - * Used for 'read -u2', 'print -u2' and fc -40. - * - '#' is like ':' in options, expect that the argument is optional - * and must start with a digit. If the argument doesn't start with a - * digit, it is assumed to be missing and normal option processing - * continues (optarg is set to 0 if the option is missing). - * Used for 'typeset -LZ4'. - * - accepts +c as well as -c IF the GF_PLUSOPT flag is present. If an - * option starting with + is accepted, the GI_PLUS flag will be set - * in go->info. - */ -int -ksh_getopt(char **argv, Getopt *go, const char *options) -{ - char c; - char *o; - - if (go->p == 0 || (c = argv[go->optind - 1][go->p]) == '\0') { - char *arg = argv[go->optind], flag = arg ? *arg : '\0'; - - go->p = 1; - if (flag == '-' && arg[1] == '-' && arg[2] == '\0') { - go->optind++; - go->p = 0; - go->info |= GI_MINUSMINUS; - return EOF; - } - if (arg == NULL - || ((flag != '-' ) /* neither a - nor a + (if + allowed) */ - && (!(go->flags & GF_PLUSOPT) || flag != '+')) - || (c = arg[1]) == '\0') - { - go->p = 0; - return EOF; - } - go->optind++; - go->info &= ~(GI_MINUS|GI_PLUS); - go->info |= flag == '-' ? GI_MINUS : GI_PLUS; - } - go->p++; - if (c == '?' || c == ':' || c == ';' || c == ',' || c == '#' - || !(o = strchr(options, c))) - { - if (options[0] == ':') { - go->buf[0] = c; - go->optarg = go->buf; - } else { - warningf(true, "%s%s-%c: unknown option", - (go->flags & GF_NONAME) ? "" : argv[0], - (go->flags & GF_NONAME) ? "" : ": ", c); - if (go->flags & GF_ERROR) - bi_errorf(null); - } - return '?'; - } - /* : means argument must be present, may be part of option argument - * or the next argument - * ; same as : but argument may be missing - * , means argument is part of option argument, and may be null. - */ - if (*++o == ':' || *o == ';') { - if (argv[go->optind - 1][go->p]) - go->optarg = argv[go->optind - 1] + go->p; - else if (argv[go->optind]) - go->optarg = argv[go->optind++]; - else if (*o == ';') - go->optarg = NULL; - else { - if (options[0] == ':') { - go->buf[0] = c; - go->optarg = go->buf; - return ':'; - } - warningf(true, "%s%s-'%c' requires argument", - (go->flags & GF_NONAME) ? "" : argv[0], - (go->flags & GF_NONAME) ? "" : ": ", c); - if (go->flags & GF_ERROR) - bi_errorf(null); - return '?'; - } - go->p = 0; - } else if (*o == ',') { - /* argument is attached to option character, even if null */ - go->optarg = argv[go->optind - 1] + go->p; - go->p = 0; - } else if (*o == '#') { - /* argument is optional and may be attached or unattached - * but must start with a digit. optarg is set to 0 if the - * argument is missing. - */ - if (argv[go->optind - 1][go->p]) { - if (digit(argv[go->optind - 1][go->p])) { - go->optarg = argv[go->optind - 1] + go->p; - go->p = 0; - } else - go->optarg = NULL; - } else { - if (argv[go->optind] && digit(argv[go->optind][0])) { - go->optarg = argv[go->optind++]; - go->p = 0; - } else - go->optarg = NULL; - } - } - return c; -} - -/* print variable/alias value using necessary quotes - * (POSIX says they should be suitable for re-entry...) - * No trailing newline is printed. - */ -void -print_value_quoted(const char *s) -{ - const char *p; - int inquote = 0; - - /* Test if any quotes are needed */ - for (p = s; *p; p++) - if (ctype(*p, C_QUOTE)) - break; - if (!*p) { - shprintf("%s", s); - return; - } - for (p = s; *p; p++) { - if (*p == '\'') { - shprintf("'\\'" + 1 - inquote); - inquote = 0; - } else { - if (!inquote) { - shprintf("'"); - inquote = 1; - } - shf_putc(*p, shl_stdout); - } - } - if (inquote) - shprintf("'"); -} - -/* Print things in columns and rows - func() is called to format the ith - * element - */ -void -print_columns(struct shf *shf, int n, char *(*func) (void *, int, char *, int), void *arg, int max_width, int prefcol) -{ - char *str = (char *) alloc(max_width + 1, ATEMP); - int i; - int r, c; - int rows, cols; - int nspace; - - /* max_width + 1 for the space. Note that no space - * is printed after the last column to avoid problems - * with terminals that have auto-wrap. - */ - cols = x_cols / (max_width + 1); - if (!cols) - cols = 1; - rows = (n + cols - 1) / cols; - if (prefcol && n && cols > rows) { - int tmp = rows; - - rows = cols; - cols = tmp; - if (rows > n) - rows = n; - } - - nspace = (x_cols - max_width * cols) / cols; - if (nspace <= 0) - nspace = 1; - for (r = 0; r < rows; r++) { - for (c = 0; c < cols; c++) { - i = c * rows + r; - if (i < n) { - shf_fprintf(shf, "%-*s", - max_width, - (*func)(arg, i, str, max_width + 1)); - if (c + 1 < cols) - shf_fprintf(shf, "%*s", nspace, null); - } - } - shf_putchar('\n', shf); - } - afree(str, ATEMP); -} - -/* Strip any nul bytes from buf - returns new length (nbytes - # of nuls) */ -int -strip_nuls(char *buf, int nbytes) -{ - char *dst; - - /* nbytes check because some systems (older freebsd's) have a buggy - * memchr() - */ - if (nbytes && (dst = memchr(buf, '\0', nbytes))) { - char *end = buf + nbytes; - char *p, *q; - - for (p = dst; p < end; p = q) { - /* skip a block of nulls */ - while (++p < end && *p == '\0') - ; - /* find end of non-null block */ - if (!(q = memchr(p, '\0', end - p))) - q = end; - memmove(dst, p, q - p); - dst += q - p; - } - *dst = '\0'; - return dst - buf; - } - return nbytes; -} - -/* Like read(2), but if read fails due to non-blocking flag, resets flag - * and restarts read. - */ -int -blocking_read(int fd, char *buf, int nbytes) -{ - int ret; - int tried_reset = 0; - - while ((ret = read(fd, buf, nbytes)) < 0) { - if (!tried_reset && (errno == EAGAIN -#ifdef EWOULDBLOCK - || errno == EWOULDBLOCK -#endif /* EWOULDBLOCK */ - )) - { - int oerrno = errno; - if (reset_nonblock(fd) > 0) { - tried_reset = 1; - continue; - } - errno = oerrno; - } - break; - } - return ret; -} - -/* Reset the non-blocking flag on the specified file descriptor. - * Returns -1 if there was an error, 0 if non-blocking wasn't set, - * 1 if it was. - */ -int -reset_nonblock(int fd) -{ - int flags; - int blocking_flags; - - if ((flags = fcntl(fd, F_GETFL, 0)) < 0) - return -1; - /* With luck, the C compiler will reduce this to a constant */ - blocking_flags = 0; -#ifdef O_NONBLOCK - blocking_flags |= O_NONBLOCK; -#endif /* O_NONBLOCK */ -#ifdef O_NDELAY - blocking_flags |= O_NDELAY; -#else /* O_NDELAY */ -# ifndef O_NONBLOCK - blocking_flags |= FNDELAY; /* hope this exists... */ -# endif /* O_NONBLOCK */ -#endif /* O_NDELAY */ - if (!(flags & blocking_flags)) - return 0; - flags &= ~blocking_flags; - if (fcntl(fd, F_SETFL, flags) < 0) - return -1; - return 1; -} - - -#ifndef MAXPATHLEN -# define MAXPATHLEN PATH -#endif /* MAXPATHLEN */ - -/* Like getcwd(), except bsize is ignored if buf is 0 (MAXPATHLEN is used) */ -char * -ksh_get_wd(char *buf, int bsize) -{ -#ifdef HAVE_GETCWD - char *b; - char *ret; - - /* Assume getcwd() available */ - if (!buf) { - bsize = MAXPATHLEN; - b = alloc(MAXPATHLEN + 1, ATEMP); - } else - b = buf; - - ret = getcwd(b, bsize); - - if (!buf) { - if (ret) - ret = aresize(b, strlen(b) + 1, ATEMP); - else - afree(b, ATEMP); - } - - return ret; -#else /* HAVE_GETCWD */ - extern char *getwd(char *); - char *b; - int len; - - /* Before memory allocated */ - HPUX_GETWD_BUG_CODE - - if (buf && bsize > MAXPATHLEN) - b = buf; - else - b = alloc(MAXPATHLEN + 1, ATEMP); - if (!getwd(b)) { - errno = EACCES; - if (b != buf) - afree(b, ATEMP); - return NULL; - } - len = strlen(b) + 1; - if (!buf) - b = aresize(b, len, ATEMP); - else if (buf != b) { - if (len > bsize) { - errno = ERANGE; - return NULL; - } - memcpy(buf, b, len); - afree(b, ATEMP); - b = buf; - } - - return b; -#endif /* HAVE_GETCWD */ -} - -#if !defined(HAVE_SETSID) -#define NO_CHVT "setsid not implemented" -#elif !defined(TIOCSCTTY) -#define NO_CHVT "no TIOCSCTTY ioctl" -#else -static char * -chvt(char *f) -{ - int fd; - - if (chown(f, 0, 0)) - return "chown"; - if (chmod(f, 0600)) - return "chmod"; -#if defined(HAVE_REVOKE) && !defined(linux) - if (revoke(f)) - return "revoke"; -#endif - - if ((fd = open(f, O_RDWR)) == -1) { - sleep(1); - if ((fd = open(f, O_RDWR)) == -1) - return "open"; - } - switch (fork()) { - case -1: - return "fork"; - case 0: - break; - default: - _exit(0); - } - if (setsid() == -1) - return "setsid"; - if (ioctl(fd, TIOCSCTTY, NULL) == -1) - return "ioctl"; - dup2(fd, 0); - dup2(fd, 1); - dup2(fd, 2); - if (fd > 2) - close(fd); - - return NULL; -} -#endif - -static int -parse_T(char *fn) -{ -#ifdef NO_CHVT - warningf(0, "chvt: %s", NO_CHVT); - return -1; -#else - char *rv, dv[20]; - struct stat sb; - - strlcpy(dv, fn, 20); - if (stat(dv, &sb)) { - snprintf(dv, 20, "/dev/ttyC%s", fn); - if (stat(dv, &sb)) { - snprintf(dv, 20, "/dev/tty%s", fn); - if (stat(dv, &sb)) { - warningf(0, "chvt: can't find tty %s", fn); - return -1; - } - } - } - if (!(sb.st_mode & S_IFCHR)) { - warningf(0, "chvt: not a char device: %s", fn); - return -1; - } - if ((rv = chvt(dv)) != NULL) { - warningf(0, "chvt: failed to %s: %s", rv, strerror(errno)); - return -1; - } - return 0; -#endif -} diff --git a/missing.c b/missing.c deleted file mode 100644 index 4b6bfc0..0000000 --- a/missing.c +++ /dev/null @@ -1,148 +0,0 @@ -/** $MirOS: src/bin/ksh/missing.c,v 1.1.7.1 2005/03/06 15:42:54 tg Exp $ */ -/* $OpenBSD: missing.c,v 1.5 2003/05/16 18:49:46 jsyn Exp $ */ - -/* - * Routines which may be missing on some machines - */ - -#include "sh.h" -#include "ksh_stat.h" - -__RCSID("$MirOS: src/bin/ksh/missing.c,v 1.1.7.1 2005/03/06 15:42:54 tg Exp $"); - -#ifndef HAVE_STRERROR -char * -strerror(err) - int err; -{ - static char buf[64]; - switch (err) { - case EINVAL: - return "Invalid argument"; - case EACCES: - return "Permission denied"; - case ESRCH: - return "No such process"; - case EPERM: - return "Not owner"; - case ENOENT: - return "No such file or directory"; - case ENOTDIR: - return "Not a directory"; - case ENOEXEC: - return "Exec format error"; - case ENOMEM: - return "Not enough memory"; - case E2BIG: - return "Argument list too long"; - default: - shf_snprintf(buf, sizeof(buf), "Unknown system error %d", err); - return buf; - } -} -#endif /* !HAVE_STRERROR */ - -#ifdef OPENDIR_DOES_NONDIR -/* Prevent opendir() from attempting to open non-directories. Such - * behavior can cause problems if it attempts to open special devices... - */ -DIR * -ksh_opendir(d) - const char *d; -{ - struct stat statb; - - if (stat(d, &statb) != 0) - return NULL; - if (!S_ISDIR(statb.st_mode)) { - errno = ENOTDIR; - return NULL; - } - return opendir(d); -} -#endif /* OPENDIR_DOES_NONDIR */ - -#ifndef HAVE_DUP2 -int -dup2(oldd, newd) - int oldd; - int newd; -{ - int old_errno; - - if (fcntl(oldd, F_GETFL, 0) == -1) - return -1; /* errno == EBADF */ - - if (oldd == newd) - return newd; - - old_errno = errno; - - close(newd); /* in case its open */ - - errno = old_errno; - - return fcntl(oldd, F_DUPFD, newd); -} -#endif /* !HAVE_DUP2 */ - -/* - * Random functions - */ - -#ifndef HAVE_SRANDOM -#undef HAVE_RANDOM -#endif - -int rnd_state; - -void -rnd_seed(long newval) -{ - rnd_put(newval); - rnd_state = 0; -} - -long -rnd_get(void) -{ -#ifdef HAVE_ARC4RANDOM - if (!rnd_state) - return arc4random() & 0x7FFF; -#endif -#ifdef HAVE_RANDOM - return random() & 0x7FFF; -#else - return rand(); -#endif -} - -void -rnd_put(long newval) -{ - long sv; - - rnd_state = 1 | rnd_get(); - sv = (rnd_get() << (newval & 7)) ^ newval; - -#if defined(HAVE_ARC4RANDOM_PUSH) - arc4random_push(sv); -#elif defined(HAVE_ARC4RANDOM_ADDRANDOM) - arc4random_addrandom((unsigned char *)&sv, sizeof(sv)); -#endif -#ifdef HAVE_ARC4RANDOM - sv ^= arc4random(); -#endif - -#ifdef HAVE_RANDOM - srandom(sv); -#else - srand(sv); -#endif - - while (rnd_state) { - rnd_get(); - rnd_state >>= 1; - } - rnd_state = 1; -} diff --git a/path.c b/path.c deleted file mode 100644 index 9dab863..0000000 --- a/path.c +++ /dev/null @@ -1,309 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: path.c,v 1.9 2003/10/22 07:40:38 jmc Exp $ */ - -#include "sh.h" -#include "ksh_stat.h" - -__RCSID("$MirOS$"); - -/* - * Contains a routine to search a : separated list of - * paths (a la CDPATH) and make appropriate file names. - * Also contains a routine to simplify .'s and ..'s out of - * a path name. - * - * Larry Bouzane (larry@cs.mun.ca) - */ - -#ifdef S_ISLNK -static char *do_phys_path(XString *xsp, char *xp, const char *path); -#endif /* S_ISLNK */ - -/* - * Makes a filename into result using the following algorithm. - * - make result NULL - * - if file starts with '/', append file to result & set cdpathp to NULL - * - if file starts with ./ or ../ append cwd and file to result - * and set cdpathp to NULL - * - if the first element of cdpathp doesnt start with a '/' xx or '.' xx - * then cwd is appended to result. - * - the first element of cdpathp is appended to result - * - file is appended to result - * - cdpathp is set to the start of the next element in cdpathp (or NULL - * if there are no more elements. - * The return value indicates whether a non-null element from cdpathp - * was appended to result. - */ -int -make_path(const char *cwd, const char *file, char **cdpathp, XString *xsp, int *phys_pathp) - - - /* & of : separated list */ - - -{ - int rval = 0; - int use_cdpath = 1; - char *plist; - int len; - int plen = 0; - char *xp = Xstring(*xsp, xp); - - if (!file) - file = null; - - if (!ISRELPATH(file)) { - *phys_pathp = 0; - use_cdpath = 0; - } else { - if (file[0] == '.') { - char c = file[1]; - - if (c == '.') - c = file[2]; - if (ISDIRSEP(c) || c == '\0') - use_cdpath = 0; - } - - plist = *cdpathp; - if (!plist) - use_cdpath = 0; - else if (use_cdpath) { - char *pend; - - for (pend = plist; *pend && *pend != PATHSEP; pend++) - ; - plen = pend - plist; - *cdpathp = *pend ? ++pend : NULL; - } - - if ((use_cdpath == 0 || !plen || ISRELPATH(plist)) - && (cwd && *cwd)) - { - len = strlen(cwd); - XcheckN(*xsp, xp, len); - memcpy(xp, cwd, len); - xp += len; - if (!ISDIRSEP(cwd[len - 1])) - Xput(*xsp, xp, DIRSEP); - } - *phys_pathp = Xlength(*xsp, xp); - if (use_cdpath && plen) { - XcheckN(*xsp, xp, plen); - memcpy(xp, plist, plen); - xp += plen; - if (!ISDIRSEP(plist[plen - 1])) - Xput(*xsp, xp, DIRSEP); - rval = 1; - } - } - - len = strlen(file) + 1; - XcheckN(*xsp, xp, len); - memcpy(xp, file, len); - - if (!use_cdpath) - *cdpathp = NULL; - - return rval; -} - -/* - * Simplify pathnames containing "." and ".." entries. - * ie, simplify_path("/a/b/c/./../d/..") returns "/a/b" - */ -void -simplify_path(char *path) -{ - char *cur; - char *t; - int isrooted; - char *very_start = path; - char *start; - - if (!*path) - return; - - if ((isrooted = ISROOTEDPATH(path))) - very_start++; -#if defined (__CYGWIN__) - if (path[0] && path[1] == ':') /* skip a: */ - very_start += 2; -#endif - - /* Before After - * /foo/ /foo - * /foo/../../bar /bar - * /foo/./blah/.. /foo - * . . - * .. .. - * ./foo foo - * foo/../../../bar ../../bar - * CYGWIN: - * a:/foo/../.. a:/ - * a:. a: - * a:.. a:.. - * a:foo/../../blah a:../blah - */ - -#ifdef __CYGWIN__ - /* preserve leading double-slash on pathnames (for UNC paths) */ - if (path[0] && ISDIRSEP(path[0]) && path[1] && ISDIRSEP(path[1])) - very_start++; -#endif /* __CYGWIN__ */ - - for (cur = t = start = very_start; ; ) { - /* treat multiple '/'s as one '/' */ - while (ISDIRSEP(*t)) - t++; - - if (*t == '\0') { - if (cur == path) - /* convert empty path to dot */ - *cur++ = '.'; - *cur = '\0'; - break; - } - - if (t[0] == '.') { - if (!t[1] || ISDIRSEP(t[1])) { - t += 1; - continue; - } else if (t[1] == '.' && (!t[2] || ISDIRSEP(t[2]))) { - if (!isrooted && cur == start) { - if (cur != very_start) - *cur++ = DIRSEP; - *cur++ = '.'; - *cur++ = '.'; - start = cur; - } else if (cur != start) - while (--cur > start && !ISDIRSEP(*cur)) - ; - t += 2; - continue; - } - } - - if (cur != very_start) - *cur++ = DIRSEP; - - /* find/copy next component of pathname */ - while (*t && !ISDIRSEP(*t)) - *cur++ = *t++; - } -} - - -void -set_current_wd(char *path) -{ - int len; - char *p = path; - - if (!p && !(p = ksh_get_wd(NULL, 0))) - p = null; - - len = strlen(p) + 1; - - if (len > current_wd_size) - current_wd = aresize(current_wd, current_wd_size = len, APERM); - memcpy(current_wd, p, len); - if (p != path && p != null) - afree(p, ATEMP); -} - -#ifdef S_ISLNK -char * -get_phys_path(const char *path) -{ - XString xs; - char *xp; - - Xinit(xs, xp, strlen(path) + 1, ATEMP); - - xp = do_phys_path(&xs, xp, path); - - if (!xp) - return NULL; - - if (Xlength(xs, xp) == 0) - Xput(xs, xp, DIRSEP); - Xput(xs, xp, '\0'); - - return Xclose(xs, xp); -} - -static char * -do_phys_path(XString *xsp, char *xp, const char *path) -{ - const char *p, *q; - size_t len; - int savepos, llen; - char lbuf[PATH_MAX]; - - Xcheck(*xsp, xp); - for (p = path; p; p = q) { - while (ISDIRSEP(*p)) - p++; - if (!*p) - break; - len = (q = ksh_strchr_dirsep(p)) ? (size_t)(q - p) : strlen(p); - if (len == 1 && p[0] == '.') - continue; - if (len == 2 && p[0] == '.' && p[1] == '.') { - while (xp > Xstring(*xsp, xp)) { - xp--; - if (ISDIRSEP(*xp)) - break; - } - continue; - } - - savepos = Xsavepos(*xsp, xp); - Xput(*xsp, xp, DIRSEP); - XcheckN(*xsp, xp, len + 1); - memcpy(xp, p, len); - xp += len; - *xp = '\0'; - - llen = readlink(Xstring(*xsp, xp), lbuf, sizeof(lbuf) - 1); - if (llen < 0) { - /* EINVAL means it wasn't a symlink... */ - if (errno != EINVAL) - return NULL; - continue; - } - lbuf[llen] = '\0'; - - /* If absolute path, start from scratch.. */ - xp = ISABSPATH(lbuf) ? Xstring(*xsp, xp) - : Xrestpos(*xsp, xp, savepos); - if (!(xp = do_phys_path(xsp, xp, lbuf))) - return NULL; - } - return xp; -} -#endif /* S_ISLNK */ - -#ifdef TEST - -main(argc, argv) -{ - int rv; - char *cp, cdpath[256], pwd[256], file[256], result[256]; - - printf("enter CDPATH: "); gets(cdpath); - printf("enter PWD: "); gets(pwd); - while (1) { - if (printf("Enter file: "), gets(file) == 0) - return 0; - cp = cdpath; - do { - rv = make_path(pwd, file, &cp, result, sizeof(result)); - printf("make_path returns (%d), \"%s\" ", rv, result); - simplify_path(result); - printf("(simpifies to \"%s\")\n", result); - } while (cp); - } -} -#endif /* TEST */ diff --git a/proto.h b/proto.h deleted file mode 100644 index 59c4f16..0000000 --- a/proto.h +++ /dev/null @@ -1,297 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: proto.h,v 1.11 2003/05/16 19:58:57 jsyn Exp $ */ -/* $From: proto.h,v 1.3 1994/05/19 18:32:40 michael Exp michael $ */ - -#ifndef PROTO_H -/* $OpenBSD: proto.h,v 1.18 2004/12/18 22:35:41 millert Exp $ */ - -/* - * prototypes for PD-KSH - * originally generated using "cproto.c 3.5 92/04/11 19:28:01 cthuang" - */ - -/* alloc.c */ -Area * ainit(Area *ap); -void afreeall(Area *ap); -void * alloc(size_t size, Area *ap); -void * aresize(void *ptr, size_t size, Area *ap); -void afree(void *ptr, Area *ap); -/* c_ksh.c */ -int c_hash(char **wp); -int c_cd(char **wp); -int c_pwd(char **wp); -int c_print(char **wp); -int c_whence(char **wp); -int c_command(char **wp); -int c_typeset(char **wp); -int c_alias(char **wp); -int c_unalias(char **wp); -int c_let(char **wp); -int c_jobs(char **wp); -int c_fgbg(char **wp); -int c_kill(char **wp); -void getopts_reset(int val); -int c_getopts(char **wp); -int c_bind(char **wp); -/* c_sh.c */ -int c_label(char **wp); -int c_shift(char **wp); -int c_umask(char **wp); -int c_dot(char **wp); -int c_wait(char **wp); -int c_read(char **wp); -int c_eval(char **wp); -int c_trap(char **wp); -int c_brkcont(char **wp); -int c_exitreturn(char **wp); -int c_set(char **wp); -int c_unset(char **wp); -int c_ulimit(char **wp); -int c_times(char **wp); -int timex(struct op *t, int f); -void timex_hook(struct op *t, char ** volatile *app); -int c_exec(char **wp); -int c_builtin(char **wp); -/* c_test.c */ -int c_test(char **wp); -/* edit.c: most prototypes in edit.h */ -void x_init(void); -int x_read(char *buf, size_t len); -void set_editmode(const char *ed); -/* emacs.c: most prototypes in edit.h */ -int x_bind(const char *a1, const char *a2, int macro, - int list); -/* eval.c */ -char * substitute(const char *cp, int f); -char ** eval(char **ap, int f); -char * evalstr(char *cp, int f); -char * evalonestr(char *cp, int f); -char *debunk(char *dp, const char *sp, size_t dlen); -void expand(char *cp, XPtrV *wp, int f); -int glob_str(char *cp, XPtrV *wp, int markdirs); -/* exec.c */ -int execute(struct op * volatile t, volatile int flags); -int shcomexec(char **wp); -struct tbl * findfunc(const char *name, unsigned int h, int create); -int define(const char *name, struct op *t); -void builtin(const char *name, int (*func)(char **)); -struct tbl * findcom(const char *name, int flags); -void flushcom(int all); -char * search(const char *name, const char *path, int mode, - int *errnop); -int search_access(const char *path, int mode, int *errnop); -int pr_menu(char *const *ap); -int pr_list(char *const *ap); -/* expr.c */ -int evaluate(const char *expr, long *rval, int error_ok); -int v_evaluate(struct tbl *vp, const char *expr, volatile int error_ok); -/* history.c */ -void init_histvec(void); -void hist_init(Source *s); -void hist_finish(void); -void histsave(int lno, const char *cmd, int dowrite); -int c_fc(char **wp); -void sethistsize(int n); -void sethistfile(const char *name); -#ifdef EASY_HISTORY -void histappend(const char *cmd, int nl_separate); -#endif -char ** histpos(void); -int histN(void); -int histnum(int n); -int findhist(int start, int fwd, const char *str, - int anchored); -/* io.c */ -void errorf(const char *fmt, ...) - GCC_FUNC_ATTR2(noreturn, format(printf, 1, 2)); -void warningf(int fileline, const char *fmt, ...) - GCC_FUNC_ATTR(format(printf, 2, 3)); -void bi_errorf(const char *fmt, ...) - GCC_FUNC_ATTR(format(printf, 1, 2)); -void internal_errorf(int jump, const char *fmt, ...) - GCC_FUNC_ATTR(format(printf, 2, 3)); -void error_prefix(int fileline); -void shellf(const char *fmt, ...) - GCC_FUNC_ATTR(format(printf, 1, 2)); -void shprintf(const char *fmt, ...) - GCC_FUNC_ATTR(format(printf, 1, 2)); -#ifdef KSH_DEBUG -void kshdebug_init_(void); -void kshdebug_printf_(const char *fmt, ...) - GCC_FUNC_ATTR(format(printf, 1, 2)); -void kshdebug_dump_(const char *str, const void *mem, int nbytes); -#endif /* KSH_DEBUG */ -int can_seek(int fd); -void initio(void); -int ksh_dup2(int ofd, int nfd, int errok); -int savefd(int fd, int noclose); -void restfd(int fd, int ofd); -void openpipe(int *pv); -void closepipe(int *pv); -int check_fd(char *name, int mode, const char **emsgp); -void coproc_init(void); -void coproc_read_close(int fd); -void coproc_readw_close(int fd); -void coproc_write_close(int fd); -int coproc_getfd(int mode, const char **emsgp); -void coproc_cleanup(int reuse); -struct temp *maketemp(Area *ap, Temp_type type, struct temp **tlist); -/* jobs.c */ -void j_init(int mflagset); -void j_exit(void); -void j_change(void); -int exchild(struct op *t, int flags, int close_fd); -void startlast(void); -int waitlast(void); -int waitfor(const char *cp, int *sigp); -int j_kill(const char *cp, int sig); -int j_resume(const char *cp, int bg); -int j_jobs(const char *cp, int slp, int nflag); -void j_notify(void); -pid_t j_async(void); -int j_stopped_running(void); -/* lex.c */ -int yylex(int cf); -void yyerror(const char *fmt, ...) - GCC_FUNC_ATTR2(noreturn, format(printf, 1, 2)); -Source * pushs(int type, Area *areap); -void set_prompt(int to, Source *s); -void pprompt(const char *cp, int ntruncate); -/* main.c */ -int include(const char *name, int argc, char **argv, - int intr_ok); -int command(const char *comm); -int shell(Source *volatile s, int volatile toplevel); -void unwind(int i) GCC_FUNC_ATTR(noreturn); -void newenv(int type); -void quitenv(struct shf *shf); -void cleanup_parents_env(void); -void cleanup_proc_env(void); -void aerror(Area *ap, const char *msg) - GCC_FUNC_ATTR(noreturn); -/* misc.c */ -void setctypes(const char *s, int t); -void initctypes(void); -char * ulton(unsigned long n, int base); -char * str_save(const char *s, Area *ap); -char * str_nsave(const char *s, int n, Area *ap); -int option(const char *n); -char * getoptions(void); -void change_flag(enum sh_flag f, int what, int newval); -int parse_args(char **argv, int what, int *setargsp); -int getn(const char *as, int *ai); -int bi_getn(const char *as, int *ai); -int gmatch(const char *s, const char *p, int isfile); -int has_globbing(const char *xp, const char *xpe); -const unsigned char *pat_scan(const unsigned char *p, - const unsigned char *pe, int match_sep); -void qsortp(void **base, size_t n, int (*f)(void *, void *)); -int xstrcmp(void *p1, void *p2); -void ksh_getopt_reset(Getopt *go, int); -int ksh_getopt(char **argv, Getopt *go, const char *options); -void print_value_quoted(const char *s); -void print_columns(struct shf *shf, int n, - char *(*func)(void *, int, char *, int), - void *arg, int max_width, int prefcol); -int strip_nuls(char *buf, int nbytes); -int blocking_read(int fd, char *buf, int nbytes); -int reset_nonblock(int fd); -char *ksh_get_wd(char *buf, int bsize); -/* missing.c */ -long rnd_get(void); -void rnd_put(long); -void rnd_seed(long); -/* path.c */ -int make_path(const char *cwd, const char *file, - char **pathlist, XString *xsp, int *phys_pathp); -void simplify_path(char *path); -char *get_phys_path(const char *path); -void set_current_wd(char *path); -/* syn.c */ -void initkeywords(void); -struct op * compile(Source *s); -/* table.c */ -unsigned int hash(const char *n); -void tinit(struct table *tp, Area *ap, int tsize); -struct tbl * tsearch(struct table *tp, const char *n, unsigned int h); -struct tbl * tenter(struct table *tp, const char *n, unsigned int h); -void tdelete(struct tbl *p); -void twalk(struct tstate *ts, struct table *tp); -struct tbl * tnext(struct tstate *ts); -struct tbl ** tsort(struct table *tp); -/* trace.c */ -/* trap.c */ -void inittraps(void); -void alarm_init(void); -Trap * gettrap(const char *name, int igncase); -RETSIGTYPE trapsig(int i); -void intrcheck(void); -int fatal_trap_check(void); -int trap_pending(void); -void runtraps(int intr); -void runtrap(Trap *p); -void cleartraps(void); -void restoresigs(void); -void settrap(Trap *p, char *s); -int block_pipe(void); -void restore_pipe(int restore_dfl); -int setsig(Trap *p, sig_t f, int flags); -void setexecsig(Trap *p, int restore); -/* tree.c */ -int fptreef(struct shf *f, int indent, const char *fmt, ...); -char * snptreef(char *s, int n, const char *fmt, ...); -struct op * tcopy(struct op *t, Area *ap); -char * wdcopy(const char *wp, Area *ap); -char * wdscan(const char *wp, int c); -char * wdstrip(const char *wp); -void tfree(struct op *t, Area *ap); -/* var.c */ -void newblock(void); -void popblock(void); -void initvar(void); -struct tbl * global(const char *n); -struct tbl * local(const char *n, bool copy); -char * str_val(struct tbl *vp); -long intval(struct tbl *vp); -int setstr(struct tbl *vq, const char *s, int error_ok); -struct tbl *setint_v(struct tbl *vq, struct tbl *vp); -void setint(struct tbl *vq, long n); -int getint(struct tbl *vp, long *nump); -struct tbl * typeset(const char *var, Tflag set, Tflag clr, int field, int base); -void unset(struct tbl *vp, int array_ref); -char * skip_varname(const char *s, int aok); -char *skip_wdvarname(const char *s, int aok); -int is_wdvarname(const char *s, int aok); -int is_wdvarassign(const char *s); -char ** makenv(void); -void change_random(void); -int array_ref_len(const char *cp); -char * arrayname(const char *str); -void set_array(const char *var, int reset, char **vals); -/* version.c */ -/* vi.c: see edit.h */ - - -/* Hack to avoid billions of compile warnings on SunOS 4.1.x */ -#if defined(MUN) && defined(sun) && !defined(__svr4__) -extern void bcopy(const void *src, void *dst, size_t size); -extern int fclose(FILE *fp); -extern int fprintf(FILE *fp, const char *fmt, ...); -extern int fread(void *buf, int size, int num, FILE *fp); -extern int ioctl(int fd, int request, void *arg); -extern int killpg(int pgrp, int sig); -extern int nice(int n); -extern int readlink(const char *path, char *buf, int bufsize); -extern int setpgrp(int pid, int pgrp); -extern int strcasecmp(const char *s1, const char *s2); -extern int tolower(int); -extern int toupper(int); -/* Include files aren't included yet */ -extern int getrlimit( /* int resource, struct rlimit *rpl */ ); -extern int getrusage( /* int who, struct rusage *rusage */ ); -extern int gettimeofday( /* struct timeval *tv, struct timezone *tz */ ); -extern int setrlimit( /* int resource, struct rlimit *rlp */ ); -extern int lstat( /* const char *path, struct stat *buf */ ); -#endif - -#endif /* ndef PROTO_H */ diff --git a/sh.h b/sh.h deleted file mode 100644 index ad95ed3..0000000 --- a/sh.h +++ /dev/null @@ -1,660 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: sh.h,v 1.23 2004/12/18 22:11:43 millert Exp $ */ -/* $From: sh.h,v 1.2 1994/05/19 18:32:40 michael Exp michael $ */ - -#ifndef SH_H -#define SH_H - -/* - * mirbsdksh - MirOS Project Korn-Shell - * from: Public Domain Bourne/Korn shell - */ - -#include "config.h" /* system and option configuration info */ - -/* Start of common headers */ - -#include -#include -#include -#ifdef HAVE_STDDEF_H -# include -#endif - -#ifdef HAVE_STDLIB_H -# include -#else -/* just a useful subset of what stdlib.h would have */ -extern char * getenv(const char *); -extern void * malloc(size_t); -extern void * realloc(void *, size_t); -extern int free(void *); -extern int exit(int); -extern int atoi(const char *); -#endif /* HAVE_STDLIB_H */ - -#ifdef HAVE_UNISTD_H -# include -#else -/* just a useful subset of what unistd.h would have */ -extern int access(const char *, int); -extern int open(const char *, int, ...); -extern int creat(const char *, mode_t); -extern int read(int, char *, unsigned); -extern int write(int, const char *, unsigned); -extern off_t lseek(int, off_t, int); -extern int close(int); -extern int pipe(int []); -extern int dup2(int, int); -extern int unlink(const char *); -extern int fork(void); -extern int execve(const char *, char * const[], char * const[]); -extern int chdir(const char *); -extern int kill(pid_t, int); -extern char *getcwd(); /* no ARGS here - differs on different machines */ -extern int geteuid(void); -extern int readlink(const char *, char *, int); -extern int getegid(void); -extern int getpid(void); -extern int getppid(void); -extern unsigned int sleep(unsigned int); -extern int isatty(int); -# ifdef POSIX_PGRP -extern int getpgrp(void); -extern int setpgid(pid_t, pid_t); -# endif /* POSIX_PGRP */ -# ifdef BSD_PGRP -extern int getpgrp(pid_t); -extern int setpgrp(pid_t, pid_t); -# endif /* BSD_PGRP */ -# ifdef SYSV_PGRP -extern int getpgrp(void); -extern int setpgrp(void); -# endif /* SYSV_PGRP */ -#endif /* HAVE_UNISTD_H */ - -#ifdef HAVE_STRING_H -# include -#else -# include -# define strchr index -# define strrchr rindex -#endif /* HAVE_STRING_H */ -#ifndef HAVE_STRSTR -char *strstr(const char *s, const char *p); -#endif /* HAVE_STRSTR */ -#ifndef HAVE_STRCASECMP -int strcasecmp(const char *s1, const char *s2); -int strncasecmp(const char *s1, const char *s2, int n); -#endif /* HAVE_STRCASECMP */ - -#ifdef HAVE_MEMORY_H -# include -#endif -#ifndef HAVE_MEMSET -# define memcpy(d, s, n) bcopy(s, d, n) -# define memcmp(s1, s2, n) bcmp(s1, s2, n) -void *memset(void *d, int c, size_t n); -#endif /* HAVE_MEMSET */ -#ifndef HAVE_MEMMOVE -# ifdef HAVE_BCOPY -# define memmove(d, s, n) bcopy(s, d, n) -# else -void *memmove(void *d, const void *s, size_t n); -# endif -#endif /* HAVE_MEMMOVE */ - -# include -# define SH_VA_START(va, argn) va_start(va, argn) - -#include - -#ifdef HAVE_FCNTL_H -# include -#else -# include -#endif /* HAVE_FCNTL_H */ -#ifndef O_ACCMODE -# define O_ACCMODE (O_RDONLY|O_WRONLY|O_RDWR) -#endif /* !O_ACCMODE */ - -#ifndef F_OK /* access() arguments */ -# define F_OK 0 -# define X_OK 1 -# define W_OK 2 -# define R_OK 4 -#endif /* !F_OK */ - -#ifndef SEEK_SET -# ifdef L_SET -# define SEEK_SET L_SET -# define SEEK_CUR L_INCR -# define SEEK_END L_XTND -# else /* L_SET */ -# define SEEK_SET 0 -# define SEEK_CUR 1 -# define SEEK_END 2 -# endif /* L_SET */ -#endif /* !SEEK_SET */ - -#ifdef HAVE_LIMITS_H -# include -#endif /* HAVE_LIMITS_H */ - -#include -#ifndef NSIG -#define NSIG 32 -#endif - -#ifdef HAVE_PATHS_H -# include -#endif /* HAVE_PATHS_H */ -#ifdef _PATH_DEFPATH -# define DEFAULT__PATH _PATH_DEFPATH -#else /* _PATH_DEFPATH */ -# define DEFAULT__PATH DEFAULT_PATH -#endif /* _PATH_DEFPATH */ - -#ifndef offsetof -# define offsetof(type,id) ((size_t)&((type*)NULL)->id) -#endif - -#ifndef HAVE_KILLPG -# define killpg(p, s) kill(-(p), (s)) -#endif /* !HAVE_KILLPG */ - -/* Special cases for execve(2) */ -# if defined(OS_ISC) && defined(_POSIX_SOURCE) -/* Kludge for ISC 3.2 (and other versions?) so programs will run correctly. */ -# define ksh_execve(p, av, ev, flags) \ - do { \ - __setostype(0); \ - execve(p, av, ev); \ - __setostype(1); \ - } while (0) -# else /* OS_ISC && _POSIX */ -# define ksh_execve(p, av, ev, flags) execve(p, av, ev) -# endif /* OS_ISC && _POSIX */ - -/* this is a hang-over from older versions of the os2 port */ -#define ksh_dupbase(fd, base) fcntl(fd, F_DUPFD, base) - -#if defined(__sun__) && defined(__svr4__) -typedef void (*sig_t)(); -#endif - -#ifdef HAVE_SIGSETJMP -# define ksh_sigsetjmp(env,sm) sigsetjmp((env), (sm)) -# define ksh_siglongjmp(env,v) siglongjmp((env), (v)) -# define ksh_jmp_buf sigjmp_buf -#else /* HAVE_SIGSETJMP */ -# ifdef HAVE__SETJMP -# define ksh_sigsetjmp(env,sm) _setjmp(env) -# define ksh_siglongjmp(env,v) _longjmp((env), (v)) -# else /* HAVE__SETJMP */ -# define ksh_sigsetjmp(env,sm) setjmp(env) -# define ksh_siglongjmp(env,v) longjmp((env), (v)) -# endif /* HAVE__SETJMP */ -# define ksh_jmp_buf jmp_buf -#endif /* HAVE_SIGSETJMP */ - -#ifndef HAVE_DUP2 -extern int dup2(int, int); -#endif /* !HAVE_DUP2 */ - -/* Find a integer type that is at least 32 bits (or die) - SIZEOF_* defined - * by autoconf (assumes an 8 bit byte, but I'm not concerned). - * NOTE: INT32 may end up being more than 32 bits. - */ -# define INT32 int - -/* end of common headers */ - -/* Stop gcc and lint from complaining about possibly uninitialized variables */ -#if defined(__GNUC__) || defined(lint) -# define UNINITIALIZED(var) var = 0 -#else -# define UNINITIALIZED(var) var -#endif /* GNUC || lint */ - -/* some useful #defines */ -#ifdef EXTERN -# define I__(i) = i -#else -# define I__(i) -# define EXTERN extern -# define EXTERN_DEFINED -#endif - -#ifndef EXECSHELL -/* shell to exec scripts (see also $SHELL initialization in main.c) */ -# ifdef _PATH_BSHELL -# define EXECSHELL _PATH_BSHELL -# else -# define EXECSHELL "/bin/mksh" -# endif -# define EXECSHELL_STR "EXECSHELL" -#endif - -/* ISABSPATH() means path is fully and completely specified, - * ISROOTEDPATH() means a .. as the first component is a no-op, - * ISRELPATH() means $PWD can be tacked on to get an absolute path. - * - * OS Path ISABSPATH ISROOTEDPATH ISRELPATH - * unix /foo yes yes no - * unix foo no no yes - * unix ../foo no no yes - * os2+cyg a:/foo yes yes no - * os2+cyg a:foo no no no - * os2+cyg /foo no yes no - * os2+cyg foo no no yes - * os2+cyg ../foo no no yes - * cyg //foo yes yes no - */ -# define PATHSEP ':' -# define DIRSEP '/' -# define DIRSEPSTR "/" -# define ISDIRSEP(c) ((c) == '/') -#ifdef __CYGWIN__ -# define ISABSPATH(s) \ - (((s)[0] && (s)[1] == ':' && ISDIRSEP((s)[2])) || ISDIRSEP((s)[0])) -# define ISRELPATH(s) (!(s)[0] || ((s)[1] != ':' && !ISDIRSEP((s)[0]))) -#else /* __CYGWIN__ */ -# define ISABSPATH(s) ISDIRSEP((s)[0]) -# define ISRELPATH(s) (!ISABSPATH(s)) -#endif /* __CYGWIN__ */ -# define ISROOTEDPATH(s) ISABSPATH(s) -# define FILECHCONV(c) c -# define FILECMP(s1, s2) strcmp(s1, s2) -# define FILENCMP(s1, s2, n) strncmp(s1, s2, n) -# define ksh_strchr_dirsep(p) strchr(p, DIRSEP) -# define ksh_strrchr_dirsep(p) strrchr(p, DIRSEP) - -#define NELEM(a) (sizeof(a) / sizeof((a)[0])) -#define sizeofN(type, n) (sizeof(type) * (n)) -#define BIT(i) (1<<(i)) /* define bit in flag */ - -/* Table flag type - needs > 16 and < 32 bits */ -typedef INT32 Tflag; - -#define NUFILE 32 /* Number of user-accessible files */ -#define FDBASE 10 /* First file usable by Shell */ - -/* you're not going to run setuid shell scripts, are you? */ -#define eaccess(path, mode) access(path, mode) - -/* Make MAGIC a char that might be printed to make bugs more obvious, but - * not a char that is used often. Also, can't use the high bit as it causes - * portability problems (calling strchr(x, 0x80|'x') is error prone). - */ -#define MAGIC (7) /* prefix for *?[!{,} during expand */ -#define ISMAGIC(c) ((unsigned char)(c) == MAGIC) -#define NOT '!' /* might use ^ (ie, [!...] vs [^..]) */ - -#define LINE 4096 /* input line size */ -#ifndef PATH_MAX -#define PATH_MAX 1024 /* pathname size (todo: pathconf()) */ -#endif - -EXTERN const char *kshname; /* $0 */ -EXTERN pid_t kshpid; /* $$, shell pid */ -EXTERN pid_t procpid; /* pid of executing process */ -EXTERN uid_t ksheuid; /* effective uid of shell */ -EXTERN int exstat; /* exit status */ -EXTERN int subst_exstat; /* exit status of last $(..)/`..` */ -EXTERN const char *safe_prompt; /* safe prompt if PS1 substitution fails */ - -/* - * Area-based allocation built on malloc/free - */ -typedef struct Area { - struct link *freelist; /* free list */ -} Area; - -EXTERN Area aperm; /* permanent object space */ -#define APERM &aperm -#define ATEMP &e->area - -#ifdef MEM_DEBUG -# include "chmem.h" /* a debugging front end for malloc et. al. */ -#endif /* MEM_DEBUG */ - -#ifdef KSH_DEBUG -# define kshdebug_init() kshdebug_init_() -# define kshdebug_printf(a) kshdebug_printf_ a -# define kshdebug_dump(a) kshdebug_dump_ a -#else /* KSH_DEBUG */ -# define kshdebug_init() -# define kshdebug_printf(a) -# define kshdebug_dump(a) -#endif /* KSH_DEBUG */ - -/* - * parsing & execution environment - */ -EXTERN struct env { - short type; /* environment type - see below */ - short flags; /* EF_* */ - Area area; /* temporary allocation area */ - struct block *loc; /* local variables and functions */ - short *savefd; /* original redirected fd's */ - struct env *oenv; /* link to previous environment */ - ksh_jmp_buf jbuf; /* long jump back to env creator */ - struct temp *temps; /* temp files */ -} *e; - -/* struct env.type values */ -#define E_NONE 0 /* dummy environment */ -#define E_PARSE 1 /* parsing command # */ -#define E_FUNC 2 /* executing function # */ -#define E_INCL 3 /* including a file via . # */ -#define E_EXEC 4 /* executing command tree */ -#define E_LOOP 5 /* executing for/while # */ -#define E_ERRH 6 /* general error handler # */ -/* # indicates env has valid jbuf (see unwind()) */ - -/* struct env.flag values */ -#define EF_FUNC_PARSE BIT(0) /* function being parsed */ -#define EF_BRKCONT_PASS BIT(1) /* set if E_LOOP must pass break/continue on */ -#define EF_FAKE_SIGDIE BIT(2) /* hack to get info from unwind to quitenv */ - -/* Do breaks/continues stop at env type e? */ -#define STOP_BRKCONT(t) ((t) == E_NONE || (t) == E_PARSE \ - || (t) == E_FUNC || (t) == E_INCL) -/* Do returns stop at env type e? */ -#define STOP_RETURN(t) ((t) == E_FUNC || (t) == E_INCL) - -/* values for ksh_siglongjmp(e->jbuf, 0) */ -#define LRETURN 1 /* return statement */ -#define LEXIT 2 /* exit statement */ -#define LERROR 3 /* errorf() called */ -#define LLEAVE 4 /* untrappable exit/error */ -#define LINTR 5 /* ^C noticed */ -#define LBREAK 6 /* break statement */ -#define LCONTIN 7 /* continue statement */ -#define LSHELL 8 /* return to interactive shell() */ -#define LAEXPR 9 /* error in arithmetic expression */ - -/* option processing */ -#define OF_CMDLINE 0x01 /* command line */ -#define OF_SET 0x02 /* set builtin */ -#define OF_SPECIAL 0x04 /* a special variable changing */ -#define OF_INTERNAL 0x08 /* set internally by shell */ -#define OF_FIRSTTIME 0x10 /* as early as possible, once */ -#define OF_ANY (OF_CMDLINE | OF_SET | OF_SPECIAL | OF_INTERNAL) - -struct option { - const char *name; /* long name of option */ - char c; /* character flag (if any) */ - short flags; /* OF_* */ -}; -extern const struct option options[]; - -/* - * flags (the order of these enums MUST match the order in misc.c(options[])) - */ -enum sh_flag { - FEXPORT = 0, /* -a: export all */ - FBRACEEXPAND, /* enable {} globbing */ - FBGNICE, /* bgnice */ - FCOMMAND, /* -c: (invocation) execute specified command */ - FEMACS, /* emacs command editing */ - FEMACSUSEMETA, /* use 8th bit as meta */ - FERREXIT, /* -e: quit on error */ - FGMACS, /* gmacs command editing */ - FIGNOREEOF, /* eof does not exit */ - FTALKING, /* -i: interactive */ - FKEYWORD, /* -k: name=value anywhere */ - FLOGIN, /* -l: a login shell */ - FMARKDIRS, /* mark dirs with / in file name completion */ - FMONITOR, /* -m: job control monitoring */ - FNOCLOBBER, /* -C: don't overwrite existing files */ - FNOEXEC, /* -n: don't execute any commands */ - FNOGLOB, /* -f: don't do file globbing */ - FNOHUP, /* -H: don't kill running jobs when login shell exits */ - FNOLOG, /* don't save functions in history (ignored) */ -#ifdef JOBS - FNOTIFY, /* -b: asynchronous job completion notification */ -#endif - FNOUNSET, /* -u: using an unset var is an error */ - FPHYSICAL, /* -o physical: don't do logical cd's/pwd's */ - FPOSIX, /* -o posix: be posixly correct */ - FPRIVILEGED, /* -p: use suid_profile */ - FRESTRICTED, /* -r: restricted shell */ - FSH, /* -o sh: favor sh behaviour */ - FSTDIN, /* -s: (invocation) parse stdin */ - FTRACKALL, /* -h: create tracked aliases for all commands */ - FVERBOSE, /* -v: echo input */ - FVI, /* vi command editing */ - FVIRAW, /* always read in raw mode (ignored) */ - FVISHOW8, /* display chars with 8th bit set as is (versus M-) */ - FVITABCOMPLETE, /* enable tab as file name completion char */ - FVIESCCOMPLETE, /* enable ESC as file name completion in command mode */ - FXTRACE, /* -x: execution trace */ - FTALKING_I, /* (internal): initial shell was interactive */ - FNFLAGS /* (place holder: how many flags are there) */ -}; - -#define Flag(f) (shell_flags[(int) (f)]) - -EXTERN char shell_flags [FNFLAGS]; - -EXTERN char null [] I__(""); /* null value for variable */ -EXTERN char space [] I__(" "); -EXTERN char newline [] I__("\n"); -EXTERN char slash [] I__("/"); - -enum temp_type { - TT_HEREDOC_EXP, /* expanded heredoc */ - TT_HIST_EDIT /* temp file used for history editing (fc -e) */ -}; -typedef enum temp_type Temp_type; -/* temp/heredoc files. The file is removed when the struct is freed. */ -struct temp { - struct temp *next; - struct shf *shf; - int pid; /* pid of process parsed here-doc */ - Temp_type type; - char *name; -}; - -/* - * stdio and our IO routines - */ - -#define shl_spare (&shf_iob[0]) /* for c_read()/c_print() */ -#define shl_stdout (&shf_iob[1]) -#define shl_out (&shf_iob[2]) -EXTERN int shl_stdout_ok; - -/* - * trap handlers - */ -typedef struct trap { - int signal; /* signal number */ - const char *name; /* short name */ - const char *mess; /* descriptive name */ - char *trap; /* trap command */ - volatile sig_atomic_t set; /* trap pending */ - int flags; /* TF_* */ - sig_t cursig; /* current handler (valid if TF_ORIG_* set) */ - sig_t shtrap; /* shell signal handler */ -} Trap; - -/* values for Trap.flags */ -#define TF_SHELL_USES BIT(0) /* shell uses signal, user can't change */ -#define TF_USER_SET BIT(1) /* user has (tried to) set trap */ -#define TF_ORIG_IGN BIT(2) /* original action was SIG_IGN */ -#define TF_ORIG_DFL BIT(3) /* original action was SIG_DFL */ -#define TF_EXEC_IGN BIT(4) /* restore SIG_IGN just before exec */ -#define TF_EXEC_DFL BIT(5) /* restore SIG_DFL just before exec */ -#define TF_DFL_INTR BIT(6) /* when received, default action is LINTR */ -#define TF_TTY_INTR BIT(7) /* tty generated signal (see j_waitj) */ -#define TF_CHANGED BIT(8) /* used by runtrap() to detect trap changes */ -#define TF_FATAL BIT(9) /* causes termination if not trapped */ - -/* values for setsig()/setexecsig() flags argument */ -#define SS_RESTORE_MASK 0x3 /* how to restore a signal before an exec() */ -#define SS_RESTORE_CURR 0 /* leave current handler in place */ -#define SS_RESTORE_ORIG 1 /* restore original handler */ -#define SS_RESTORE_DFL 2 /* restore to SIG_DFL */ -#define SS_RESTORE_IGN 3 /* restore to SIG_IGN */ -#define SS_FORCE BIT(3) /* set signal even if original signal ignored */ -#define SS_USER BIT(4) /* user is doing the set (ie, trap command) */ -#define SS_SHTRAP BIT(5) /* trap for internal use (CHLD,ALRM,WINCH) */ - -#define SIGEXIT_ 0 /* for trap EXIT */ -#define SIGERR_ NSIG /* for trap ERR */ - -EXTERN volatile sig_atomic_t trap; /* traps pending? */ -EXTERN volatile sig_atomic_t intrsig; /* pending trap interrupts executing command */ -EXTERN volatile sig_atomic_t fatal_trap;/* received a fatal signal */ -#ifndef FROM_TRAP_C -/* Kludge to avoid bogus re-declaration of sigtraps[] error on AIX 3.2.5 */ -extern Trap sigtraps[NSIG + 1]; -#endif /* !FROM_TRAP_C */ - -/* - * TMOUT support - */ -/* values for ksh_tmout_state */ -enum tmout_enum { - TMOUT_EXECUTING = 0, /* executing commands */ - TMOUT_READING, /* waiting for input */ - TMOUT_LEAVING /* have timed out */ - }; -EXTERN unsigned int ksh_tmout; -EXTERN enum tmout_enum ksh_tmout_state I__(TMOUT_EXECUTING); - -/* For "You have stopped jobs" message */ -EXTERN int really_exit; - -/* - * fast character classes - */ -#define C_ALPHA BIT(0) /* a-z_A-Z */ -#define C_DIGIT BIT(1) /* 0-9 */ -#define C_LEX1 BIT(2) /* \0 \t\n|&;<>() */ -#define C_VAR1 BIT(3) /* *@#!$-? */ -#define C_IFSWS BIT(4) /* \t \n (IFS white space) */ -#define C_SUBOP1 BIT(5) /* "=-+?" */ -#define C_SUBOP2 BIT(6) /* "#%" */ -#define C_IFS BIT(7) /* $IFS */ -#define C_QUOTE BIT(8) /* \n\t"#$&'()*;<>?[\`| (needing quoting) */ - -extern short ctypes []; - -#define ctype(c, t) !!(ctypes[(unsigned char)(c)]&(t)) -#define letter(c) ctype(c, C_ALPHA) -#define digit(c) ctype(c, C_DIGIT) -#define letnum(c) ctype(c, C_ALPHA|C_DIGIT) - -EXTERN int ifs0 I__(' '); /* for "$*" */ - -/* Argument parsing for built-in commands and getopts command */ - -/* Values for Getopt.flags */ -#define GF_ERROR BIT(0) /* call errorf() if there is an error */ -#define GF_PLUSOPT BIT(1) /* allow +c as an option */ -#define GF_NONAME BIT(2) /* don't print argv[0] in errors */ - -/* Values for Getopt.info */ -#define GI_MINUS BIT(0) /* an option started with -... */ -#define GI_PLUS BIT(1) /* an option started with +... */ -#define GI_MINUSMINUS BIT(2) /* arguments were ended with -- */ - -typedef struct { - int optind; - int uoptind;/* what user sees in $OPTIND */ - char *optarg; - int flags; /* see GF_* */ - int info; /* see GI_* */ - unsigned int p; /* 0 or index into argv[optind - 1] */ - char buf[2]; /* for bad option OPTARG value */ -} Getopt; - -EXTERN Getopt builtin_opt; /* for shell builtin commands */ -EXTERN Getopt user_opt; /* parsing state for getopts builtin command */ - -/* This for co-processes */ - -typedef INT32 Coproc_id; /* something that won't (realisticly) wrap */ -struct coproc { - int read; /* pipe from co-process's stdout */ - int readw; /* other side of read (saved temporarily) */ - int write; /* pipe to co-process's stdin */ - Coproc_id id; /* id of current output pipe */ - int njobs; /* number of live jobs using output pipe */ - void *job; /* 0 or job of co-process using input pipe */ -}; -EXTERN struct coproc coproc; - -/* Used in jobs.c and by coprocess stuff in exec.c */ -#ifdef JOB_SIGS -EXTERN sigset_t sm_default, sm_sigchld; -#endif /* JOB_SIGS */ - -extern const char ksh_version[]; - -/* name of called builtin function (used by error functions) */ -EXTERN char *builtin_argv0; -EXTERN Tflag builtin_flag; /* flags of called builtin (SPEC_BI, etc.) */ - -/* current working directory, and size of memory allocated for same */ -EXTERN char *current_wd; -EXTERN int current_wd_size; - -/* Minimum required space to work with on a line - if the prompt leaves less - * space than this on a line, the prompt is truncated. - */ -# define MIN_EDIT_SPACE 7 -/* Minimum allowed value for x_cols: 2 for prompt, 3 for " < " at end of line - */ -# define MIN_COLS (2 + MIN_EDIT_SPACE + 3) -EXTERN int x_cols I__(80); /* tty columns */ - -/* These to avoid bracket matching problems */ -#define OPAREN '(' -#define CPAREN ')' -#define OBRACK '[' -#define CBRACK ']' -#define OBRACE '{' -#define CBRACE '}' - -/* Determine the location of the system (common) profile */ -#ifndef KSH_SYSTEM_PROFILE -# ifdef __NeXT -# define KSH_SYSTEM_PROFILE "/etc/profile.std" -# else /* __NeXT */ -# define KSH_SYSTEM_PROFILE "/etc/profile" -# endif /* __NeXT */ -#endif /* KSH_SYSTEM_PROFILE */ - -/* Used by v_evaluate() and setstr() to control action when error occurs */ -#define KSH_UNWIND_ERROR 0 /* unwind the stack (longjmp) */ -#define KSH_RETURN_ERROR 1 /* return 1/0 for success/failure */ - -#include "shf.h" -#include "table.h" -#include "tree.h" -#include "expand.h" -#include "lex.h" -#include "proto.h" - -/* be sure not to interfere with anyone else's idea about EXTERN */ -#ifdef EXTERN_DEFINED -# undef EXTERN_DEFINED -# undef EXTERN -#endif -#undef I__ - -#ifndef __RCSID -#ifdef __ELF__ -#define __RCSID(x) static const char __rcsid[] \ - GCC_FUNC_ATTR(section(".comment")) = (x) -#else -#define __RCSID(x) static const char __rcsid[] = (x) -#endif -#endif - -#endif /* ndef SH_H */ diff --git a/shf.c b/shf.c deleted file mode 100644 index 00f13f9..0000000 --- a/shf.c +++ /dev/null @@ -1,1226 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: shf.c,v 1.10 2004/12/18 22:35:41 millert Exp $ */ - -/* - * Shell file I/O routines - */ - -#include "sh.h" -#include "ksh_stat.h" - -__RCSID("$MirOS$"); - -/* flags to shf_emptybuf() */ -#define EB_READSW 0x01 /* about to switch to reading */ -#define EB_GROW 0x02 /* grow buffer if necessary (STRING+DYNAMIC) */ - -/* - * Replacement stdio routines. Stdio is too flakey on too many machines - * to be useful when you have multiple processes using the same underlying - * file descriptors. - */ - -static int shf_fillbuf(struct shf *shf); -static int shf_emptybuf(struct shf *shf, int flags); - -/* Open a file. First three args are for open(), last arg is flags for - * this package. Returns NULL if file could not be opened, or if a dup - * fails. - */ -struct shf * -shf_open(const char *name, int oflags, int mode, int sflags) -{ - struct shf *shf; - int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE; - int fd; - - /* Done before open so if alloca fails, fd won't be lost. */ - shf = (struct shf *) alloc(sizeof(struct shf) + bsize, ATEMP); - shf->areap = ATEMP; - shf->buf = (unsigned char *) &shf[1]; - shf->bsize = bsize; - shf->flags = SHF_ALLOCS; - /* Rest filled in by reopen. */ - - fd = open(name, oflags, mode); - if (fd < 0) { - afree(shf, shf->areap); - return NULL; - } - if ((sflags & SHF_MAPHI) && fd < FDBASE) { - int nfd; - - nfd = ksh_dupbase(fd, FDBASE); - close(fd); - if (nfd < 0) { - afree(shf, shf->areap); - return NULL; - } - fd = nfd; - } - sflags &= ~SHF_ACCMODE; - sflags |= (oflags & O_ACCMODE) == O_RDONLY ? SHF_RD - : ((oflags & O_ACCMODE) == O_WRONLY ? SHF_WR - : SHF_RDWR); - - return shf_reopen(fd, sflags, shf); -} - -/* Set up the shf structure for a file descriptor. Doesn't fail. */ -struct shf * -shf_fdopen(int fd, int sflags, struct shf *shf) -{ - int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE; - - /* use fcntl() to figure out correct read/write flags */ - if (sflags & SHF_GETFL) { - int flags = fcntl(fd, F_GETFL, 0); - - if (flags < 0) - /* will get an error on first read/write */ - sflags |= SHF_RDWR; - else - switch (flags & O_ACCMODE) { - case O_RDONLY: sflags |= SHF_RD; break; - case O_WRONLY: sflags |= SHF_WR; break; - case O_RDWR: sflags |= SHF_RDWR; break; - } - } - - if (!(sflags & (SHF_RD | SHF_WR))) - internal_errorf(1, "shf_fdopen: missing read/write"); - - if (shf) { - if (bsize) { - shf->buf = (unsigned char *) alloc(bsize, ATEMP); - sflags |= SHF_ALLOCB; - } else - shf->buf = NULL; - } else { - shf = (struct shf *) alloc(sizeof(struct shf) + bsize, ATEMP); - shf->buf = (unsigned char *) &shf[1]; - sflags |= SHF_ALLOCS; - } - shf->areap = ATEMP; - shf->fd = fd; - shf->rp = shf->wp = shf->buf; - shf->rnleft = 0; - shf->rbsize = bsize; - shf->wnleft = 0; /* force call to shf_emptybuf() */ - shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize; - shf->flags = sflags; - shf->errno_ = 0; - shf->bsize = bsize; - if (sflags & SHF_CLEXEC) - fcntl(fd, F_SETFD, FD_CLOEXEC); - return shf; -} - -/* Set up an existing shf (and buffer) to use the given fd */ -struct shf * -shf_reopen(int fd, int sflags, struct shf *shf) -{ - int bsize = sflags & SHF_UNBUF ? (sflags & SHF_RD ? 1 : 0) : SHF_BSIZE; - - /* use fcntl() to figure out correct read/write flags */ - if (sflags & SHF_GETFL) { - int flags = fcntl(fd, F_GETFL, 0); - - if (flags < 0) - /* will get an error on first read/write */ - sflags |= SHF_RDWR; - else - switch (flags & O_ACCMODE) { - case O_RDONLY: sflags |= SHF_RD; break; - case O_WRONLY: sflags |= SHF_WR; break; - case O_RDWR: sflags |= SHF_RDWR; break; - } - } - - if (!(sflags & (SHF_RD | SHF_WR))) - internal_errorf(1, "shf_reopen: missing read/write"); - if (!shf || !shf->buf || shf->bsize < bsize) - internal_errorf(1, "shf_reopen: bad shf/buf/bsize"); - - /* assumes shf->buf and shf->bsize already set up */ - shf->fd = fd; - shf->rp = shf->wp = shf->buf; - shf->rnleft = 0; - shf->rbsize = bsize; - shf->wnleft = 0; /* force call to shf_emptybuf() */ - shf->wbsize = sflags & SHF_UNBUF ? 0 : bsize; - shf->flags = (shf->flags & (SHF_ALLOCS | SHF_ALLOCB)) | sflags; - shf->errno_ = 0; - if (sflags & SHF_CLEXEC) - fcntl(fd, F_SETFD, FD_CLOEXEC); - return shf; -} - -/* Open a string for reading or writing. If reading, bsize is the number - * of bytes that can be read. If writing, bsize is the maximum number of - * bytes that can be written. If shf is not null, it is filled in and - * returned, if it is null, shf is allocated. If writing and buf is null - * and SHF_DYNAMIC is set, the buffer is allocated (if bsize > 0, it is - * used for the initial size). Doesn't fail. - * When writing, a byte is reserved for a trailing null - see shf_sclose(). - */ -struct shf * -shf_sopen(char *buf, int bsize, int sflags, struct shf *shf) -{ - /* can't have a read+write string */ - if (!(sflags & (SHF_RD | SHF_WR)) - || (sflags & (SHF_RD | SHF_WR)) == (SHF_RD | SHF_WR)) - internal_errorf(1, "shf_sopen: flags 0x%x", sflags); - - if (!shf) { - shf = (struct shf *) alloc(sizeof(struct shf), ATEMP); - sflags |= SHF_ALLOCS; - } - shf->areap = ATEMP; - if (!buf && (sflags & SHF_WR) && (sflags & SHF_DYNAMIC)) { - if (bsize <= 0) - bsize = 64; - sflags |= SHF_ALLOCB; - buf = alloc(bsize, shf->areap); - } - shf->fd = -1; - shf->buf = shf->rp = shf->wp = (unsigned char *) buf; - shf->rnleft = bsize; - shf->rbsize = bsize; - shf->wnleft = bsize - 1; /* space for a '\0' */ - shf->wbsize = bsize; - shf->flags = sflags | SHF_STRING; - shf->errno_ = 0; - shf->bsize = bsize; - - return shf; -} - -/* Flush and close file descriptor, free the shf structure */ -int -shf_close(struct shf *shf) -{ - int ret = 0; - - if (shf->fd >= 0) { - ret = shf_flush(shf); - if (close(shf->fd) < 0) - ret = EOF; - } - if (shf->flags & SHF_ALLOCS) - afree(shf, shf->areap); - else if (shf->flags & SHF_ALLOCB) - afree(shf->buf, shf->areap); - - return ret; -} - -/* Flush and close file descriptor, don't free file structure */ -int -shf_fdclose(struct shf *shf) -{ - int ret = 0; - - if (shf->fd >= 0) { - ret = shf_flush(shf); - if (close(shf->fd) < 0) - ret = EOF; - shf->rnleft = 0; - shf->rp = shf->buf; - shf->wnleft = 0; - shf->fd = -1; - } - - return ret; -} - -/* Close a string - if it was opened for writing, it is null terminated; - * returns a pointer to the string and frees shf if it was allocated - * (does not free string if it was allocated). - */ -char * -shf_sclose(struct shf *shf) -{ - unsigned char *s = shf->buf; - - /* null terminate */ - if (shf->flags & SHF_WR) { - shf->wnleft++; - shf_putc('\0', shf); - } - if (shf->flags & SHF_ALLOCS) - afree(shf, shf->areap); - return (char *) s; -} - -/* Flush and free file structure, don't close file descriptor */ -int -shf_finish(struct shf *shf) -{ - int ret = 0; - - if (shf->fd >= 0) - ret = shf_flush(shf); - if (shf->flags & SHF_ALLOCS) - afree(shf, shf->areap); - else if (shf->flags & SHF_ALLOCB) - afree(shf->buf, shf->areap); - - return ret; -} - -/* Un-read what has been read but not examined, or write what has been - * buffered. Returns 0 for success, EOF for (write) error. - */ -int -shf_flush(struct shf *shf) -{ - if (shf->flags & SHF_STRING) - return (shf->flags & SHF_WR) ? EOF : 0; - - if (shf->fd < 0) - internal_errorf(1, "shf_flush: no fd"); - - if (shf->flags & SHF_ERROR) { - errno = shf->errno_; - return EOF; - } - - if (shf->flags & SHF_READING) { - shf->flags &= ~(SHF_EOF | SHF_READING); - if (shf->rnleft > 0) { - lseek(shf->fd, (off_t) -shf->rnleft, 1); - shf->rnleft = 0; - shf->rp = shf->buf; - } - return 0; - } else if (shf->flags & SHF_WRITING) - return shf_emptybuf(shf, 0); - - return 0; -} - -/* Write out any buffered data. If currently reading, flushes the read - * buffer. Returns 0 for success, EOF for (write) error. - */ -static int -shf_emptybuf(struct shf *shf, int flags) -{ - int ret = 0; - - if (!(shf->flags & SHF_STRING) && shf->fd < 0) - internal_errorf(1, "shf_emptybuf: no fd"); - - if (shf->flags & SHF_ERROR) { - errno = shf->errno_; - return EOF; - } - - if (shf->flags & SHF_READING) { - if (flags & EB_READSW) /* doesn't happen */ - return 0; - ret = shf_flush(shf); - shf->flags &= ~SHF_READING; - } - if (shf->flags & SHF_STRING) { - unsigned char *nbuf; - - /* Note that we assume SHF_ALLOCS is not set if SHF_ALLOCB - * is set... (changing the shf pointer could cause problems) - */ - if (!(flags & EB_GROW) || !(shf->flags & SHF_DYNAMIC) - || !(shf->flags & SHF_ALLOCB)) - return EOF; - /* allocate more space for buffer */ - nbuf = (unsigned char *) aresize(shf->buf, shf->wbsize * 2, - shf->areap); - shf->rp = nbuf + (shf->rp - shf->buf); - shf->wp = nbuf + (shf->wp - shf->buf); - shf->rbsize += shf->wbsize; - shf->wnleft += shf->wbsize; - shf->wbsize *= 2; - shf->buf = nbuf; - } else { - if (shf->flags & SHF_WRITING) { - int ntowrite = shf->wp - shf->buf; - unsigned char *buf = shf->buf; - int n; - - while (ntowrite > 0) { - n = write(shf->fd, buf, ntowrite); - if (n < 0) { - if (errno == EINTR - && !(shf->flags & SHF_INTERRUPT)) - continue; - shf->flags |= SHF_ERROR; - shf->errno_ = errno; - shf->wnleft = 0; - if (buf != shf->buf) { - /* allow a second flush - * to work */ - memmove(shf->buf, buf, - ntowrite); - shf->wp = shf->buf + ntowrite; - } - return EOF; - } - buf += n; - ntowrite -= n; - } - if (flags & EB_READSW) { - shf->wp = shf->buf; - shf->wnleft = 0; - shf->flags &= ~SHF_WRITING; - return 0; - } - } - shf->wp = shf->buf; - shf->wnleft = shf->wbsize; - } - shf->flags |= SHF_WRITING; - - return ret; -} - -/* Fill up a read buffer. Returns EOF for a read error, 0 otherwise. */ -static int -shf_fillbuf(struct shf *shf) -{ - if (shf->flags & SHF_STRING) - return 0; - - if (shf->fd < 0) - internal_errorf(1, "shf_fillbuf: no fd"); - - if (shf->flags & (SHF_EOF | SHF_ERROR)) { - if (shf->flags & SHF_ERROR) - errno = shf->errno_; - return EOF; - } - - if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF) - return EOF; - - shf->flags |= SHF_READING; - - shf->rp = shf->buf; - while (1) { - shf->rnleft = blocking_read(shf->fd, (char *) shf->buf, - shf->rbsize); - if (shf->rnleft < 0 && errno == EINTR - && !(shf->flags & SHF_INTERRUPT)) - continue; - break; - } - if (shf->rnleft <= 0) { - if (shf->rnleft < 0) { - shf->flags |= SHF_ERROR; - shf->errno_ = errno; - shf->rnleft = 0; - shf->rp = shf->buf; - return EOF; - } - shf->flags |= SHF_EOF; - } - return 0; -} - -/* Seek to a new position in the file. If writing, flushes the buffer - * first. If reading, optimizes small relative seeks that stay inside the - * buffer. Returns 0 for success, EOF otherwise. - */ -int -shf_seek(struct shf *shf, off_t where, int from) -{ - if (shf->fd < 0) { - errno = EINVAL; - return EOF; - } - - if (shf->flags & SHF_ERROR) { - errno = shf->errno_; - return EOF; - } - - if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF) - return EOF; - - if (shf->flags & SHF_READING) { - if (from == SEEK_CUR && - (where < 0 ? - -where >= shf->rbsize - shf->rnleft : - where < shf->rnleft)) { - shf->rnleft -= where; - shf->rp += where; - return 0; - } - shf->rnleft = 0; - shf->rp = shf->buf; - } - - shf->flags &= ~(SHF_EOF | SHF_READING | SHF_WRITING); - - if (lseek(shf->fd, where, from) < 0) { - shf->errno_ = errno; - shf->flags |= SHF_ERROR; - return EOF; - } - - return 0; -} - - -/* Read a buffer from shf. Returns the number of bytes read into buf, - * if no bytes were read, returns 0 if end of file was seen, EOF if - * a read error occurred. - */ -int -shf_read(char *buf, int bsize, struct shf *shf) -{ - int orig_bsize = bsize; - int ncopy; - - if (!(shf->flags & SHF_RD)) - internal_errorf(1, "shf_read: flags %x", shf->flags); - - if (bsize <= 0) - internal_errorf(1, "shf_read: bsize %d", bsize); - - while (bsize > 0) { - if (shf->rnleft == 0 - && (shf_fillbuf(shf) == EOF || shf->rnleft == 0)) - break; - ncopy = shf->rnleft; - if (ncopy > bsize) - ncopy = bsize; - memcpy(buf, shf->rp, ncopy); - buf += ncopy; - bsize -= ncopy; - shf->rp += ncopy; - shf->rnleft -= ncopy; - } - /* Note: fread(3S) returns 0 for errors - this doesn't */ - return orig_bsize == bsize ? (shf_error(shf) ? EOF : 0) - : orig_bsize - bsize; -} - -/* Read up to a newline or EOF. The newline is put in buf; buf is always - * null terminated. Returns NULL on read error or if nothing was read before - * end of file, returns a pointer to the null byte in buf otherwise. - */ -char * -shf_getse(char *buf, int bsize, struct shf *shf) -{ - unsigned char *end; - int ncopy; - char *orig_buf = buf; - - if (!(shf->flags & SHF_RD)) - internal_errorf(1, "shf_getse: flags %x", shf->flags); - - if (bsize <= 0) - return NULL; - - --bsize; /* save room for null */ - do { - if (shf->rnleft == 0) { - if (shf_fillbuf(shf) == EOF) - return NULL; - if (shf->rnleft == 0) { - *buf = '\0'; - return buf == orig_buf ? NULL : buf; - } - } - end = (unsigned char *) memchr((char *) shf->rp, '\n', - shf->rnleft); - ncopy = end ? end - shf->rp + 1 : shf->rnleft; - if (ncopy > bsize) - ncopy = bsize; - memcpy(buf, (char *) shf->rp, ncopy); - shf->rp += ncopy; - shf->rnleft -= ncopy; - buf += ncopy; - bsize -= ncopy; - } while (!end && bsize); - *buf = '\0'; - return buf; -} - -/* Returns the char read. Returns EOF for error and end of file. */ -int -shf_getchar(struct shf *shf) -{ - if (!(shf->flags & SHF_RD)) - internal_errorf(1, "shf_getchar: flags %x", shf->flags); - - if (shf->rnleft == 0 && (shf_fillbuf(shf) == EOF || shf->rnleft == 0)) - return EOF; - --shf->rnleft; - return *shf->rp++; -} - -/* Put a character back in the input stream. Returns the character if - * successful, EOF if there is no room. - */ -int -shf_ungetc(int c, struct shf *shf) -{ - if (!(shf->flags & SHF_RD)) - internal_errorf(1, "shf_ungetc: flags %x", shf->flags); - - if ((shf->flags & SHF_ERROR) || c == EOF - || (shf->rp == shf->buf && shf->rnleft)) - return EOF; - - if ((shf->flags & SHF_WRITING) && shf_emptybuf(shf, EB_READSW) == EOF) - return EOF; - - if (shf->rp == shf->buf) - shf->rp = shf->buf + shf->rbsize; - if (shf->flags & SHF_STRING) { - /* Can unget what was read, but not something different - we - * don't want to modify a string. - */ - if (shf->rp[-1] != c) - return EOF; - shf->flags &= ~SHF_EOF; - shf->rp--; - shf->rnleft++; - return c; - } - shf->flags &= ~SHF_EOF; - *--(shf->rp) = c; - shf->rnleft++; - return c; -} - -/* Write a character. Returns the character if successful, EOF if - * the char could not be written. - */ -int -shf_putchar(int c, struct shf *shf) -{ - if (!(shf->flags & SHF_WR)) - internal_errorf(1, "shf_putchar: flags %x", shf->flags); - - if (c == EOF) - return EOF; - - if (shf->flags & SHF_UNBUF) { - char cc = c; - int n; - - if (shf->fd < 0) - internal_errorf(1, "shf_putchar: no fd"); - if (shf->flags & SHF_ERROR) { - errno = shf->errno_; - return EOF; - } - while ((n = write(shf->fd, &cc, 1)) != 1) - if (n < 0) { - if (errno == EINTR - && !(shf->flags & SHF_INTERRUPT)) - continue; - shf->flags |= SHF_ERROR; - shf->errno_ = errno; - return EOF; - } - } else { - /* Flush deals with strings and sticky errors */ - if (shf->wnleft == 0 && shf_emptybuf(shf, EB_GROW) == EOF) - return EOF; - shf->wnleft--; - *shf->wp++ = c; - } - - return c; -} - -/* Write a string. Returns the length of the string if successful, EOF if - * the string could not be written. - */ -int -shf_puts(const char *s, struct shf *shf) -{ - if (!s) - return EOF; - - return shf_write(s, strlen(s), shf); -} - -/* Write a buffer. Returns nbytes if successful, EOF if there is an error. */ -int -shf_write(const char *buf, int nbytes, struct shf *shf) -{ - int orig_nbytes = nbytes; - int n; - int ncopy; - - if (!(shf->flags & SHF_WR)) - internal_errorf(1, "shf_write: flags %x", shf->flags); - - if (nbytes < 0) - internal_errorf(1, "shf_write: nbytes %d", nbytes); - - /* Don't buffer if buffer is empty and we're writting a large amount. */ - if ((ncopy = shf->wnleft) - && (shf->wp != shf->buf || nbytes < shf->wnleft)) - { - if (ncopy > nbytes) - ncopy = nbytes; - memcpy(shf->wp, buf, ncopy); - nbytes -= ncopy; - buf += ncopy; - shf->wp += ncopy; - shf->wnleft -= ncopy; - } - if (nbytes > 0) { - /* Flush deals with strings and sticky errors */ - if (shf_emptybuf(shf, EB_GROW) == EOF) - return EOF; - if (nbytes > shf->wbsize) { - ncopy = nbytes; - if (shf->wbsize) - ncopy -= nbytes % shf->wbsize; - nbytes -= ncopy; - while (ncopy > 0) { - n = write(shf->fd, buf, ncopy); - if (n < 0) { - if (errno == EINTR - && !(shf->flags & SHF_INTERRUPT)) - continue; - shf->flags |= SHF_ERROR; - shf->errno_ = errno; - shf->wnleft = 0; - /* Note: fwrite(3S) returns 0 for - * errors - this doesn't */ - return EOF; - } - buf += n; - ncopy -= n; - } - } - if (nbytes > 0) { - memcpy(shf->wp, buf, nbytes); - shf->wp += nbytes; - shf->wnleft -= nbytes; - } - } - - return orig_nbytes; -} - -int -shf_fprintf(struct shf *shf, const char *fmt, ...) -{ - va_list args; - int n; - - SH_VA_START(args, fmt); - n = shf_vfprintf(shf, fmt, args); - va_end(args); - - return n; -} - -int -shf_snprintf(char *buf, int bsize, const char *fmt, ...) -{ - struct shf shf; - va_list args; - int n; - - if (!buf || bsize <= 0) - internal_errorf(1, "shf_snprintf: buf %lx, bsize %d", - (long) buf, bsize); - - shf_sopen(buf, bsize, SHF_WR, &shf); - SH_VA_START(args, fmt); - n = shf_vfprintf(&shf, fmt, args); - va_end(args); - shf_sclose(&shf); /* null terminates */ - return n; -} - -char * -shf_smprintf(const char *fmt, ...) -{ - struct shf shf; - va_list args; - - shf_sopen(NULL, 0, SHF_WR|SHF_DYNAMIC, &shf); - SH_VA_START(args, fmt); - shf_vfprintf(&shf, fmt, args); - va_end(args); - return shf_sclose(&shf); /* null terminates */ -} - -#undef FP /* if you want floating point stuff */ - -#define BUF_SIZE 128 -#ifndef DMAXEXP -# define DMAXEXP 128 /* should be big enough */ -#endif -#define FPBUF_SIZE (DMAXEXP+16)/* this must be > - * MAX(DMAXEXP, log10(pow(2, DSIGNIF))) - * + ceil(log10(DMAXEXP)) + 8 (I think). - * Since this is hard to express as a - * constant, just use a large buffer. - */ - -/* - * What kinda of machine we on? Hopefully the C compiler will optimize - * this out... - * - * For shorts, we want sign extend for %d but not for %[oxu] - on 16 bit - * machines it don't matter. Assumes C compiler has converted shorts to - * ints before pushing them. - */ -#define POP_INT(f, s, a) (((f) & FL_LONG) ? \ - va_arg((a), unsigned long) \ - : \ - (sizeof(int) < sizeof(long) ? \ - ((s) ? \ - (long) va_arg((a), int) \ - : \ - va_arg((a), unsigned)) \ - : \ - va_arg((a), unsigned))) - -#define ABIGNUM 32000 /* big numer that will fit in a short */ -#define LOG2_10 3.321928094887362347870319429 /* log base 2 of 10 */ - -#define FL_HASH 0x001 /* '#' seen */ -#define FL_PLUS 0x002 /* '+' seen */ -#define FL_RIGHT 0x004 /* '-' seen */ -#define FL_BLANK 0x008 /* ' ' seen */ -#define FL_SHORT 0x010 /* 'h' seen */ -#define FL_LONG 0x020 /* 'l' seen */ -#define FL_ZERO 0x040 /* '0' seen */ -#define FL_DOT 0x080 /* '.' seen */ -#define FL_UPPER 0x100 /* format character was uppercase */ -#define FL_NUMBER 0x200 /* a number was formated %[douxefg] */ - - -#ifdef FP -#include - -static double -my_ceil(d) - double d; -{ - double i; - - return d - modf(d, &i) + (d < 0 ? -1 : 1); -} -#endif /* FP */ - -int -shf_vfprintf(struct shf *shf, const char *fmt, va_list args) -{ - char c, *s; - int UNINITIALIZED(tmp); - int field, precision; - int len; - int flags; - unsigned long lnum; - /* %#o produces the longest output */ - char numbuf[(8*sizeof(long) + 2) / 3 + 1]; - /* this stuff for dealing with the buffer */ - int nwritten = 0; -#ifdef FP - /* should be in - * extern double frexp(); - */ - extern char *ecvt(); - - double fpnum; - int expo, decpt; - char style; - char fpbuf[FPBUF_SIZE]; -#endif /* FP */ - - if (!fmt) - return 0; - - while ((c = *fmt++)) { - if (c != '%') { - shf_putc(c, shf); - nwritten++; - continue; - } - /* - * This will accept flags/fields in any order - not - * just the order specified in printf(3), but this is - * the way _doprnt() seems to work (on bsd and sysV). - * The only restriction is that the format character must - * come last :-). - */ - flags = field = precision = 0; - for ( ; (c = *fmt++) ; ) { - switch (c) { - case '#': - flags |= FL_HASH; - continue; - - case '+': - flags |= FL_PLUS; - continue; - - case '-': - flags |= FL_RIGHT; - continue; - - case ' ': - flags |= FL_BLANK; - continue; - - case '0': - if (!(flags & FL_DOT)) - flags |= FL_ZERO; - continue; - - case '.': - flags |= FL_DOT; - precision = 0; - continue; - - case '*': - tmp = va_arg(args, int); - if (flags & FL_DOT) - precision = tmp; - else if ((field = tmp) < 0) { - field = -field; - flags |= FL_RIGHT; - } - continue; - - case 'l': - flags |= FL_LONG; - continue; - - case 'h': - flags |= FL_SHORT; - continue; - } - if (digit(c)) { - tmp = c - '0'; - while (c = *fmt++, digit(c)) - tmp = tmp * 10 + c - '0'; - --fmt; - if (tmp < 0) /* overflow? */ - tmp = 0; - if (flags & FL_DOT) - precision = tmp; - else - field = tmp; - continue; - } - break; - } - - if (precision < 0) - precision = 0; - - if (!c) /* nasty format */ - break; - - if (c >= 'A' && c <= 'Z') { - flags |= FL_UPPER; - c = c - 'A' + 'a'; - } - - switch (c) { - case 'p': /* pointer */ - flags &= ~(FL_LONG | FL_SHORT); - if (sizeof(char *) > sizeof(int)) - flags |= FL_LONG; /* hope it fits.. */ - /* aaahhh... */ - case 'd': - case 'i': - case 'o': - case 'u': - case 'x': - flags |= FL_NUMBER; - s = &numbuf[sizeof(numbuf)]; - lnum = POP_INT(flags, c == 'd', args); - switch (c) { - case 'd': - case 'i': - if (0 > (long) lnum) - lnum = - (long) lnum, tmp = 1; - else - tmp = 0; - /* aaahhhh..... */ - - case 'u': - do { - *--s = lnum % 10 + '0'; - lnum /= 10; - } while (lnum); - - if (c != 'u') { - if (tmp) - *--s = '-'; - else if (flags & FL_PLUS) - *--s = '+'; - else if (flags & FL_BLANK) - *--s = ' '; - } - break; - - case 'o': - do { - *--s = (lnum & 0x7) + '0'; - lnum >>= 3; - } while (lnum); - - if ((flags & FL_HASH) && *s != '0') - *--s = '0'; - break; - - case 'p': - case 'x': - { - const char *digits = (flags & FL_UPPER) ? - "0123456789ABCDEF" - : "0123456789abcdef"; - do { - *--s = digits[lnum & 0xf]; - lnum >>= 4; - } while (lnum); - - if (flags & FL_HASH) { - *--s = (flags & FL_UPPER) ? 'X' : 'x'; - *--s = '0'; - } - } - } - len = &numbuf[sizeof(numbuf)] - s; - if (flags & FL_DOT) { - if (precision > len) { - field = precision; - flags |= FL_ZERO; - } else - precision = len; /* no loss */ - } - break; - -#ifdef FP - case 'e': - case 'g': - case 'f': - { - char *p; - - /* - * This could probably be done better, - * but it seems to work. Note that gcvt() - * is not used, as you cannot tell it to - * not strip the zeros. - */ - flags |= FL_NUMBER; - if (!(flags & FL_DOT)) - precision = 6; /* default */ - /* - * Assumes doubles are pushed on - * the stack. If this is not so, then - * FL_LONG/FL_SHORT should be checked. - */ - fpnum = va_arg(args, double); - s = fpbuf; - style = c; - /* - * This is the same as - * expo = ceil(log10(fpnum)) - * but doesn't need -lm. This is an - * approximation as expo is rounded up. - */ - (void) frexp(fpnum, &expo); - expo = my_ceil(expo / LOG2_10); - - if (expo < 0) - expo = 0; - - p = ecvt(fpnum, precision + 1 + expo, - &decpt, &tmp); - if (c == 'g') { - if (decpt < -4 || decpt > precision) - style = 'e'; - else - style = 'f'; - if (decpt > 0 && (precision -= decpt) < 0) - precision = 0; - } - if (tmp) - *s++ = '-'; - else if (flags & FL_PLUS) - *s++ = '+'; - else if (flags & FL_BLANK) - *s++ = ' '; - - if (style == 'e') - *s++ = *p++; - else { - if (decpt > 0) { - /* Overflow check - should - * never have this problem. - */ - if (decpt > - &fpbuf[sizeof(fpbuf)] - - s - 8) - decpt = - &fpbuf[sizeof(fpbuf)] - - s - 8; - (void) memcpy(s, p, decpt); - s += decpt; - p += decpt; - } else - *s++ = '0'; - } - - /* print the fraction? */ - if (precision > 0) { - *s++ = '.'; - /* Overflow check - should - * never have this problem. - */ - if (precision > &fpbuf[sizeof(fpbuf)] - - s - 7) - precision = - &fpbuf[sizeof(fpbuf)] - - s - 7; - for (tmp = decpt; tmp++ < 0 && - precision > 0 ; precision--) - *s++ = '0'; - tmp = strlen(p); - if (precision > tmp) - precision = tmp; - /* Overflow check - should - * never have this problem. - */ - if (precision > &fpbuf[sizeof(fpbuf)] - - s - 7) - precision = - &fpbuf[sizeof(fpbuf)] - - s - 7; - (void) memcpy(s, p, precision); - s += precision; - /* - * 'g' format strips trailing - * zeros after the decimal. - */ - if (c == 'g' && !(flags & FL_HASH)) { - while (*--s == '0') - ; - if (*s != '.') - s++; - } - } else if (flags & FL_HASH) - *s++ = '.'; - - if (style == 'e') { - *s++ = (flags & FL_UPPER) ? 'E' : 'e'; - if (--decpt >= 0) - *s++ = '+'; - else { - *s++ = '-'; - decpt = -decpt; - } - p = &numbuf[sizeof(numbuf)]; - for (tmp = 0; tmp < 2 || decpt ; tmp++) { - *--p = '0' + decpt % 10; - decpt /= 10; - } - tmp = &numbuf[sizeof(numbuf)] - p; - (void) memcpy(s, p, tmp); - s += tmp; - } - - len = s - fpbuf; - s = fpbuf; - precision = len; - break; - } -#endif /* FP */ - - case 's': - if (!(s = va_arg(args, char *))) - s = "(null %s)"; - len = strlen(s); - break; - - case 'c': - flags &= ~FL_DOT; - numbuf[0] = va_arg(args, int); - s = numbuf; - len = 1; - break; - - case '%': - default: - numbuf[0] = c; - s = numbuf; - len = 1; - break; - } - - /* - * At this point s should point to a string that is - * to be formatted, and len should be the length of the - * string. - */ - if (!(flags & FL_DOT) || len < precision) - precision = len; - if (field > precision) { - field -= precision; - if (!(flags & FL_RIGHT)) { - field = -field; - /* skip past sign or 0x when padding with 0 */ - if ((flags & FL_ZERO) && (flags & FL_NUMBER)) { - if (*s == '+' || *s == '-' || *s ==' ') - { - shf_putc(*s, shf); - s++; - precision--; - nwritten++; - } else if (*s == '0') { - shf_putc(*s, shf); - s++; - nwritten++; - if (--precision > 0 && - (*s | 0x20) == 'x') - { - shf_putc(*s, shf); - s++; - precision--; - nwritten++; - } - } - c = '0'; - } else - c = flags & FL_ZERO ? '0' : ' '; - if (field < 0) { - nwritten += -field; - for ( ; field < 0 ; field++) - shf_putc(c, shf); - } - } else - c = ' '; - } else - field = 0; - - if (precision > 0) { - nwritten += precision; - for ( ; precision-- > 0 ; s++) - shf_putc(*s, shf); - } - if (field > 0) { - nwritten += field; - for ( ; field > 0 ; --field) - shf_putc(c, shf); - } - } - - return shf_error(shf) ? EOF : nwritten; -} diff --git a/shf.h b/shf.h deleted file mode 100644 index 4c2b541..0000000 --- a/shf.h +++ /dev/null @@ -1,87 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: shf.h,v 1.2 1999/01/08 20:25:02 millert Exp $ */ - -#ifndef SHF_H -#define SHF_H - -/* - * Shell file I/O routines - */ - -#define SHF_BSIZE 512 - -#define shf_fileno(shf) ((shf)->fd) -#define shf_setfileno(shf,nfd) ((shf)->fd = (nfd)) -#define shf_getc(shf) ((shf)->rnleft > 0 ? (shf)->rnleft--, *(shf)->rp++ : \ - shf_getchar(shf)) -#define shf_putc(c, shf) ((shf)->wnleft == 0 ? shf_putchar((c), (shf)) \ - : ((shf)->wnleft--, *(shf)->wp++ = (c))) -#define shf_eof(shf) ((shf)->flags & SHF_EOF) -#define shf_error(shf) ((shf)->flags & SHF_ERROR) -#define shf_errno(shf) ((shf)->errno_) -#define shf_clearerr(shf) ((shf)->flags &= ~(SHF_EOF | SHF_ERROR)) - -/* Flags passed to shf_*open() */ -#define SHF_RD 0x0001 -#define SHF_WR 0x0002 -#define SHF_RDWR (SHF_RD|SHF_WR) -#define SHF_ACCMODE 0x0003 /* mask */ -#define SHF_GETFL 0x0004 /* use fcntl() to figure RD/WR flags */ -#define SHF_UNBUF 0x0008 /* unbuffered I/O */ -#define SHF_CLEXEC 0x0010 /* set close on exec flag */ -#define SHF_MAPHI 0x0020 /* make fd > FDBASE (and close orig) - * (shf_open() only) */ -#define SHF_DYNAMIC 0x0040 /* string: increase buffer as needed */ -#define SHF_INTERRUPT 0x0080 /* EINTR in read/write causes error */ -/* Flags used internally */ -#define SHF_STRING 0x0100 /* a string, not a file */ -#define SHF_ALLOCS 0x0200 /* shf and shf->buf were alloc()ed */ -#define SHF_ALLOCB 0x0400 /* shf->buf was alloc()ed */ -#define SHF_ERROR 0x0800 /* read()/write() error */ -#define SHF_EOF 0x1000 /* read eof (sticky) */ -#define SHF_READING 0x2000 /* currently reading: rnleft,rp valid */ -#define SHF_WRITING 0x4000 /* currently writing: wnleft,wp valid */ - - -struct shf { - int flags; /* see SHF_* */ - unsigned char *rp; /* read: current position in buffer */ - int rbsize; /* size of buffer (1 if SHF_UNBUF) */ - int rnleft; /* read: how much data left in buffer */ - unsigned char *wp; /* write: current position in buffer */ - int wbsize; /* size of buffer (0 if SHF_UNBUF) */ - int wnleft; /* write: how much space left in buffer */ - unsigned char *buf; /* buffer */ - int fd; /* file descriptor */ - int errno_; /* saved value of errno after error */ - int bsize; /* actual size of buf */ - Area *areap; /* area shf/buf were allocated in */ -}; - -extern struct shf shf_iob[]; - -struct shf *shf_open(const char *name, int oflags, int mode, - int sflags); -struct shf *shf_fdopen(int fd, int sflags, struct shf *shf); -struct shf *shf_reopen(int fd, int sflags, struct shf *shf); -struct shf *shf_sopen(char *buf, int bsize, int sflags, - struct shf *shf); -int shf_close(struct shf *shf); -int shf_fdclose(struct shf *shf); -char *shf_sclose(struct shf *shf); -int shf_finish(struct shf *shf); -int shf_flush(struct shf *shf); -int shf_seek(struct shf *shf, off_t where, int from); -int shf_read(char *buf, int bsize, struct shf *shf); -char *shf_getse(char *buf, int bsize, struct shf *shf); -int shf_getchar(struct shf *shf); -int shf_ungetc(int c, struct shf *shf); -int shf_putchar(int c, struct shf *shf); -int shf_puts(const char *s, struct shf *shf); -int shf_write(const char *buf, int nbytes, struct shf *shf); -int shf_fprintf(struct shf *shf, const char *fmt, ...); -int shf_snprintf(char *buf, int bsize, const char *fmt, ...); -char *shf_smprintf(const char *fmt, ...); -int shf_vfprintf(struct shf *, const char *fmt, va_list args); - -#endif /* SHF_H */ diff --git a/syn.c b/syn.c deleted file mode 100644 index 2a69908..0000000 --- a/syn.c +++ /dev/null @@ -1,910 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: syn.c,v 1.14 2003/10/22 07:40:38 jmc Exp $ */ - -/* - * shell parser (C version) - */ - -#include "sh.h" -#include "c_test.h" - -__RCSID("$MirOS$"); - -struct nesting_state { - int start_token; /* token than began nesting (eg, FOR) */ - int start_line; /* line nesting began on */ -}; - -static void yyparse(void); -static struct op *pipeline(int cf); -static struct op *andor(void); -static struct op *c_list(int multi); -static struct ioword *synio(int cf); -static void musthave(int c, int cf); -static struct op *nested(int type, int smark, int emark); -static struct op *get_command(int cf); -static struct op *dogroup(void); -static struct op *thenpart(void); -static struct op *elsepart(void); -static struct op *caselist(void); -static struct op *casepart(int endtok); -static struct op *function_body(char *name, int ksh_func); -static char ** wordlist(void); -static struct op *block(int type, struct op *t1, struct op *t2, - char **wp); -static struct op *newtp(int type); -static void syntaxerr(const char *what) - GCC_FUNC_ATTR(noreturn); -static void nesting_push(struct nesting_state *save, int tok); -static void nesting_pop(struct nesting_state *saved); -static int assign_command(char *s); -static int inalias(struct source *s); -static int dbtestp_isa(Test_env *te, Test_meta meta); -static const char *dbtestp_getopnd(Test_env *te, Test_op op, - int do_eval); -static int dbtestp_eval(Test_env *te, Test_op op, const char *opnd1, - const char *opnd2, int do_eval); -static void dbtestp_error(Test_env *te, int offset, const char *msg); - -static struct op *outtree; /* yyparse output */ - -static struct nesting_state nesting; /* \n changed to ; */ - -static int reject; /* token(cf) gets symbol again */ -static int symbol; /* yylex value */ - -#define REJECT (reject = 1) -#define ACCEPT (reject = 0) -#define token(cf) \ - ((reject) ? (ACCEPT, symbol) : (symbol = yylex(cf))) -#define tpeek(cf) \ - ((reject) ? (symbol) : (REJECT, symbol = yylex(cf))) - -static void -yyparse(void) -{ - int c; - - ACCEPT; - - outtree = c_list(source->type == SSTRING); - c = tpeek(0); - if (c == 0 && !outtree) - outtree = newtp(TEOF); - else if (c != '\n' && c != 0) - syntaxerr(NULL); -} - -static struct op * -pipeline(int cf) -{ - struct op *t, *p, *tl = NULL; - - t = get_command(cf); - if (t != NULL) { - while (token(0) == '|') { - if ((p = get_command(CONTIN)) == NULL) - syntaxerr(NULL); - if (tl == NULL) - t = tl = block(TPIPE, t, p, NOWORDS); - else - tl = tl->right = block(TPIPE, tl->right, p, NOWORDS); - } - REJECT; - } - return (t); -} - -static struct op * -andor(void) -{ - struct op *t, *p; - int c; - - t = pipeline(0); - if (t != NULL) { - while ((c = token(0)) == LOGAND || c == LOGOR) { - if ((p = pipeline(CONTIN)) == NULL) - syntaxerr(NULL); - t = block(c == LOGAND? TAND: TOR, t, p, NOWORDS); - } - REJECT; - } - return (t); -} - -static struct op * -c_list(int multi) -{ - struct op *t = NULL, *p, *tl = NULL; - int c; - int have_sep; - - while (1) { - p = andor(); - /* Token has always been read/rejected at this point, so - * we don't worry about what flags to pass token() - */ - c = token(0); - have_sep = 1; - if (c == '\n' && (multi || inalias(source))) { - if (!p) /* ignore blank lines */ - continue; - } else if (!p) - break; - else if (c == '&' || c == COPROC) - p = block(c == '&' ? TASYNC : TCOPROC, - p, NOBLOCK, NOWORDS); - else if (c != ';') - have_sep = 0; - if (!t) - t = p; - else if (!tl) - t = tl = block(TLIST, t, p, NOWORDS); - else - tl = tl->right = block(TLIST, tl->right, p, NOWORDS); - if (!have_sep) - break; - } - REJECT; - return t; -} - -static struct ioword * -synio(int cf) -{ - struct ioword *iop; - int ishere; - - if (tpeek(cf) != REDIR) - return NULL; - ACCEPT; - iop = yylval.iop; - ishere = (iop->flag&IOTYPE) == IOHERE; - musthave(LWORD, ishere ? HEREDELIM : 0); - if (ishere) { - iop->delim = yylval.cp; - if (*ident != 0) /* unquoted */ - iop->flag |= IOEVAL; - if (herep >= &heres[HERES]) - yyerror("too many <<'s\n"); - *herep++ = iop; - } else - iop->name = yylval.cp; - return iop; -} - -static void -musthave(int c, int cf) -{ - if ((token(cf)) != c) - syntaxerr(NULL); -} - -static struct op * -nested(int type, int smark, int emark) -{ - struct op *t; - struct nesting_state old_nesting; - - nesting_push(&old_nesting, smark); - t = c_list(true); - musthave(emark, KEYWORD|ALIAS); - nesting_pop(&old_nesting); - return (block(type, t, NOBLOCK, NOWORDS)); -} - -static struct op * -get_command(int cf) -{ - struct op *t; - int c, iopn = 0, syniocf; - struct ioword *iop, **iops; - XPtrV args, vars; - struct nesting_state old_nesting; - - iops = (struct ioword **) alloc(sizeofN(struct ioword *, NUFILE+1), - ATEMP); - XPinit(args, 16); - XPinit(vars, 16); - - syniocf = KEYWORD|ALIAS; - switch (c = token(cf|KEYWORD|ALIAS|VARASN)) { - default: - REJECT; - afree((void*) iops, ATEMP); - XPfree(args); - XPfree(vars); - return NULL; /* empty line */ - - case LWORD: - case REDIR: - REJECT; - syniocf &= ~(KEYWORD|ALIAS); - t = newtp(TCOM); - t->lineno = source->line; - while (1) { - cf = (t->u.evalflags ? ARRAYVAR : 0) - | (XPsize(args) == 0 ? ALIAS|VARASN : CMDWORD); - switch (tpeek(cf)) { - case REDIR: - if (iopn >= NUFILE) - yyerror("too many redirections\n"); - iops[iopn++] = synio(cf); - break; - - case LWORD: - ACCEPT; - /* the iopn == 0 and XPsize(vars) == 0 are - * dubious but at&t ksh acts this way - */ - if (iopn == 0 && XPsize(vars) == 0 - && XPsize(args) == 0 - && assign_command(ident)) - t->u.evalflags = DOVACHECK; - if ((XPsize(args) == 0 || Flag(FKEYWORD)) - && is_wdvarassign(yylval.cp)) - XPput(vars, yylval.cp); - else - XPput(args, yylval.cp); - break; - - case '(': - /* Check for "> foo (echo hi)", which at&t ksh - * allows (not POSIX, but not disallowed) - */ - afree(t, ATEMP); - if (XPsize(args) == 0 && XPsize(vars) == 0) { - ACCEPT; - goto Subshell; - } - /* Must be a function */ - if (iopn != 0 || XPsize(args) != 1 - || XPsize(vars) != 0) - syntaxerr(NULL); - ACCEPT; - /*(*/ - musthave(')', 0); - t = function_body(XPptrv(args)[0], false); - goto Leave; - - default: - goto Leave; - } - } - Leave: - break; - - Subshell: - case '(': - t = nested(TPAREN, '(', ')'); - break; - - case '{': /*}*/ - t = nested(TBRACE, '{', '}'); - break; - - case MDPAREN: - { - static const char let_cmd[] = { CHAR, 'l', CHAR, 'e', - CHAR, 't', EOS }; - /* Leave KEYWORD in syniocf (allow if (( 1 )) then ...) */ - t = newtp(TCOM); - t->lineno = source->line; - ACCEPT; - XPput(args, wdcopy(let_cmd, ATEMP)); - musthave(LWORD,LETEXPR); - XPput(args, yylval.cp); - break; - } - - case DBRACKET: /* [[ .. ]] */ - /* Leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */ - t = newtp(TDBRACKET); - ACCEPT; - { - Test_env te; - - te.flags = TEF_DBRACKET; - te.pos.av = &args; - te.isa = dbtestp_isa; - te.getopnd = dbtestp_getopnd; - te.eval = dbtestp_eval; - te.error = dbtestp_error; - - test_parse(&te); - } - break; - - case FOR: - case SELECT: - t = newtp((c == FOR) ? TFOR : TSELECT); - musthave(LWORD, ARRAYVAR); - if (!is_wdvarname(yylval.cp, true)) - yyerror("%s: bad identifier\n", - c == FOR ? "for" : "select"); - t->str = str_save(ident, ATEMP); - nesting_push(&old_nesting, c); - t->vars = wordlist(); - t->left = dogroup(); - nesting_pop(&old_nesting); - break; - - case WHILE: - case UNTIL: - nesting_push(&old_nesting, c); - t = newtp((c == WHILE) ? TWHILE : TUNTIL); - t->left = c_list(true); - t->right = dogroup(); - nesting_pop(&old_nesting); - break; - - case CASE: - t = newtp(TCASE); - musthave(LWORD, 0); - t->str = yylval.cp; - nesting_push(&old_nesting, c); - t->left = caselist(); - nesting_pop(&old_nesting); - break; - - case IF: - nesting_push(&old_nesting, c); - t = newtp(TIF); - t->left = c_list(true); - t->right = thenpart(); - musthave(FI, KEYWORD|ALIAS); - nesting_pop(&old_nesting); - break; - - case BANG: - syniocf &= ~(KEYWORD|ALIAS); - t = pipeline(0); - if (t == NULL) - syntaxerr(NULL); - t = block(TBANG, NOBLOCK, t, NOWORDS); - break; - - case TIME: - syniocf &= ~(KEYWORD|ALIAS); - t = pipeline(0); - t = block(TTIME, t, NOBLOCK, NOWORDS); - break; - - case FUNCTION: - musthave(LWORD, 0); - t = function_body(yylval.cp, true); - break; - } - - while ((iop = synio(syniocf)) != NULL) { - if (iopn >= NUFILE) - yyerror("too many redirections\n"); - iops[iopn++] = iop; - } - - if (iopn == 0) { - afree((void*) iops, ATEMP); - t->ioact = NULL; - } else { - iops[iopn++] = NULL; - iops = (struct ioword **) aresize((void*) iops, - sizeofN(struct ioword *, iopn), ATEMP); - t->ioact = iops; - } - - if (t->type == TCOM || t->type == TDBRACKET) { - XPput(args, NULL); - t->args = (char **) XPclose(args); - XPput(vars, NULL); - t->vars = (char **) XPclose(vars); - } else { - XPfree(args); - XPfree(vars); - } - - return t; -} - -static struct op * -dogroup(void) -{ - int c; - struct op *list; - - c = token(CONTIN|KEYWORD|ALIAS); - /* A {...} can be used instead of do...done for for/select loops - * but not for while/until loops - we don't need to check if it - * is a while loop because it would have been parsed as part of - * the conditional command list... - */ - if (c == DO) - c = DONE; - else if (c == '{') - c = '}'; - else - syntaxerr(NULL); - list = c_list(true); - musthave(c, KEYWORD|ALIAS); - return list; -} - -static struct op * -thenpart(void) -{ - struct op *t; - - musthave(THEN, KEYWORD|ALIAS); - t = newtp(0); - t->left = c_list(true); - if (t->left == NULL) - syntaxerr(NULL); - t->right = elsepart(); - return (t); -} - -static struct op * -elsepart(void) -{ - struct op *t; - - switch (token(KEYWORD|ALIAS|VARASN)) { - case ELSE: - if ((t = c_list(true)) == NULL) - syntaxerr(NULL); - return (t); - - case ELIF: - t = newtp(TELIF); - t->left = c_list(true); - t->right = thenpart(); - return (t); - - default: - REJECT; - } - return NULL; -} - -static struct op * -caselist(void) -{ - struct op *t, *tl; - int c; - - c = token(CONTIN|KEYWORD|ALIAS); - /* A {...} can be used instead of in...esac for case statements */ - if (c == IN) - c = ESAC; - else if (c == '{') - c = '}'; - else - syntaxerr(NULL); - t = tl = NULL; - while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) { /* no ALIAS here */ - struct op *tc = casepart(c); - if (tl == NULL) - t = tl = tc, tl->right = NULL; - else - tl->right = tc, tl = tc; - } - musthave(c, KEYWORD|ALIAS); - return (t); -} - -static struct op * -casepart(int endtok) -{ - struct op *t; - int c; - XPtrV ptns; - - XPinit(ptns, 16); - t = newtp(TPAT); - c = token(CONTIN|KEYWORD); /* no ALIAS here */ - if (c != '(') - REJECT; - do { - musthave(LWORD, 0); - XPput(ptns, yylval.cp); - } while ((c = token(0)) == '|'); - REJECT; - XPput(ptns, NULL); - t->vars = (char **) XPclose(ptns); - musthave(')', 0); - - t->left = c_list(true); - /* Note: Posix requires the ;; */ - if ((tpeek(CONTIN|KEYWORD|ALIAS)) != endtok) - musthave(BREAK, CONTIN|KEYWORD|ALIAS); - return (t); -} - -static struct op * -function_body(char *name, int ksh_func) - - /* function foo { ... } vs foo() { .. } */ -{ - char *sname, *p; - struct op *t; - int old_func_parse; - - sname = wdstrip(name); - /* Check for valid characters in name. posix and ksh93 say only - * allow [a-zA-Z_0-9] but this allows more as old pdkshs have - * allowed more (the following were never allowed: - * nul space nl tab $ ' " \ ` ( ) & | ; = < > - * C_QUOTE covers all but = and adds # [ ? *) - */ - for (p = sname; *p; p++) - if (ctype(*p, C_QUOTE) || *p == '=') - yyerror("%s: invalid function name\n", sname); - - t = newtp(TFUNCT); - t->str = sname; - t->u.ksh_func = ksh_func; - t->lineno = source->line; - - /* Note that POSIX allows only compound statements after foo(), sh and - * at&t ksh allow any command, go with the later since it shouldn't - * break anything. However, for function foo, at&t ksh only accepts - * an open-brace. - */ - if (ksh_func) { - musthave('{', CONTIN|KEYWORD|ALIAS); /* } */ - REJECT; - } - - old_func_parse = e->flags & EF_FUNC_PARSE; - e->flags |= EF_FUNC_PARSE; - if ((t->left = get_command(CONTIN)) == NULL) { - /* - * Probably something like foo() followed by eof or ;. - * This is accepted by sh and ksh88. - * To make "typeset -f foo" work reliably (so its output can - * be used as input), we pretend there is a colon here. - */ - t->left = newtp(TCOM); - t->left->args = (char **) alloc(sizeof(char *) * 2, ATEMP); - t->left->args[0] = alloc(sizeof(char) * 3, ATEMP); - t->left->args[0][0] = CHAR; - t->left->args[0][1] = ':'; - t->left->args[0][2] = EOS; - t->left->args[1] = NULL; - t->left->vars = (char **) alloc(sizeof(char *), ATEMP); - t->left->vars[0] = NULL; - t->left->lineno = 1; - } - if (!old_func_parse) - e->flags &= ~EF_FUNC_PARSE; - - return t; -} - -static char ** -wordlist(void) -{ - int c; - XPtrV args; - - XPinit(args, 16); - /* Posix does not do alias expansion here... */ - if ((c = token(CONTIN|KEYWORD|ALIAS)) != IN) { - if (c != ';') /* non-POSIX, but at&t ksh accepts a ; here */ - REJECT; - return NULL; - } - while ((c = token(0)) == LWORD) - XPput(args, yylval.cp); - if (c != '\n' && c != ';') - syntaxerr(NULL); - if (XPsize(args) == 0) { - XPfree(args); - return NULL; - } else { - XPput(args, NULL); - return (char **) XPclose(args); - } -} - -/* - * supporting functions - */ - -static struct op * -block(int type, struct op *t1, struct op *t2, char **wp) -{ - struct op *t; - - t = newtp(type); - t->left = t1; - t->right = t2; - t->vars = wp; - return (t); -} - -const struct tokeninfo { - const char *name; - short val; - short reserved; -} tokentab[] = { - /* Reserved words */ - { "if", IF, true }, - { "then", THEN, true }, - { "else", ELSE, true }, - { "elif", ELIF, true }, - { "fi", FI, true }, - { "case", CASE, true }, - { "esac", ESAC, true }, - { "for", FOR, true }, - { "select", SELECT, true }, - { "while", WHILE, true }, - { "until", UNTIL, true }, - { "do", DO, true }, - { "done", DONE, true }, - { "in", IN, true }, - { "function", FUNCTION, true }, - { "time", TIME, true }, - { "{", '{', true }, - { "}", '}', true }, - { "!", BANG, true }, - { "[[", DBRACKET, true }, - /* Lexical tokens (0[EOF], LWORD and REDIR handled specially) */ - { "&&", LOGAND, false }, - { "||", LOGOR, false }, - { ";;", BREAK, false }, - { "((", MDPAREN, false }, - { "|&", COPROC, false }, - /* and some special cases... */ - { "newline", '\n', false }, - { NULL, 0, false } -}; - -void -initkeywords(void) -{ - struct tokeninfo const *tt; - struct tbl *p; - - tinit(&keywords, APERM, 32); /* must be 2^n (currently 20 keywords) */ - for (tt = tokentab; tt->name; tt++) { - if (tt->reserved) { - p = tenter(&keywords, tt->name, hash(tt->name)); - p->flag |= DEFINED|ISSET; - p->type = CKEYWD; - p->val.i = tt->val; - } - } -} - -static void -syntaxerr(const char *what) -{ - char redir[6]; /* 2<<- is the longest redirection, I think */ - const char *s; - struct tokeninfo const *tt; - int c; - - if (!what) - what = "unexpected"; - REJECT; - c = token(0); - Again: - switch (c) { - case 0: - if (nesting.start_token) { - c = nesting.start_token; - source->errline = nesting.start_line; - what = "unmatched"; - goto Again; - } - /* don't quote the EOF */ - yyerror("syntax error: unexpected EOF\n"); - /*NOTREACHED*/ - - case LWORD: - s = snptreef(NULL, 32, "%S", yylval.cp); - break; - - case REDIR: - s = snptreef(redir, sizeof(redir), "%R", yylval.iop); - break; - - default: - for (tt = tokentab; tt->name; tt++) - if (tt->val == c) - break; - if (tt->name) - s = tt->name; - else { - if (c > 0 && c < 256) { - redir[0] = c; - redir[1] = '\0'; - } else - shf_snprintf(redir, sizeof(redir), - "?%d", c); - s = redir; - } - } - yyerror("syntax error: '%s' %s\n", s, what); -} - -static void -nesting_push(struct nesting_state *save, int tok) -{ - *save = nesting; - nesting.start_token = tok; - nesting.start_line = source->line; -} - -static void -nesting_pop(struct nesting_state *saved) -{ - nesting = *saved; -} - -static struct op * -newtp(int type) -{ - struct op *t; - - t = (struct op *) alloc(sizeof(*t), ATEMP); - t->type = type; - t->u.evalflags = 0; - t->args = t->vars = NULL; - t->ioact = NULL; - t->left = t->right = NULL; - t->str = NULL; - return (t); -} - -struct op * -compile(Source *s) -{ - nesting.start_token = 0; - nesting.start_line = 0; - herep = heres; - source = s; - yyparse(); - return outtree; -} - -/* This kludge exists to take care of sh/at&t ksh oddity in which - * the arguments of alias/export/readonly/typeset have no field - * splitting, file globbing, or (normal) tilde expansion done. - * at&t ksh seems to do something similar to this since - * $ touch a=a; typeset a=[ab]; echo "$a" - * a=[ab] - * $ x=typeset; $x a=[ab]; echo "$a" - * a=a - * $ - */ -static int -assign_command(char *s) -{ - char c = *s; - - if (Flag(FPOSIX) || !*s) - return 0; - return (c == 'a' && strcmp(s, "alias") == 0) - || (c == 'e' && strcmp(s, "export") == 0) - || (c == 'r' && strcmp(s, "readonly") == 0) - || (c == 't' && strcmp(s, "typeset") == 0); -} - -/* Check if we are in the middle of reading an alias */ -static int -inalias(struct source *s) -{ - for (; s && s->type == SALIAS; s = s->next) - if (!(s->flags & SF_ALIASEND)) - return 1; - return 0; -} - - -/* Order important - indexed by Test_meta values - * Note that ||, &&, ( and ) can't appear in as unquoted strings - * in normal shell input, so these can be interpreted unambiguously - * in the evaluation pass. - */ -static const char dbtest_or[] = { CHAR, '|', CHAR, '|', EOS }; -static const char dbtest_and[] = { CHAR, '&', CHAR, '&', EOS }; -static const char dbtest_not[] = { CHAR, '!', EOS }; -static const char dbtest_oparen[] = { CHAR, '(', EOS }; -static const char dbtest_cparen[] = { CHAR, ')', EOS }; -const char *const dbtest_tokens[] = { - dbtest_or, dbtest_and, dbtest_not, - dbtest_oparen, dbtest_cparen - }; -const char db_close[] = { CHAR, ']', CHAR, ']', EOS }; -const char db_lthan[] = { CHAR, '<', EOS }; -const char db_gthan[] = { CHAR, '>', EOS }; - -/* Test if the current token is a whatever. Accepts the current token if - * it is. Returns 0 if it is not, non-zero if it is (in the case of - * TM_UNOP and TM_BINOP, the returned value is a Test_op). - */ -static int -dbtestp_isa(Test_env *te, Test_meta meta) -{ - int c = tpeek(ARRAYVAR | (meta == TM_BINOP ? 0 : CONTIN)); - int uqword = 0; - char *save = NULL; - int ret = 0; - - /* unquoted word? */ - uqword = c == LWORD && *ident; - - if (meta == TM_OR) - ret = c == LOGOR; - else if (meta == TM_AND) - ret = c == LOGAND; - else if (meta == TM_NOT) - ret = uqword && strcmp(yylval.cp, dbtest_tokens[(int) TM_NOT]) == 0; - else if (meta == TM_OPAREN) - ret = c == '(' /*)*/; - else if (meta == TM_CPAREN) - ret = c == /*(*/ ')'; - else if (meta == TM_UNOP || meta == TM_BINOP) { - if (meta == TM_BINOP && c == REDIR - && (yylval.iop->flag == IOREAD - || yylval.iop->flag == IOWRITE)) - { - ret = 1; - save = wdcopy(yylval.iop->flag == IOREAD ? - db_lthan : db_gthan, ATEMP); - } else if (uqword && (ret = (int) test_isop(te, meta, ident))) - save = yylval.cp; - } else /* meta == TM_END */ - ret = uqword && strcmp(yylval.cp, db_close) == 0; - if (ret) { - ACCEPT; - if (meta != TM_END) { - if (!save) - save = wdcopy(dbtest_tokens[(int) meta], ATEMP); - XPput(*te->pos.av, save); - } - } - return ret; -} - -static const char * -dbtestp_getopnd(Test_env *te, Test_op op GCC_FUNC_ATTR(unused), - int do_eval GCC_FUNC_ATTR(unused)) -{ - int c = tpeek(ARRAYVAR); - - if (c != LWORD) - return NULL; - - ACCEPT; - XPput(*te->pos.av, yylval.cp); - - return null; -} - -static int -dbtestp_eval(Test_env *te GCC_FUNC_ATTR(unused), - Test_op op GCC_FUNC_ATTR(unused), - const char *opnd1 GCC_FUNC_ATTR(unused), - const char *opnd2 GCC_FUNC_ATTR(unused), - int do_eval GCC_FUNC_ATTR(unused)) -{ - return 1; -} - -static void -dbtestp_error(Test_env *te, int offset, const char *msg) -{ - te->flags |= TEF_ERROR; - - if (offset < 0) { - REJECT; - /* Kludgy to say the least... */ - symbol = LWORD; - yylval.cp = *(XPptrv(*te->pos.av) + XPsize(*te->pos.av) - + offset); - } - syntaxerr(msg); -} diff --git a/table.c b/table.c deleted file mode 100644 index a44f5fd..0000000 --- a/table.c +++ /dev/null @@ -1,231 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: table.c,v 1.5 1999/01/10 17:55:03 millert Exp $ */ - -/* - * dynamic hashed associative table for commands and variables - */ - -#include "sh.h" - -__RCSID("$MirOS$"); - -#define INIT_TBLS 8 /* initial table size (power of 2) */ - -static void texpand(struct table *tp, int nsize); -static int tnamecmp(void *p1, void *p2); - - -unsigned int -hash(const char *n) -{ - unsigned int h = 0; - - while (*n != '\0') - h = 2*h + *n++; - return h * 32821; /* scatter bits */ -} - -void -tinit(struct table *tp, Area *ap, int tsize) -{ - tp->areap = ap; - tp->tbls = NULL; - tp->size = tp->nfree = 0; - if (tsize) - texpand(tp, tsize); -} - -static void -texpand(struct table *tp, int nsize) -{ - int i; - struct tbl *tblp, **p; - struct tbl **ntblp, **otblp = tp->tbls; - int osize = tp->size; - - ntblp = (struct tbl**) alloc(sizeofN(struct tbl *, nsize), tp->areap); - for (i = 0; i < nsize; i++) - ntblp[i] = NULL; - tp->size = nsize; - tp->nfree = 8*nsize/10; /* table can get 80% full */ - tp->tbls = ntblp; - if (otblp == NULL) - return; - for (i = 0; i < osize; i++) - if ((tblp = otblp[i]) != NULL) { - if ((tblp->flag&DEFINED)) { - for (p = &ntblp[hash(tblp->name) - & (tp->size-1)]; - *p != NULL; p--) - if (p == ntblp) /* wrap */ - p += tp->size; - *p = tblp; - tp->nfree--; - } else if (!(tblp->flag & FINUSE)) { - afree((void*)tblp, tp->areap); - } - } - afree((void*)otblp, tp->areap); -} - -struct tbl * -tsearch(struct table *tp, const char *n, unsigned int h) - /* table */ - /* name to enter */ - /* hash(n) */ -{ - struct tbl **pp, *p; - - if (tp->size == 0) - return NULL; - - /* search for name in hashed table */ - for (pp = &tp->tbls[h & (tp->size-1)]; (p = *pp) != NULL; pp--) { - if (*p->name == *n && strcmp(p->name, n) == 0 - && (p->flag&DEFINED)) - return p; - if (pp == tp->tbls) /* wrap */ - pp += tp->size; - } - - return NULL; -} - -struct tbl * -tenter(struct table *tp, const char *n, unsigned int h) - /* table */ - /* name to enter */ - /* hash(n) */ -{ - struct tbl **pp, *p; - int len; - - if (tp->size == 0) - texpand(tp, INIT_TBLS); - Search: - /* search for name in hashed table */ - for (pp = &tp->tbls[h & (tp->size-1)]; (p = *pp) != NULL; pp--) { - if (*p->name == *n && strcmp(p->name, n) == 0) - return p; /* found */ - if (pp == tp->tbls) /* wrap */ - pp += tp->size; - } - - if (tp->nfree <= 0) { /* too full */ - texpand(tp, 2*tp->size); - goto Search; - } - - /* create new tbl entry */ - len = strlen(n) + 1; - p = (struct tbl *) alloc(offsetof(struct tbl, name[0]) + len, - tp->areap); - p->flag = 0; - p->type = 0; - p->areap = tp->areap; - p->u2.field = 0; - p->u.array = NULL; - memcpy(p->name, n, len); - - /* enter in tp->tbls */ - tp->nfree--; - *pp = p; - return p; -} - -void -tdelete(struct tbl *p) -{ - p->flag = 0; -} - -void -twalk(struct tstate *ts, struct table *tp) -{ - ts->left = tp->size; - ts->next = tp->tbls; -} - -struct tbl * -tnext(struct tstate *ts) -{ - while (--ts->left >= 0) { - struct tbl *p = *ts->next++; - if (p != NULL && (p->flag&DEFINED)) - return p; - } - return NULL; -} - -static int -tnamecmp(void *p1, void *p2) -{ - return strcmp(((struct tbl *)p1)->name, ((struct tbl *)p2)->name); -} - -struct tbl ** -tsort(struct table *tp) -{ - int i; - struct tbl **p, **sp, **dp; - - p = (struct tbl **)alloc(sizeofN(struct tbl *, tp->size+1), ATEMP); - sp = tp->tbls; /* source */ - dp = p; /* dest */ - for (i = 0; i < tp->size; i++) - if ((*dp = *sp++) != NULL && (((*dp)->flag&DEFINED) || - ((*dp)->flag&ARRAY))) - dp++; - i = dp - p; - qsortp((void**)p, (size_t)i, tnamecmp); - p[i] = NULL; - return p; -} - -#ifdef PERF_DEBUG /* performance debugging */ - -void tprintinfo(struct table *tp); - -void -tprintinfo(tp) - struct table *tp; -{ - struct tbl *te; - char *n; - unsigned int h; - int ncmp; - int totncmp = 0, maxncmp = 0; - int nentries = 0; - struct tstate ts; - - shellf("table size %d, nfree %d\n", tp->size, tp->nfree); - shellf(" Ncmp name\n"); - twalk(&ts, tp); - while ((te = tnext(&ts))) { - struct tbl **pp, *p; - - h = hash(n = te->name); - ncmp = 0; - - /* taken from tsearch() and added counter */ - for (pp = &tp->tbls[h & (tp->size-1)]; (p = *pp); pp--) { - ncmp++; - if (*p->name == *n && strcmp(p->name, n) == 0 - && (p->flag&DEFINED)) - break; /* return p; */ - if (pp == tp->tbls) /* wrap */ - pp += tp->size; - } - shellf(" %4d %s\n", ncmp, n); - totncmp += ncmp; - nentries++; - if (ncmp > maxncmp) - maxncmp = ncmp; - } - if (nentries) - shellf(" %d entries, worst ncmp %d, avg ncmp %d.%02d\n", - nentries, maxncmp, - totncmp / nentries, - (totncmp % nentries) * 100 / nentries); -} -#endif /* PERF_DEBUG */ diff --git a/table.h b/table.h deleted file mode 100644 index 3ed37ea..0000000 --- a/table.h +++ /dev/null @@ -1,182 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: table.h,v 1.5 1999/06/15 01:18:36 millert Exp $ */ -/* $From: table.h,v 1.3 1994/05/31 13:34:34 michael Exp $ */ - -#ifndef TABLE_H -#define TABLE_H - -/* - * generic hashed associative table for commands and variables. - */ - -struct table { - Area *areap; /* area to allocate entries */ - short size, nfree; /* hash size (always 2^^n), free entries */ - struct tbl **tbls; /* hashed table items */ -}; - -struct tbl { /* table item */ - Tflag flag; /* flags */ - int type; /* command type (see below), base (if INTEGER), - * or offset from val.s of value (if EXPORT) */ - Area *areap; /* area to allocate from */ - union { - char *s; /* string */ - long i; /* integer */ - int (*f)(char **); /* int function */ - struct op *t; /* "function" tree */ - } val; /* value */ - int index; /* index for an array */ - union { - int field; /* field with for -L/-R/-Z */ - int errno_; /* CEXEC/CTALIAS */ - } u2; - union { - struct tbl *array; /* array values */ - char *fpath; /* temporary path to undef function */ - } u; - char name[4]; /* name -- variable length */ -}; - -/* common flag bits */ -#define ALLOC BIT(0) /* val.s has been allocated */ -#define DEFINED BIT(1) /* is defined in block */ -#define ISSET BIT(2) /* has value, vp->val.[si] */ -#define EXPORT BIT(3) /* exported variable/function */ -#define TRACE BIT(4) /* var: user flagged, func: execution tracing */ -/* (start non-common flags at 8) */ -/* flag bits used for variables */ -#define SPECIAL BIT(8) /* PATH, IFS, SECONDS, etc */ -#define INTEGER BIT(9) /* val.i contains integer value */ -#define RDONLY BIT(10) /* read-only variable */ -#define LOCAL BIT(11) /* for local typeset() */ -#define ARRAY BIT(13) /* array */ -#define LJUST BIT(14) /* left justify */ -#define RJUST BIT(15) /* right justify */ -#define ZEROFIL BIT(16) /* 0 filled if RJUSTIFY, strip 0s if LJUSTIFY */ -#define LCASEV BIT(17) /* convert to lower case */ -#define UCASEV_AL BIT(18)/* convert to upper case / autoload function */ -#define INT_U BIT(19) /* unsigned integer */ -#define INT_L BIT(20) /* long integer (no-op) */ -#define IMPORT BIT(21) /* flag to typeset(): no arrays, must have = */ -#define LOCAL_COPY BIT(22) /* with LOCAL - copy attrs from existing var */ -#define EXPRINEVAL BIT(23) /* contents currently being evaluated */ -#define EXPRLVALUE BIT(24) /* useable as lvalue (temp flag) */ -/* flag bits used for taliases/builtins/aliases/keywords/functions */ -#define KEEPASN BIT(8) /* keep command assignments (eg, var=x cmd) */ -#define FINUSE BIT(9) /* function being executed */ -#define FDELETE BIT(10) /* function deleted while it was executing */ -#define FKSH BIT(11) /* function defined with function x (vs x()) */ -#define SPEC_BI BIT(12) /* a POSIX special builtin */ -#define REG_BI BIT(13) /* a POSIX regular builtin */ -/* Attributes that can be set by the user (used to decide if an unset param - * should be repoted by set/typeset). Does not include ARRAY or LOCAL. - */ -#define USERATTRIB (EXPORT|INTEGER|RDONLY|LJUST|RJUST|ZEROFIL\ - |LCASEV|UCASEV_AL|INT_U|INT_L) - -/* command types */ -#define CNONE 0 /* undefined */ -#define CSHELL 1 /* built-in */ -#define CFUNC 2 /* function */ -#define CEXEC 4 /* executable command */ -#define CALIAS 5 /* alias */ -#define CKEYWD 6 /* keyword */ -#define CTALIAS 7 /* tracked alias */ - -/* Flags for findcom()/comexec() */ -#define FC_SPECBI BIT(0) /* special builtin */ -#define FC_FUNC BIT(1) /* function builtin */ -#define FC_REGBI BIT(2) /* regular builtin */ -#define FC_UNREGBI BIT(3) /* un-regular builtin (!special,!regular) */ -#define FC_BI (FC_SPECBI|FC_REGBI|FC_UNREGBI) -#define FC_PATH BIT(4) /* do path search */ -#define FC_DEFPATH BIT(5) /* use default path in path search */ - - -#define AF_ARGV_ALLOC 0x1 /* argv[] array allocated */ -#define AF_ARGS_ALLOCED 0x2 /* argument strings allocated */ -#define AI_ARGV(a, i) ((i) == 0 ? (a).argv[0] : (a).argv[(i) - (a).skip]) -#define AI_ARGC(a) ((a).argc_ - (a).skip) - -/* Argument info. Used for $#, $* for shell, functions, includes, etc. */ -struct arg_info { - int flags; /* AF_* */ - char **argv; - int argc_; - int skip; /* first arg is argv[0], second is argv[1 + skip] */ -}; - -/* - * activation record for function blocks - */ -struct block { - Area area; /* area to allocate things */ - /*struct arg_info argi;*/ - char **argv; - int argc; - int flags; /* see BF_* */ - struct table vars; /* local variables */ - struct table funs; /* local functions */ - Getopt getopts_state; - char * error; /* error handler */ - char * exit; /* exit handler */ - struct block *next; /* enclosing block */ -}; - -/* Values for struct block.flags */ -#define BF_DOGETOPTS BIT(0) /* save/restore getopts state */ - -/* - * Used by twalk() and tnext() routines. - */ -struct tstate { - int left; - struct tbl **next; -}; - - -EXTERN struct table taliases; /* tracked aliases */ -EXTERN struct table builtins; /* built-in commands */ -EXTERN struct table aliases; /* aliases */ -EXTERN struct table keywords; /* keywords */ -EXTERN struct table homedirs; /* homedir() cache */ - -struct builtin { - const char *name; - int (*func)(char **); -}; - -/* these really are externs! Look in table.c for them */ -extern const struct builtin shbuiltins [], kshbuiltins []; - -/* var spec values */ -#define V_NONE 0 -#define V_PATH 1 -#define V_IFS 2 -#define V_SECONDS 3 -#define V_OPTIND 4 -#define V_RANDOM 8 -#define V_HISTSIZE 9 -#define V_HISTFILE 10 -#define V_VISUAL 11 -#define V_EDITOR 12 -#define V_COLUMNS 13 -#define V_POSIXLY_CORRECT 14 -#define V_TMOUT 15 -#define V_TMPDIR 16 -#define V_LINENO 17 -#define V_PGRP 18 - -/* values for set_prompt() */ -#define PS1 0 /* command */ -#define PS2 1 /* command continuation */ - -EXTERN char *path; /* copy of either PATH or def_path */ -EXTERN const char *def_path; /* path to use if PATH not set */ -EXTERN char *tmpdir; /* TMPDIR value */ -EXTERN const char *prompt; -EXTERN int cur_prompt; /* PS1 or PS2 */ -EXTERN int current_lineno; /* LINENO value */ - -#endif /* ndef TABLE_H */ diff --git a/tests/alias.t b/tests/alias.t deleted file mode 100644 index ce434eb..0000000 --- a/tests/alias.t +++ /dev/null @@ -1,111 +0,0 @@ -name: alias-1 -description: - Check that recursion is detected/avoided in aliases. -stdin: - alias fooBar=fooBar - fooBar - exit 0 -expected-stderr-pattern: - /fooBar.*not found.*/ ---- - -name: alias-2 -description: - Check that recursion is detected/avoided in aliases. -stdin: - alias fooBar=barFoo - alias barFoo=fooBar - fooBar - barFoo - exit 0 -expected-stderr-pattern: - /fooBar.*not found.*\n.*barFoo.*not found/ ---- - -name: alias-3 -description: - Check that recursion is detected/avoided in aliases. -stdin: - alias Echo='echo ' - alias fooBar=barFoo - alias barFoo=fooBar - Echo fooBar - unalias barFoo - Echo fooBar -expected-stdout: - fooBar - barFoo ---- - -name: alias-4 -description: - Check that alias expansion isn't done on keywords (in keyword - postitions). -stdin: - alias Echo='echo ' - alias while=While - while false; do echo hi ; done - Echo while -expected-stdout: - While ---- - -name: alias-5 -description: - Check that alias expansion done after alias with trailing space. -stdin: - alias Echo='echo ' - alias foo='bar stuff ' - alias bar='Bar1 Bar2 ' - alias stuff='Stuff' - alias blah='Blah' - Echo foo blah -expected-stdout: - Bar1 Bar2 Stuff Blah ---- - -name: alias-6 -description: - Check that alias expansion done after alias with trailing space. -stdin: - alias Echo='echo ' - alias foo='bar bar' - alias bar='Bar ' - alias blah=Blah - Echo foo blah -expected-stdout: - Bar Bar Blah ---- - -name: alias-7 -description: - Check that alias expansion done after alias with trailing space - after a keyword. -stdin: - alias X='case ' - alias Y=Z - X Y in 'Y') echo is y ;; Z) echo is z ; esac -expected-stdout: - is z ---- - -name: alias-8 -description: - Check that newlines in an alias don't cause the command to be lost. -stdin: - alias foo=' - - - echo hi - - - - echo there - - - ' - foo -expected-stdout: - hi - there ---- diff --git a/tests/arith.t b/tests/arith.t deleted file mode 100644 index 71679e9..0000000 --- a/tests/arith.t +++ /dev/null @@ -1,78 +0,0 @@ -name: arith-lazy-1 -description: - Check that only one side of ternary operator is evaluated -stdin: - x=i+=2 - y=j+=2 - typeset -i i=1 j=1 - echo $((1 ? 20 : (x+=2))) - echo $i,$x - echo $((0 ? (y+=2) : 30)) - echo $j,$y -expected-stdout: - 20 - 1,i+=2 - 30 - 1,j+=2 ---- - -name: arith-lazy-2 -description: - Check that assignments not done on non-evaluated side of ternary - operator -stdin: - x=i+=2 - y=j+=2 - typeset -i i=1 j=1 - echo $((1 ? 20 : (x+=2))) - echo $i,$x - echo $((0 ? (y+=2) : 30)) - echo $i,$y -expected-stdout: - 20 - 1,i+=2 - 30 - 1,j+=2 ---- - -name: arith-ternary-prec-1 -description: - Check precidance of ternary operator vs assignment -stdin: - typeset -i x=2 - y=$((1 ? 20 : x+=2)) -expected-exit: e != 0 -expected-stderr-pattern: - /.*:.*1 \? 20 : x\+=2.*lvalue.*\n$/ ---- - -name: arith-ternary-prec-2 -description: - Check precidance of ternary operator vs assignment -stdin: - typeset -i x=2 - echo $((0 ? x+=2 : 20)) -expected-stdout: - 20 ---- - -name: arith-div-assoc-1 -description: - Check associativity of division operator -stdin: - echo $((20 / 2 / 2)) -expected-stdout: - 5 ---- - -name: arith-assop-assoc-1 -description: - Check associativity of assignment-operator operator -stdin: - typeset -i i=1 j=2 k=3 - echo $((i += j += k)) - echo $i,$j,$k -expected-stdout: - 6 - 6,5,3 ---- diff --git a/tests/bksl-nl.t b/tests/bksl-nl.t deleted file mode 100644 index 75d9f1a..0000000 --- a/tests/bksl-nl.t +++ /dev/null @@ -1,340 +0,0 @@ -# $MirOS$ -# $OpenBSD: bksl-nl.t,v 1.2 2001/01/28 23:04:56 niklas Exp $ -# -# These tests deal with how \newline is handled in various situations. The -# first group of tests are places where it shouldn't be collapsed, the next -# group of tests are places where it should be collapsed. -# -name: bksl-nl-ign-1 -description: - Check that \newline is not collasped after # -stdin: - echo hi #there \ - echo folks -expected-stdout: - hi - folks ---- - -name: bksl-nl-ign-2 -description: - Check that \newline is not collasped inside single quotes -stdin: - echo 'hi \ - there' - echo folks -expected-stdout: - hi \ - there - folks ---- - -name: bksl-nl-ign-3 -description: - Check that \newline is not collasped inside single quotes -stdin: - cat << \EOF - hi \ - there - EOF -expected-stdout: - hi \ - there ---- - -name: blsk-nl-ign-4 -description: - Check interaction of aliases, single quotes and here-documents - with backslash-newline - (don't know what posix has to say about this) -stdin: - a=2 - alias x='echo hi - cat << "EOF" - foo\ - bar - some' - x - more\ - stuff$a - EOF -expected-stdout: - hi - foo\ - bar - some - more\ - stuff$a ---- - -name: blsk-nl-ign-5 -description: - Check what happens with backslash at end of input - (the old bourne shell trashes them; so do we) -stdin: ! - echo `echo foo\\`bar - echo hi\ -expected-stdout: - foobar - hi ---- - - -# -# Places \newline should be collapsed -# -name: bksl-nl-1 -description: - Check that \newline is collasped before, in the middle of, and - after words -stdin: - \ - echo hi\ - There, \ - folks -expected-stdout: - hiThere, folks ---- - -name: bksl-nl-2 -description: - Check that \newline is collasped in $ sequences - (ksh93 fails this) -stdin: - a=12 - ab=19 - echo $\ - a - echo $a\ - b - echo $\ - {a} - echo ${a\ - b} - echo ${ab\ - } -expected-stdout: - 12 - 19 - 12 - 19 - 19 ---- - -name: bksl-nl-3 -description: - Check that \newline is collasped in $(..) and `...` sequences - (ksh93 fails this) -stdin: - echo $\ - (echo foobar1) - echo $(\ - echo foobar2) - echo $(echo foo\ - bar3) - echo $(echo foobar4\ - ) - echo ` - echo stuff1` - echo `echo st\ - uff2` -expected-stdout: - foobar1 - foobar2 - foobar3 - foobar4 - stuff1 - stuff2 ---- - -name: bksl-nl-4 -description: - Check that \newline is collasped in $((..)) sequences - (ksh93 fails this) -stdin: - echo $\ - ((1+2)) - echo $(\ - (1+2+3)) - echo $((\ - 1+2+3+4)) - echo $((1+\ - 2+3+4+5)) - echo $((1+2+3+4+5+6)\ - ) -expected-stdout: - 3 - 6 - 10 - 15 - 21 ---- - -name: bksl-nl-5 -description: - Check that \newline is collasped in double quoted strings -stdin: - echo "\ - hi" - echo "foo\ - bar" - echo "folks\ - " -expected-stdout: - hi - foobar - folks ---- - -name: bksl-nl-6 -description: - Check that \newline is collasped in here document delimiters - (ksh93 fails second part of this) -stdin: - a=12 - cat << EO\ - F - a=$a - foo\ - bar - EOF - cat << E_O_F - foo - E_O_\ - F - echo done -expected-stdout: - a=12 - foobar - foo - done ---- - -name: bksl-nl-7 -description: - Check that \newline is collasped in double-quoted here-document - delimiter. -stdin: - a=12 - cat << "EO\ - F" - a=$a - foo\ - bar - EOF - echo done -expected-stdout: - a=$a - foo\ - bar - done ---- - -name: bksl-nl-8 -description: - Check that \newline is collasped in various 2+ character tokens - delimiter. - (ksh93 fails this) -stdin: - echo hi &\ - & echo there - echo foo |\ - | echo bar - cat <\ - < EOF - stuff - EOF - cat <\ - <\ - - EOF - more stuff - EOF - cat <<\ - EOF - abcdef - EOF - echo hi >\ - > /dev/null - echo $? - i=1 - case $i in - (\ - x|\ - 1\ - ) echo hi;\ - ; - (*) echo oops - esac -expected-stdout: - hi - there - foo - stuff - more stuff - abcdef - 0 - hi ---- - -name: blsk-nl-9 -description: - Check that \ at the end of an alias is collapsed when followed - by a newline - (don't know what posix has to say about this) -stdin: - alias x='echo hi\' - x - echo there -expected-stdout: - hiecho there ---- - -name: blsk-nl-10 -description: - Check that \newline in a keyword is collapsed -stdin: - i\ - f true; then\ - echo pass; el\ - se echo fail; fi -expected-stdout: - pass ---- - -# -# Places \newline should be collapsed (ksh extensions) -# - -name: blsk-nl-ksh-1 -description: - Check that \newline is collapsed in extended globbing - (ksh93 fails this) -stdin: - xxx=foo - case $xxx in - (f*\ - (\ - o\ - )\ - ) echo ok ;; - *) echo bad - esac -expected-stdout: - ok ---- - -name: blsk-nl-ksh-2 -description: - Check that \newline is collapsed in ((...)) expressions - (ksh93 fails this) -stdin: - i=1 - (\ - (\ - i=i+2\ - )\ - ) - echo $i -expected-stdout: - 3 ---- diff --git a/tests/brkcont.t b/tests/brkcont.t deleted file mode 100644 index f6abbb3..0000000 --- a/tests/brkcont.t +++ /dev/null @@ -1,193 +0,0 @@ -name: break-1 -description: - See if break breaks out of loops -stdin: - for i in a b c; do echo $i; break; echo bad-$i; done - echo end-1 - for i in a b c; do echo $i; break 1; echo bad-$i; done - echo end-2 - for i in a b c; do - for j in x y z; do - echo $i:$j - break - echo bad-$i - done - echo end-$i - done - echo end-3 -expected-stdout: - a - end-1 - a - end-2 - a:x - end-a - b:x - end-b - c:x - end-c - end-3 ---- - -name: break-2 -description: - See if break breaks out of nested loops -stdin: - for i in a b c; do - for j in x y z; do - echo $i:$j - break 2 - echo bad-$i - done - echo end-$i - done - echo end -expected-stdout: - a:x - end ---- - - -name: break-3 -description: - What if break used outside of any loops - (ksh88,ksh93 don't print error messages here) -stdin: - break -expected-stderr-pattern: - /.*break.*/ ---- - - -name: break-4 -description: - What if break N used when only N-1 loops - (ksh88,ksh93 don't print error messages here) -stdin: - for i in a b c; do echo $i; break 2; echo bad-$i; done - echo end -expected-stdout: - a - end -expected-stderr-pattern: - /.*break.*/ ---- - - -name: break-5 -description: - Error if break argument isn't a number -stdin: - for i in a b c; do echo $i; break abc; echo more-$i; done - echo end -expected-stdout: - a -expected-exit: e != 0 -expected-stderr-pattern: - /.*break.*/ ---- - - -name: continue-1 -description: - See if continue continues loops -stdin: - for i in a b c; do echo $i; continue; echo bad-$i ; done - echo end-1 - for i in a b c; do echo $i; continue 1; echo bad-$i; done - echo end-2 - for i in a b c; do - for j in x y z; do - echo $i:$j - continue - echo bad-$i-$j - done - echo end-$i - done - echo end-3 -expected-stdout: - a - b - c - end-1 - a - b - c - end-2 - a:x - a:y - a:z - end-a - b:x - b:y - b:z - end-b - c:x - c:y - c:z - end-c - end-3 ---- - - -name: continue-2 -description: - See if continue breaks out of nested loops -stdin: - for i in a b c; do - for j in x y z; do - echo $i:$j - continue 2 - echo bad-$i-$j - done - echo end-$i - done - echo end -expected-stdout: - a:x - b:x - c:x - end ---- - - -name: continue-3 -description: - What if continue used outside of any loops - (ksh88,ksh93 don't print error messages here) -stdin: - continue -expected-stderr-pattern: - /.*continue.*/ ---- - - -name: continue-4 -description: - What if continue N used when only N-1 loops - (ksh88,ksh93 don't print error messages here) -stdin: - for i in a b c; do echo $i; continue 2; echo bad-$i; done - echo end -expected-stdout: - a - b - c - end -expected-stderr-pattern: - /.*continue.*/ ---- - - -name: continue-5 -description: - Error if continue argument isn't a number -stdin: - for i in a b c; do echo $i; continue abc; echo more-$i; done - echo end -expected-stdout: - a -expected-exit: e != 0 -expected-stderr-pattern: - /.*continue.*/ ---- diff --git a/tests/cdhist.t b/tests/cdhist.t deleted file mode 100644 index 5c5d4f2..0000000 --- a/tests/cdhist.t +++ /dev/null @@ -1,162 +0,0 @@ -name: cd-history -description: - Test someone's CD history package (uses arrays) -# Fails on OS/2, since directory names are prepended with drive letter. -category: !os:os2 -stdin: - # go to known place before doing anything - cd / - - alias cd=_cd - function _cd - { - typeset -i cdlen i - typeset t - - if [ $# -eq 0 ] - then - set -- $HOME - fi - - if [ "$CDHISTFILE" -a -r "$CDHISTFILE" ] # if directory history exists - then - typeset CDHIST - i=-1 - while read -r t # read directory history file - do - CDHIST[i=i+1]=$t - done <$CDHISTFILE - fi - - if [ "${CDHIST[0]}" != "$PWD" -a "$PWD" != "" ] - then - _cdins # insert $PWD into cd history - fi - - cdlen=${#CDHIST[*]} # number of elements in history - - case "$@" in - -) # cd to new dir - if [ "$OLDPWD" = "" ] && ((cdlen>1)) - then - 'print' ${CDHIST[1]} - 'cd' ${CDHIST[1]} - _pwd - else - 'cd' $@ - _pwd - fi - ;; - -l) # print directory list - typeset -R3 num - ((i=cdlen)) - while (((i=i-1)>=0)) - do - num=$i - 'print' "$num ${CDHIST[i]}" - done - return - ;; - -[0-9]|-[0-9][0-9]) # cd to dir in list - if (((i=${1#-})=cdlen)) - then - 'cd' $@ - _pwd - fi - ;; - *) # cd to new dir - 'cd' $@ - _pwd - ;; - esac - - _cdins # insert $PWD into cd history - - if [ "$CDHISTFILE" ] - then - cdlen=${#CDHIST[*]} # number of elements in history - - i=0 - while ((i$CDHISTFILE - fi - } - - function _cdins # insert $PWD into cd history - { # meant to be called only by _cd - typeset -i i - - ((i=0)) - while ((i<${#CDHIST[*]})) # see if dir is already in list - do - if [ "${CDHIST[$i]}" = "$PWD" ] - then - break - fi - ((i=i+1)) - done - - if ((i>22)) # limit max size of list - then - i=22 - fi - - while (((i=i-1)>=0)) # bump old dirs in list - do - CDHIST[i+1]=${CDHIST[i]} - done - - CDHIST[0]=$PWD # insert new directory in list - } - - - function _pwd - { - if [ -n "$ECD" ] - then - pwd 1>&6 - fi - } - # Start of test - cd /tmp - cd /bin - cd /etc - cd - - cd -2 - cd -l -expected-stdout: - /bin - /tmp - 3 / - 2 /etc - 1 /bin - 0 /tmp ---- diff --git a/tests/eglob.t b/tests/eglob.t deleted file mode 100644 index 9301e48..0000000 --- a/tests/eglob.t +++ /dev/null @@ -1,144 +0,0 @@ -name: eglob-bad-1 -description: - Check that globbing isn't done when glob has syntax error -file-setup: file 644 "abcx" -file-setup: file 644 "abcz" -file-setup: file 644 "bbc" -stdin: - echo !([*)* - echo +(a|b[)* -expected-stdout: - !([*)* - +(a|b[)* ---- - -name: eglob-bad-2 -description: - Check that globbing isn't done when glob has syntax error - (at&t ksh fails this test) -file-setup: file 644 "abcx" -file-setup: file 644 "abcz" -file-setup: file 644 "bbc" -stdin: - echo [a*(]*)z -expected-stdout: - [a*(]*)z ---- - -name: eglob-infinite-plus -description: - Check that shell doesn't go into infinite loop expanding +(...) - expressions. -file-setup: file 644 "abc" -time-limit: 3 -stdin: - echo +()c - echo +()x - echo +(*)c - echo +(*)x -expected-stdout: - +()c - +()x - abc - +(*)x ---- - -name: eglob-subst-1 -description: - Check that eglobbing isn't done on substitution results -file-setup: file 644 "abc" -stdin: - x='@(*)' - echo $x -expected-stdout: - @(*) ---- - -name: eglob-nomatch-1 -description: - Check that the pattern doesn't match -stdin: - echo 1: no-file+(a|b)stuff - echo 2: no-file+(a*(c)|b)stuff - echo 3: no-file+((((c)))|b)stuff -expected-stdout: - 1: no-file+(a|b)stuff - 2: no-file+(a*(c)|b)stuff - 3: no-file+((((c)))|b)stuff ---- - -name: eglob-match-1 -description: - Check that the pattern matches correctly -file-setup: file 644 "abd" -file-setup: file 644 "acd" -file-setup: file 644 "abac" -stdin: - echo 1: a+(b|c)d - echo 2: a!(@(b|B))d - echo 3: *(a(b|c)) # (...|...) can be used within X(..) - echo 4: a[b*(foo|bar)]d # patterns not special inside [...] -expected-stdout: - 1: abd acd - 2: acd - 3: abac - 4: abd ---- - -name: eglob-case-1 -description: - Simple negation tests -stdin: - case foo in !(foo|bar)) echo yes;; *) echo no;; esac - case bar in !(foo|bar)) echo yes;; *) echo no;; esac -expected-stdout: - no - no ---- - -name: eglob-case-2 -description: - Simple kleene tests -stdin: - case foo in *(a|b[)) echo yes;; *) echo no;; esac - case foo in *(a|b[)|f*) echo yes;; *) echo no;; esac - case '*(a|b[)' in *(a|b[)) echo yes;; *) echo no;; esac -expected-stdout: - no - yes - yes ---- - -name: eglob-trim-1 -description: - Eglobing in trim expressions... - (at&t ksh fails this - docs say # matches shortest string, ## matches - longest...) -stdin: - x=abcdef - echo 1: ${x#a|abc} - echo 2: ${x##a|abc} - echo 3: ${x%def|f} - echo 4: ${x%%f|def} -expected-stdout: - 1: bcdef - 2: def - 3: abcde - 4: abc ---- - -name: eglob-trim-2 -description: - Check eglobing works in trims... -stdin: - x=abcdef - echo 1: ${x#*(a|b)cd} - echo 2: "${x#*(a|b)cd}" - echo 3: ${x#"*(a|b)cd"} - echo 4: ${x#a(b|c)} -expected-stdout: - 1: ef - 2: ef - 3: abcdef - 4: cdef ---- diff --git a/tests/glob.t b/tests/glob.t deleted file mode 100644 index 6e6a391..0000000 --- a/tests/glob.t +++ /dev/null @@ -1,98 +0,0 @@ -name: glob-bad-1 -description: - Check that globbing isn't done when glob has syntax error -file-setup: dir 755 "[x" -file-setup: file 644 "[x/foo" -stdin: - echo [* - echo *[x - echo [x/* -expected-stdout: - [* - *[x - [x/foo ---- - -name: glob-bad-2 -description: - Check that symbolic links aren't stat()'d -category: !os:os2 -file-setup: dir 755 "dir" -file-setup: symlink 644 "dir/abc" - non-existent-file -stdin: - echo d*/* - echo d*/abc -expected-stdout: - dir/abc - dir/abc ---- - -name: glob-range-1 -description: - Test range matching -file-setup: file 644 ".bc" -file-setup: file 644 "abc" -file-setup: file 644 "bbc" -file-setup: file 644 "cbc" -file-setup: file 644 "-bc" -stdin: - echo [ab-]* - echo [-ab]* - echo [!-ab]* - echo [!ab]* - echo []ab]* -expected-stdout: - -bc abc bbc - -bc abc bbc - cbc - -bc cbc - abc bbc ---- - -name: glob-range-2 -description: - Test range matching - (at&t ksh fails this; POSIX says invalid) -file-setup: file 644 "abc" -stdin: - echo [a--]* -expected-stdout: - [a--]* ---- - -name: glob-range-3 -description: - Check that globbing matches the right things... -file-setup: file 644 "aÂc" -stdin: - echo a[Á-Ú]* -expected-stdout: - aÂc ---- - -name: glob-range-4 -description: - Results unspecified according to POSIX -file-setup: file 644 ".bc" -stdin: - echo [a.]* -expected-stdout: - [a.]* ---- - -name: glob-range-5 -description: - Results unspecified according to POSIX - (at&t ksh treats this like [a-cc-e]*) -file-setup: file 644 "abc" -file-setup: file 644 "bbc" -file-setup: file 644 "cbc" -file-setup: file 644 "dbc" -file-setup: file 644 "ebc" -file-setup: file 644 "-bc" -stdin: - echo [a-c-e]* -expected-stdout: - -bc abc bbc cbc ebc ---- diff --git a/tests/heredoc.t b/tests/heredoc.t deleted file mode 100644 index 2537e0f..0000000 --- a/tests/heredoc.t +++ /dev/null @@ -1,330 +0,0 @@ -name: heredoc-1 -description: - Check ordering/content of redundent here documents. -stdin: - cat << EOF1 << EOF2 - hi - EOF1 - there - EOF2 -expected-stdout: - there ---- - -name: heredoc-2 -description: - Check quoted here-doc is protected. -stdin: - a=foo - cat << 'EOF' - hi\ - there$a - stuff - EO\ - F - EOF -expected-stdout: - hi\ - there$a - stuff - EO\ - F ---- - -name: heredoc-3 -description: - Check that newline isn't needed after heredoc-delimiter marker. -stdin: ! - cat << EOF - hi - there - EOF -expected-stdout: - hi - there ---- - -name: heredoc-4 -description: - Check that an error occurs if the heredoc-delimiter is missing. -stdin: ! - cat << EOF - hi - there -expected-exit: e > 0 -expected-stderr-pattern: /.*/ ---- - -name: heredoc-5 -description: - Check that backslash quotes a $, ` and \ and kills a \newline -stdin: - a=BAD - b=ok - cat << EOF - h\${a}i - h\\${b}i - th\`echo not-run\`ere - th\\`echo is-run`ere - fol\\ks - more\\ - last \ - line - EOF -expected-stdout: - h${a}i - h\oki - th`echo not-run`ere - th\is-runere - fol\ks - more\ - last line ---- - -name: heredoc-6 -description: - Check that \newline in initial here-delim word doesn't imply - a quoted here-doc. -stdin: - a=i - cat << EO\ - F - h$a - there - EOF -expected-stdout: - hi - there ---- - -name: heredoc-7 -description: - Check that double quoted $ expressions in here delimiters are - not expanded and match the delimiter. - POSIX says only quote removal is applied to the delimiter. -stdin: - a=b - cat << "E$a" - hi - h$a - hb - E$a - echo done -expected-stdout: - hi - h$a - hb - done ---- - -name: heredoc-8 -description: - Check that double quoted escaped $ expressions in here - delimiters are not expanded and match the delimiter. - POSIX says only quote removal is applied to the delimiter - (\ counts as a quote). -stdin: - a=b - cat << "E\$a" - hi - h$a - h\$a - hb - h\b - E$a - echo done -expected-stdout: - hi - h$a - h\$a - hb - h\b - done ---- - -name: heredoc-tmpfile-1 -description: - Check that heredoc temp files aren't removed too soon or too late. - Heredoc in simple command. -stdin: - TMPDIR=$PWD - eval ' - cat <<- EOF - hi - EOF - for i in a b ; do - cat <<- EOF - more - EOF - done - ' & - sleep 1 - echo Left overs: * -expected-stdout: - hi - more - more - Left overs: * ---- - -name: heredoc-tmpfile-2 -description: - Check that heredoc temp files aren't removed too soon or too late. - Heredoc in function, multiple calls to function. -stdin: - TMPDIR=$PWD - eval ' - foo() { - cat <<- EOF - hi - EOF - } - foo - foo - ' & - sleep 1 - echo Left overs: * -expected-stdout: - hi - hi - Left overs: * ---- - -name: heredoc-tmpfile-3 -description: - Check that heredoc temp files aren't removed too soon or too late. - Heredoc in function in loop, multiple calls to function. -stdin: - TMPDIR=$PWD - eval ' - foo() { - cat <<- EOF - hi - EOF - } - for i in a b; do - foo - foo() { - cat <<- EOF - folks $i - EOF - } - done - foo - ' & - sleep 1 - echo Left overs: * -expected-stdout: - hi - folks b - folks b - Left overs: * ---- - -name: heredoc-tmpfile-4 -description: - Check that heredoc temp files aren't removed too soon or too late. - Backgrounded simple command with here doc -stdin: - TMPDIR=$PWD - eval ' - cat <<- EOF & - hi - EOF - ' & - sleep 1 - echo Left overs: * -expected-stdout: - hi - Left overs: * ---- - -name: heredoc-tmpfile-5 -description: - Check that heredoc temp files aren't removed too soon or too late. - Backgrounded subshell command with here doc -stdin: - TMPDIR=$PWD - eval ' - ( - sleep 1 # so parent exits - echo A - cat <<- EOF - hi - EOF - echo B - ) & - ' & - sleep 2 - echo Left overs: * -expected-stdout: - A - hi - B - Left overs: * ---- - -name: heredoc-tmpfile-6 -description: - Check that heredoc temp files aren't removed too soon or too late. - Heredoc in pipeline. -stdin: - TMPDIR=$PWD - eval ' - cat <<- EOF | sed "s/hi/HI/" - hi - EOF - ' & - sleep 1 - echo Left overs: * -expected-stdout: - HI - Left overs: * ---- - -name: heredoc-tmpfile-7 -description: - Check that heredoc temp files aren't removed too soon or too late. - Heredoc in backgrounded pipeline. -stdin: - TMPDIR=$PWD - eval ' - cat <<- EOF | sed 's/hi/HI/' & - hi - EOF - ' & - sleep 1 - echo Left overs: * -expected-stdout: - HI - Left overs: * ---- - -name: heredoc-tmpfile-8 -description: - Check that heredoc temp files aren't removed too soon or too late. - Heredoc in function, backgrounded call to function. -stdin: - TMPDIR=$PWD - # Background eval so main shell doesn't do parsing - eval ' - foo() { - cat <<- EOF - hi - EOF - } - foo - # sleep so eval can die - (sleep 1; foo) & - (sleep 1; foo) & - foo - ' & - sleep 2 - echo Left overs: * -expected-stdout: - hi - hi - hi - hi - Left overs: * ---- diff --git a/tests/history.t b/tests/history.t deleted file mode 100644 index ffc6600..0000000 --- a/tests/history.t +++ /dev/null @@ -1,560 +0,0 @@ -# $MirOS$ -# $OpenBSD: history.t,v 1.5 2001/01/28 23:04:56 niklas Exp $ -# -# Not tested yet: -# - commands in history file are not numbered negatively -# (and a few hundred other things) - -name: history-basic -description: - See if we can test history at all -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo hi - fc -l -expected-stdout: - hi - 1 echo hi -expected-stderr-pattern: - /^X*$/ ---- - -name: history-e-minus-1 -description: - Check if more recent command is executed -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo hi - echo there - fc -e - -expected-stdout: - hi - there - there -expected-stderr-pattern: - /^X*echo there\nX*$/ ---- - -name: history-e-minus-2 -description: - Check that repeated command is printed before command - is re-executed. -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - exec 2>&1 - echo hi - echo there - fc -e - -expected-stdout-pattern: - /X*hi\nX*there\nX*echo there\nthere\nX*/ -expected-stderr-pattern: - /^X*$/ ---- - -name: history-e-minus-3 -description: - fc -e - fails when there is no history - (ksh93 has a bug that causes this to fail) - (ksh88 loops on this) -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - fc -e - - echo ok -expected-stdout: - ok -expected-stderr-pattern: - /^X*.*:.*history.*\nX*$/ ---- - -name: history-e-minus-4 -description: - Check if "fc -e -" command output goes to stdout. -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo abc - fc -e - | (read x; echo "A $x") - echo ok -expected-stdout: - abc - A abc - ok -expected-stderr-pattern: - /^X*echo abc\nX*/ ---- - -name: history-e-minus-5 -description: - fc is replaced in history by new command. -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo abc def - echo ghi jkl - fc -e - echo - fc -l 2 4 -expected-stdout: - abc def - ghi jkl - ghi jkl - 2 echo ghi jkl - 3 echo ghi jkl - 4 fc -l 2 4 -expected-stderr-pattern: - /^X*echo ghi jkl\nX*$/ ---- - -name: history-list-1 -description: - List lists correct range - (ksh88 fails 'cause it lists the fc command) -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo line 1 - echo line 2 - echo line 3 - fc -l -- -2 -expected-stdout: - line 1 - line 2 - line 3 - 2 echo line 2 - 3 echo line 3 -expected-stderr-pattern: - /^X*$/ ---- - -name: history-list-2 -description: - Lists oldest history if given pre-historic number - (ksh93 has a bug that causes this to fail) - (ksh88 fails 'cause it lists the fc command) -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo line 1 - echo line 2 - echo line 3 - fc -l -- -40 -expected-stdout: - line 1 - line 2 - line 3 - 1 echo line 1 - 2 echo line 2 - 3 echo line 3 -expected-stderr-pattern: - /^X*$/ ---- - -name: history-list-3 -description: - Can give number 'options' to fc -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo line 1 - echo line 2 - echo line 3 - echo line 4 - fc -l -3 -2 -expected-stdout: - line 1 - line 2 - line 3 - line 4 - 2 echo line 2 - 3 echo line 3 -expected-stderr-pattern: - /^X*$/ ---- - -name: history-list-4 -description: - -1 refers to previous command -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo line 1 - echo line 2 - echo line 3 - echo line 4 - fc -l -1 -1 -expected-stdout: - line 1 - line 2 - line 3 - line 4 - 4 echo line 4 -expected-stderr-pattern: - /^X*$/ ---- - -name: history-list-5 -description: - List command stays in history -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo line 1 - echo line 2 - echo line 3 - echo line 4 - fc -l -1 -1 - fc -l -2 -1 -expected-stdout: - line 1 - line 2 - line 3 - line 4 - 4 echo line 4 - 4 echo line 4 - 5 fc -l -1 -1 -expected-stderr-pattern: - /^X*$/ ---- - -name: history-list-6 -description: - HISTSIZE limits about of history kept. - (ksh88 fails 'cause it lists the fc command) -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3! -file-setup: file 644 "Env" - PS1=X -stdin: - echo line 1 - echo line 2 - echo line 3 - echo line 4 - echo line 5 - fc -l -expected-stdout: - line 1 - line 2 - line 3 - line 4 - line 5 - 4 echo line 4 - 5 echo line 5 -expected-stderr-pattern: - /^X*$/ ---- - -name: history-list-7 -description: - fc allows too old/new errors in range specification -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3! -file-setup: file 644 "Env" - PS1=X -stdin: - echo line 1 - echo line 2 - echo line 3 - echo line 4 - echo line 5 - fc -l 1 30 -expected-stdout: - line 1 - line 2 - line 3 - line 4 - line 5 - 4 echo line 4 - 5 echo line 5 - 6 fc -l 1 30 -expected-stderr-pattern: - /^X*$/ ---- - -name: history-list-r-1 -description: - test -r flag in history -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo line 1 - echo line 2 - echo line 3 - echo line 4 - echo line 5 - fc -l -r 2 4 -expected-stdout: - line 1 - line 2 - line 3 - line 4 - line 5 - 4 echo line 4 - 3 echo line 3 - 2 echo line 2 -expected-stderr-pattern: - /^X*$/ ---- - -name: history-list-r-2 -description: - If first is newer than last, -r is implied. -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo line 1 - echo line 2 - echo line 3 - echo line 4 - echo line 5 - fc -l 4 2 -expected-stdout: - line 1 - line 2 - line 3 - line 4 - line 5 - 4 echo line 4 - 3 echo line 3 - 2 echo line 2 -expected-stderr-pattern: - /^X*$/ ---- - -name: history-list-r-3 -description: - If first is newer than last, -r is cancelled. -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo line 1 - echo line 2 - echo line 3 - echo line 4 - echo line 5 - fc -l -r 4 2 -expected-stdout: - line 1 - line 2 - line 3 - line 4 - line 5 - 2 echo line 2 - 3 echo line 3 - 4 echo line 4 -expected-stderr-pattern: - /^X*$/ ---- - -name: history-subst-1 -description: - Basic substitution -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo abc def - echo ghi jkl - fc -e - abc=AB 'echo a' -expected-stdout: - abc def - ghi jkl - AB def -expected-stderr-pattern: - /^X*echo AB def\nX*$/ ---- - -name: history-subst-2 -description: - Does subst find previous command? -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo abc def - echo ghi jkl - fc -e - jkl=XYZQRT 'echo g' -expected-stdout: - abc def - ghi jkl - ghi XYZQRT -expected-stderr-pattern: - /^X*echo ghi XYZQRT\nX*$/ ---- - -name: history-subst-3 -description: - Does subst find previous command when no arguments given -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo abc def - echo ghi jkl - fc -e - jkl=XYZQRT -expected-stdout: - abc def - ghi jkl - ghi XYZQRT -expected-stderr-pattern: - /^X*echo ghi XYZQRT\nX*$/ ---- - -name: history-subst-4 -description: - Global substitutions work - (ksh88 and ksh93 do not have -g option) -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo abc def asjj sadjhasdjh asdjhasd - fc -e - -g a=FooBAR -expected-stdout: - abc def asjj sadjhasdjh asdjhasd - FooBARbc def FooBARsjj sFooBARdjhFooBARsdjh FooBARsdjhFooBARsd -expected-stderr-pattern: - /^X*echo FooBARbc def FooBARsjj sFooBARdjhFooBARsdjh FooBARsdjhFooBARsd\nX*$/ ---- - -name: history-subst-5 -description: - Make sure searches don't find current (fc) command - (ksh88/ksh93 don't have the ? prefix thing so they fail this test) -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo abc def - echo ghi jkl - fc -e - abc=AB \?abc -expected-stdout: - abc def - ghi jkl - AB def -expected-stderr-pattern: - /^X*echo AB def\nX*$/ ---- - -name: history-ed-1 -description: - Basic (ed) editing works (assumes you have generic ed editor - that prints no prompts). -# No ed on os/2 (yet?). -category: !os:os2 -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo abc def - fc echo - s/abc/FOOBAR/ - w - q -expected-stdout: - abc def - 13 - 16 - FOOBAR def -expected-stderr-pattern: - /^X*echo FOOBAR def\nX*$/ ---- - -name: history-ed-2 -description: - Correct command is edited when number given -category: !os:os2 -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo line 1 - echo line 2 is here - echo line 3 - echo line 4 - fc 2 - s/is here/is changed/ - w - q -expected-stdout: - line 1 - line 2 is here - line 3 - line 4 - 20 - 23 - line 2 is changed -expected-stderr-pattern: - /^X*echo line 2 is changed\nX*$/ ---- - -name: history-ed-3 -description: - Newly created multi line commands show up as single command - in history. - (NOTE: will fail if using COMPLEX HISTORY compile time option) - (ksh88 fails 'cause it lists the fc command) -category: !os:os2 -arguments: !-i! -env-setup: !ENV=./Env!HISTFILE=hist.file! -file-setup: file 644 "Env" - PS1=X -stdin: - echo abc def - fc echo - s/abc/FOOBAR/ - $a - echo a new line - . - w - q - fc -l -expected-stdout: - abc def - 13 - 32 - FOOBAR def - a new line - 1 echo abc def - 2 echo FOOBAR def - echo a new line -expected-stderr-pattern: - /^X*echo FOOBAR def\necho a new line\nX*$/ ---- diff --git a/tests/ifs.t b/tests/ifs.t deleted file mode 100644 index c6be027..0000000 --- a/tests/ifs.t +++ /dev/null @@ -1,175 +0,0 @@ -name: IFS-space-1 -description: - Simple test, default IFS -stdin: - showargs() { for i; do echo -n " <$i>"; done; echo; } - set -- A B C - showargs 1 $* - showargs 2 "$*" - showargs 3 $@ - showargs 4 "$@" -expected-stdout: - <1> - <2> - <3> - <4> ---- - -name: IFS-colon-1 -description: - Simple test, IFS=: -stdin: - showargs() { for i; do echo -n " <$i>"; done; echo; } - IFS=: - set -- A B C - showargs 1 $* - showargs 2 "$*" - showargs 3 $@ - showargs 4 "$@" -expected-stdout: - <1> - <2> - <3> - <4> ---- - -name: IFS-null-1 -description: - Simple test, IFS="" -stdin: - showargs() { for i; do echo -n " <$i>"; done; echo; } - IFS="" - set -- A B C - showargs 1 $* - showargs 2 "$*" - showargs 3 $@ - showargs 4 "$@" -expected-stdout: - <1> - <2> - <3> - <4> ---- - -name: IFS-space-colon-1 -description: - Simple test, IFS=: -stdin: - showargs() { for i; do echo -n " <$i>"; done; echo; } - IFS="$IFS:" - set -- - showargs 1 $* - showargs 2 "$*" - showargs 3 $@ - showargs 4 "$@" - showargs 5 : "$@" -expected-stdout: - <1> - <2> <> - <3> - <4> - <5> <:> ---- - -name: IFS-space-colon-2 -description: - Simple test, IFS=: - At&t ksh fails this, POSIX says the test is correct. -stdin: - showargs() { for i; do echo -n " <$i>"; done; echo; } - IFS="$IFS:" - set -- - showargs :"$@" -expected-stdout: - <:> ---- - -name: IFS-space-colon-3 -description: - Simple test, IFS=: - pdksh fails both of these tests -stdin: - showargs() { for i; do echo -n " <$i>"; done; echo; } - IFS="$IFS:" - x= - set -- - showargs "$x$@" - showargs "$@$x" -expected-fail: yes -expected-stdout: - <> - <> ---- - -name: IFS-space-colon-4 -description: - Simple test, IFS=: -stdin: - showargs() { for i; do echo -n " <$i>"; done; echo; } - IFS="$IFS:" - set -- - showargs "$@$@" -expected-stdout: - ---- - -name: IFS-space-colon-5 -description: - Simple test, IFS=: - Don't know what POSIX thinks of this. at&t ksh does not do this. -stdin: - showargs() { for i; do echo -n " <$i>"; done; echo; } - IFS="$IFS:" - set -- - showargs "${@:-}" -expected-stdout: - <> ---- - -name: IFS-subst-1 -description: - Simple test, IFS=: -stdin: - showargs() { for i; do echo -n " <$i>"; done; echo; } - IFS="$IFS:" - x=":b: :" - echo -n '1:'; for i in $x ; do echo -n " [$i]" ; done ; echo - echo -n '2:'; for i in :b:: ; do echo -n " [$i]" ; done ; echo - showargs 3 $x - showargs 4 :b:: - x="a:b:" - echo -n '5:'; for i in $x ; do echo -n " [$i]" ; done ; echo - showargs 6 $x - x="a::c" - echo -n '7:'; for i in $x ; do echo -n " [$i]" ; done ; echo - showargs 8 $x - echo -n '9:'; for i in ${FOO-`echo -n h:i`th:ere} ; do echo -n " [$i]" ; done ; echo - showargs 10 ${FOO-`echo -n h:i`th:ere} - showargs 11 "${FOO-`echo -n h:i`th:ere}" -expected-stdout: - 1: [] [b] [] - 2: [:b::] - <3> <> <> - <4> <:b::> - 5: [a] [b] - <6> - 7: [a] [] [c] - <8> <> - 9: [h] [ith] [ere] - <10> - <11> ---- - -name: IFS-subst-2 -description: - manual page test, IFS=: -stdin: - showargs() { for i; do echo -n " <$i>"; done; echo; } - IFS=" :" - x=" A : B::D" - echo -n '1:'; for i in $x ; do echo -n " [$i]" ; done ; echo - showargs 2 $x -expected-stdout: - 1: [A] [B] [] [D] - <2> <> ---- diff --git a/tests/integer.t b/tests/integer.t deleted file mode 100644 index c69053d..0000000 --- a/tests/integer.t +++ /dev/null @@ -1,217 +0,0 @@ -name: integer-base-err-1 -description: - Can't have 0 base (causes shell to exit) -expected-exit: e != 0 -stdin: - typeset -i i - i=3 - i=0#4 - echo $i -expected-stderr-pattern: - /^.*:.*0#4.*\n$/ ---- - -name: integer-base-err-2 -description: - Can't have multiple bases in a 'constant' (causes shell to exit) - (ksh88 fails this test) -expected-exit: e != 0 -stdin: - typeset -i i - i=3 - i=2#110#11 - echo $i -expected-stderr-pattern: - /^.*:.*2#110#11.*\n$/ ---- - -name: integer-base-err-3 -description: - Syntax errors in expressions and effects on bases - (interactive so errors don't cause exits) - (ksh88 fails this test - shell exits, even with -i) -arguments: !-i! -stdin: - PS1= # minimize prompt hassles - typeset -i4 a=10 - typeset -i a=2+ - echo $a - typeset -i4 a=10 - typeset -i2 a=2+ - echo $a -expected-stderr-pattern: - /^([#\$] )?.*:.*2+.*\n.*:.*2+.*\n$/ -expected-stdout: - 4#22 - 4#22 ---- - -name: integer-base-err-4 -description: - Are invalid digits (according to base) errors? - (ksh93 fails this test) -expected-exit: e != 0 -stdin: - typeset -i i; - i=3#4 -expected-stderr-pattern: - /^([#\$] )?.*:.*3#4.*\n$/ ---- - - -name: integer-base-1 -description: - Missing number after base is treated as 0. -stdin: - typeset -i i - i=3 - i=2# - echo $i -expected-stdout: - 0 ---- - -name: integer-base-2 -description: - Check 'stickyness' of base in various situations -stdin: - typeset -i i=8 - echo $i - echo ---------- A - typeset -i4 j=8 - echo $j - echo ---------- B - typeset -i k=8 - typeset -i4 k=8 - echo $k - echo ---------- C - typeset -i4 l - l=3#10 - echo $l - echo ---------- D - typeset -i m - m=3#10 - echo $m - echo ---------- E - n=2#11 - typeset -i n - echo $n - n=10 - echo $n - echo ---------- F - typeset -i8 o=12 - typeset -i4 o - echo $o - echo ---------- G - typeset -i p - let p=8#12 - echo $p -expected-stdout: - 8 - ---------- A - 4#20 - ---------- B - 4#20 - ---------- C - 4#3 - ---------- D - 3#10 - ---------- E - 2#11 - 2#1010 - ---------- F - 4#30 - ---------- G - 8#12 ---- - -name: integer-base-3 -description: - More base parsing (hmm doesn't test much..) -stdin: - typeset -i aa - aa=1+12#10+2 - echo $aa - typeset -i bb - bb=1+$aa - echo $bb - typeset -i bb - bb=$aa - echo $bb - typeset -i cc - cc=$aa - echo $cc -expected-stdout: - 15 - 16 - 15 - 15 ---- - -name: integer-base-4 -description: - Check that things not declared as integers are not made integers, - also, check if base is not reset by -i with no arguments. - (ksh93 fails - prints 10#20 - go figure) -stdin: - xx=20 - let xx=10 - typeset -i | grep '^xx=' - typeset -i4 a=10 - typeset -i a=20 - echo $a -expected-stdout: - 4#110 ---- - -name: integer-base-5 -description: - More base stuff -stdin: - typeset -i4 a=3#10 - echo $a - echo -- - typeset -i j=3 - j=~3 - echo $j - echo -- - typeset -i k=1 - x[k=k+1]=3 - echo $k - echo -- - typeset -i l - for l in 1 2+3 4; do echo $l; done -expected-stdout: - 4#3 - -- - -4 - -- - 2 - -- - 1 - 5 - 4 ---- - -name: integer-base-6 -description: - Even more base stuff - (ksh93 fails this test - prints 0) -stdin: - typeset -i7 i - i= - echo $i -expected-stdout: - 7#0 ---- - -name: integer-base-7 -description: - Check that non-integer parameters don't get bases assigned -stdin: - echo $(( zz = 8#100 )) - echo $zz -expected-stdout: - 64 - 64 ---- diff --git a/tests/lineno.t b/tests/lineno.t deleted file mode 100644 index 5ce16a1..0000000 --- a/tests/lineno.t +++ /dev/null @@ -1,110 +0,0 @@ -name: lineno-stdin -description: - See if $LINENO is updated and can be modified. -stdin: - echo A $LINENO - echo B $LINENO - LINENO=20 - echo C $LINENO -expected-stdout: - A 1 - B 2 - C 20 ---- - -name: lineno-inc -description: - See if $LINENO is set for .'d files. -file-setup: file 644 "dotfile" - echo dot A $LINENO - echo dot B $LINENO - LINENO=20 - echo dot C $LINENO -stdin: - echo A $LINENO - echo B $LINENO - . ./dotfile -expected-stdout: - A 1 - B 2 - dot A 1 - dot B 2 - dot C 20 ---- - - -name: lineno-func -description: - See if $LINENO is set for commands in a function. -stdin: - echo A $LINENO - echo B $LINENO - bar() { - echo func A $LINENO - echo func B $LINENO - } - bar - echo C $LINENO -expected-stdout: - A 1 - B 2 - func A 4 - func B 5 - C 8 ---- - -name: lineno-unset -description: - See if unsetting LINENO makes it non-magic. -file-setup: file 644 "dotfile" - echo dot A $LINENO - echo dot B $LINENO -stdin: - unset LINENO - echo A $LINENO - echo B $LINENO - bar() { - echo func A $LINENO - echo func B $LINENO - } - bar - . ./dotfile - echo C $LINENO -expected-stdout: - A - B - func A - func B - dot A - dot B - C ---- - -name: lineno-unset-use -description: - See if unsetting LINENO makes it non-magic even - when it is re-used. -file-setup: file 644 "dotfile" - echo dot A $LINENO - echo dot B $LINENO -stdin: - unset LINENO - LINENO=3 - echo A $LINENO - echo B $LINENO - bar() { - echo func A $LINENO - echo func B $LINENO - } - bar - . ./dotfile - echo C $LINENO -expected-stdout: - A 3 - B 3 - func A 3 - func B 3 - dot A 3 - dot B 3 - C 3 ---- diff --git a/tests/read.t b/tests/read.t deleted file mode 100644 index 0a73f10..0000000 --- a/tests/read.t +++ /dev/null @@ -1,57 +0,0 @@ -# $MirOS$ -# $OpenBSD: read.t,v 1.3 2003/03/10 03:48:16 david Exp $ -# -# To test: -# POSIX: -# - if no -r, \ is escape character -# - \newline disappear -# - \ -> don't break here -# - \ -> -# - if -r, backslash is not special -# - if stdin is tty and shell interactive -# - prompt for continuation if \newline (prompt to stderr) -# - a here-document isn't terminated after newline ???? -# - remaining vars set to empty string (not null) -# - check field splitting -# - left over fields and their separators assigned to last var -# - exit status is normally 0 -# - exit status is > 0 on eof -# - exit status > 0 on error -# - signals interrupt reads -# extra: -# - can't change read-only variables -# - error if var name bogus -# - set -o allexport effects read -# ksh: -# x check default variable: REPLY -# - check -p, -s, -u options -# - check var?prompt stuff -# - "echo a b | read x y" sets x,y in parent shell (at&t) -# -name: read-IFS-1 -description: - Simple test, default IFS -stdin: - echo "A B " > IN - unset x y z - read x y z < IN - echo 1: "x[$x] y[$y] z[$z]" - echo 1a: ${z-z not set} - read x < IN - echo 2: "x[$x]" -expected-stdout: - 1: x[A] y[B] z[] - 1a: - 2: x[A B] ---- - -name: read-ksh-1 -description: - If no var specified, REPLY is used -stdin: - echo "abc" > IN - read < IN - echo "[$REPLY]"; -expected-stdout: - [abc] ---- diff --git a/tests/regress.t b/tests/regress.t deleted file mode 100644 index 34106b5..0000000 --- a/tests/regress.t +++ /dev/null @@ -1,1091 +0,0 @@ -# $MirOS$ -# $OpenBSD: regress.t,v 1.12 2003/11/08 19:17:27 jmc Exp $ -# -# The first 39 of these tests are from the old Bugs script. - -name: regression-1 -description: - Lex array code had problems with this. -stdin: - echo foo[ - n=bar - echo "hi[ $n ]=1" -expected-stdout: - foo[ - hi[ bar ]=1 ---- - - -name: regression-2 -description: - When PATH is set before running a command, the new path is - not used in doing the path search - $ echo echo hi > /tmp/q ; chmod a+rx /tmp/q - $ PATH=/tmp q - q: not found - $ - in comexec() the two lines - while (*vp != NULL) - (void) typeset(*vp++, xxx, 0); - need to be moved out of the switch to before findcom() is - called - I don't know what this will break. -stdin: - : ${PWD:-`pwd 2> /dev/null`} - : ${PWD:?"PWD not set - can't do test"} - mkdir Y - cat > Y/xxxscript << EOF - #!/bin/sh - # Need to restore path so echo can be found (some shells don't have - # it as a built-in) - PATH=\$OLDPATH - echo hi - exit 0 - EOF - chmod a+rx Y/xxxscript - export OLDPATH="$PATH" - PATH=$PWD/Y xxxscript - exit $? -expected-stdout: - hi ---- - - -# -# 3. Sun OS 4.0.x (This seems to be a problem with sun's PENDIN not being done -# properly) -# sleep 5^J ls^J ls^J ls [only first ls runs] -# vi ... ZZ (while waiting type) [some of the input gets eaten] -# [not present in SunOS 4.1.x] -#echo " [No automatic test for bug 3 - interactive]" - - -# -# 4. (fixed) -# -#echo " [Don't know what bug 4 was]" - - -# -# 5. Everywhere -# File name completion (^X,*) does not mesh well with cd and -# symbolic links. cd does path simplification wrt $PWD before -# doing the actual chdir(), while file name completion does -# not do the simplification. E.g., you are in directory A -# which has a symbolic link to directory B, you create a file -# called foobar and you then cd to the symlink to B, and type -# $ echo ../foo^X -# and the shell beeps at you. Would be more consistent to -# do the completion after simplifing the `$PWD/..'. -#echo " [No automatic test for bug 5 - interactive]" - - -name: regression-6 -description: - Parsing of $(..) expressions is non-optimal. It is - impossible to have any parentheses inside the expression. - I.e., - $ ksh -c 'echo $(echo \( )' - no closing quote - $ ksh -c 'echo $(echo "(" )' - no closing quote - $ - The solution is to hack the parsing clode in lex.c, the - question is how to hack it: should any parentheses be - escaped by a backslash, or should recursive parsing be done - (so quotes could also be used to hide hem). The former is - easier, the later better... -stdin: - echo $(echo \() -expected-stdout: - ( ---- - - -# -# 7. (fixed) -# -#echo " [Don't know what bug 7 was]" - - -# -# 8. Everywhere - NOT A BUG - this is what at&t ksh88 does -# Strange typset -x behaviour in functions. The following function -# does not set the environment variable BLAH outside the function: -# function blah -# { -# typeset -x BLAH=foobar -# } -# This function does work: -# function blah -# { BLAH=foobar; export BLAH -# } -#echo ' [Bug 8 was bogus]' - - -name: regression-9 -description: - Continue in a for loop does not work right: - for i in a b c ; do - if [ $i = b ] ; then - continue - fi - echo $i - done - Prints a forever... -stdin: - first=yes - for i in a b c ; do - if [ $i = b ] ; then - if [ $first = no ] ; then - echo 'continue in for loop broken' - break # hope break isn't broken too :-) - fi - first=no - continue - fi - done - echo bye -expected-stdout: - bye ---- - - -name: regression-10 -description: - The following: - set -- `false` - echo $? - shoud not print 0. (according to /bin/sh, at&t ksh88, and the - getopt(1) man page - not according to POSIX) -stdin: - set -- `false` - echo $? -expected-stdout: - 1 ---- - - -name: regression-11 -description: - The following: - x=/foo/bar/blah - echo ${x##*/} - should echo blah but on some machines echos /foo/bar/blah. -stdin: - x=/foo/bar/blah - echo ${x##*/} -expected-stdout: - blah ---- - - -name: regression-12 -description: - Both of the following echos produce the same output under sh/ksh.att: - #!/bin/sh - x="foo bar" - echo "`echo \"$x\"`" - echo "`echo "$x"`" - pdksh produces different output for the former (foo instead of foo\tbar) -stdin: - x="foo bar" - echo "`echo \"$x\"`" - echo "`echo "$x"`" -expected-stdout: - foo bar - foo bar ---- - - -name: regression-13 -description: - The following command hangs forever: - $ (: ; cat /etc/termcap) | sleep 2 - This is because the shell forks a shell to run the (..) command - and this shell has the pipe open. When the sleep dies, the cat - doesn't get a SIGPIPE 'cause a process (ie, the second shell) - still has the pipe open. - - NOTE: this test provokes a bizarre bug in ksh93 (shell starts reading - commands from /etc/termcap..) -time-limit: 10 -stdin: - echo A line of text that will be duplicated quite a number of times.> t1 - cat t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 t1 > t2 - cat t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 t2 > t1 - cat t1 t1 t1 t1 > t2 - (: ; cat t2) | sleep 1 ---- - - -name: regression-14 -description: - The command - $ (foobar) 2> /dev/null - generates no output under /bin/sh, but pdksh produces the error - foobar: not found - Also, the command - $ foobar 2> /dev/null - generates an error under /bin/sh and pdksh, but at&t ksh88 produces - no error (redirected to /dev/null). -stdin: - (you/should/not/see/this/error/1) 2> /dev/null - you/should/not/see/this/error/2 2> /dev/null - true ---- - - -name: regression-15 -description: - The command - $ whence foobar - generates a blank line under pdksh and sets the exit status to 0. - at&t ksh88 generates no output and sets the exit status to 1. Also, - the command - $ whence foobar cat - generates no output under at&t ksh88 (pdksh generates a blank line - and /bin/cat). -stdin: - whence does/not/exist > /dev/null - echo 1: $? - echo 2: $(whence does/not/exist | wc -l) - echo 3: $(whence does/not/exist cat | wc -l) -expected-stdout: - 1: 1 - 2: 0 - 3: 0 ---- - - -name: regression-16 -description: - ${var%%expr} seems to be broken in many places. On the mips - the commands - $ read line < /etc/passwd - $ echo $line - root:0:1:... - $ echo ${line%%:*} - root - $ echo $line - root - $ - change the value of line. On sun4s & pas, the echo ${line%%:*} doesn't - work. Haven't checked elsewhere... -script: - read x - y=$x - echo ${x%%:*} - echo $x -stdin: - root:asdjhasdasjhs:0:1:Root:/:/bin/sh -expected-stdout: - root - root:asdjhasdasjhs:0:1:Root:/:/bin/sh ---- - - -name: regression-17 -description: - The command - . /foo/bar - should set the exit status to non-zero (sh and at&t ksh88 do). - XXX doting a non existent file is a fatal error for a script -stdin: - . does/not/exist -expected-exit: e != 0 -expected-stderr-pattern: /.?/ ---- - - -# -# 18. Everywhere -# In vi mode ^X (and *) can dump core: -# $ ab[cd^XMemory fault (core dumped) -#echo " [No automatic test for bug 18 - interactive]" - - -name: regression-19 -description: - Both of the following echos should produce the same thing, but don't: - $ x=foo/bar - $ echo ${x%/*} - foo - $ echo "${x%/*}" - foo/bar -stdin: - x=foo/bar - echo "${x%/*}" -expected-stdout: - foo ---- - - -# -# 20. (same as 18) -# - - -name: regression-21 -description: - backslash does not work as expected in case labels: - $ x='-x' - $ case $x in - -\?) echo hi - esac - hi - $ x='-?' - $ case $x in - -\\?) echo hi - esac - hi - $ -stdin: - case -x in - -\?) echo fail - esac ---- - - -name: regression-22 -description: - Quoting backquotes inside backquotes doesn't work: - $ echo `echo hi \`echo there\` folks` - asks for more info. sh and at&t ksh88 both echo - hi there folks -stdin: - echo `echo hi \`echo there\` folks` -expected-stdout: - hi there folks ---- - - -name: regression-23 -description: - )) is not treated `correctly': - $ (echo hi ; (echo there ; echo folks)) - missing (( - $ - instead of (as sh and ksh.att) - $ (echo hi ; (echo there ; echo folks)) - hi - there - folks - $ -stdin: - ( : ; ( : ; echo hi)) -expected-stdout: - hi ---- - - -# -# 24. strangeness with file name completion involving symlinks to nowhere -# $ mkdir foo foo/bar -# $ ln -s /stuff/junk foo/bar/xx -# $ echo foo/*/xx  -# (beep) -# $ -#echo " [No automatic test for bug 24 - interactive]" - - -name: regression-25 -description: - Check reading stdin in a while loop. The read should only read - a single line, not a whole stdio buffer; the cat should get - the rest. -stdin: - (echo a; echo b) | while read x ; do - echo $x - cat > /dev/null - done -expected-stdout: - a ---- - - -name: regression-26 -description: - Check reading stdin in a while loop. The read should read both - lines, not just the first. -script: - a= - while [ "$a" != xxx ] ; do - last=$x - read x - cat /dev/null | sed 's/x/y/' - a=x$a - done - echo $last -stdin: - a - b -expected-stdout: - b ---- - - -name: regression-27 -description: - The command - . /does/not/exist - should cause a script to exit. -stdin: - . does/not/exist - echo hi -expected-exit: e != 0 -expected-stderr-pattern: /does\/not\/exist/ ---- - - -name: regression-28 -description: - variable assignements not detected well -stdin: - a.x=1 echo hi -expected-exit: e != 0 -expected-stderr-pattern: /a\.x=1/ ---- - - -name: regression-29 -description: - alias expansion different from at&t ksh88 -stdin: - alias a='for ' b='i in' - a b hi ; do echo $i ; done -expected-stdout: - hi ---- - - -name: regression-30 -description: - strange characters allowed inside ${...} -stdin: - echo ${a{b}} -expected-exit: e != 0 -expected-stderr-pattern: /.?/ ---- - - -name: regression-31 -description: - Does read handle partial lines correctly -script: - a= ret= - while [ "$a" != xxx ] ; do - read x y z - ret=$? - a=x$a - done - echo "[$x]" - echo $ret -stdin: ! - a A aA - b B Bb - c -expected-stdout: - [c] - 1 ---- - - -name: regression-32 -description: - Does read set variables to null at eof? -script: - a= - while [ "$a" != xxx ] ; do - read x y z - a=x$a - done - echo 1: ${x-x not set} ${y-y not set} ${z-z not set} - echo 2: ${x:+x not null} ${y:+y not null} ${z:+z not null} -stdin: - a A Aa - b B Bb -expected-stdout: - 1: - 2: ---- - - -name: regression-33 -description: - Does umask print a leading 0 when umask is 3 digits? -stdin: - umask 222 - umask -expected-stdout: - 0222 ---- - - -# -# -# Does umask print a umask of 0 sanely? -# There is lots of variety here (0, 00, 000, and 0000 have all been -# seen in various shells...) -# -#echo ' [Bug 34 was bogus]' - - -name: regression-35 -description: - Tempory files used for here-docs in functions get trashed after - the function is parsed (before it is executed) -stdin: - f1() { - cat <<- EOF - F1 - EOF - f2() { - cat <<- EOF - F2 - EOF - } - } - f1 - f2 - unset -f f1 - f2 -expected-stdout: - F1 - F2 - F2 ---- - - -name: regression-36 -description: - Command substitution breaks reading in while loop - (test from ) -stdin: - (echo abcdef; echo; echo 123) | - while read line - do - # the following line breaks it - c=`echo $line | wc -c` - echo $c - done -expected-stdout: - 7 - 1 - 4 ---- - - -name: regression-37 -description: - Machines with broken times() (reported by ) - time does not report correct real time -stdin: - time sleep 1 -expected-stderr-pattern: !/^\s*0\.0[\s\d]+real|^\s*real[\s]+0+\.0/ ---- - - -name: regression-38 -description: - set -e doesn't ignore exit codes for if/while/until/&&/||/!. -arguments: !-e! -stdin: - if false; then echo hi ; fi - false || true - false && true - while false; do echo hi; done - echo ok -expected-stdout: - ok ---- - - -name: regression-39 -description: - set -e: errors in command substitutions aren't ignored - Not clear if they should be or not... -expected-fail: yes -arguments: !-e! -stdin: - echo `false; echo hi` -expected-stdout: - hi ---- - -name: regression-40 -description: - This used to cause a core dump -env-setup: !RANDOM=12! -stdin: - echo hi -expected-stdout: - hi ---- - -name: regression-41 -description: - foo should be set to bar (should not be empty) -stdin: - foo=` - echo bar` - echo "($foo)" -expected-stdout: - (bar) ---- - -name: regression-42 -description: - Can't use command line assignments to assign readonly parameters. -stdin: - foo=bar - readonly foo - foo=stuff env | grep '^foo' -expected-exit: e != 0 -expected-stderr-pattern: - /.*read *only.*/ ---- - -name: regression-43 -description: - Can subshells be prefixed by redirections (historical shells allow - this) -stdin: - < /dev/null (sed 's/^/X/') ---- - -name: regression-44 -description: - getopts sets OPTIND correctly for unparsed option -stdin: - set -- -a -a -x - while getopts :a optc; do - echo "OPTARG=$OPTARG, OPTIND=$OPTIND, optc=$optc." - done - echo done -expected-stdout: - OPTARG=, OPTIND=2, optc=a. - OPTARG=, OPTIND=3, optc=a. - OPTARG=x, OPTIND=3, optc=?. - done ---- - -name: regression-45 -description: - Parameter assignments with [] recognized correctly -stdin: - FOO=*[12] - BAR=abc[ - MORE=[abc] - JUNK=a[bc - echo "<$FOO>" - echo "<$BAR>" - echo "<$MORE>" - echo "<$JUNK>" -expected-stdout: - <*[12]> - - <[abc]> - ---- - -name: regression-46 -description: - Check that alias expansion works in command substitutions and - at the end of file. -stdin: - alias x='echo hi' - FOO="`x` " - echo "[$FOO]" - x -expected-stdout: - [hi ] - hi ---- - -name: regression-47 -description: - Check that aliases are fully read. -stdin: - alias x='echo hi; - echo there' - x - echo done -expected-stdout: - hi - there - done ---- - -name: regression-48 -description: - Check that (here doc) temp files are not left behind after an exec. -stdin: - mkdir foo || exit 1 - TMPDIR=$PWD/foo $0 <<- 'EOF' - x() { - sed 's/^/X /' << E_O_F - hi - there - folks - E_O_F - echo "done ($?)" - } - echo=echo; [ -x /bin/echo ] && echo=/bin/echo - exec $echo subtest-1 hi - EOF - echo subtest-1 foo/* - TMPDIR=$PWD/foo $0 <<- 'EOF' - echo=echo; [ -x /bin/echo ] && echo=/bin/echo - sed 's/^/X /' << E_O_F; exec $echo subtest-2 hi - a - few - lines - E_O_F - EOF - echo subtest-2 foo/* -expected-stdout: - subtest-1 hi - subtest-1 foo/* - X a - X few - X lines - subtest-2 hi - subtest-2 foo/* ---- - -name: regression-49 -description: - Check that unset params with attributes are reported by set, those - sans attributes are not. -stdin: - unset FOO BAR - echo X$FOO - export BAR - typeset -i BLAH - set | grep FOO - set | grep BAR - set | grep BLAH -expected-stdout: - X - BAR - BLAH ---- - -name: regression-50 -description: - Check that aliases do not use continuation prompt after trailing - semi-colon. -file-setup: file 644 "env" - PS1=Y - PS2=X -env-setup: !ENV=./env! -arguments: !-i! -stdin: - alias foo='echo hi ; ' - foo - foo echo there -expected-stdout: - hi - hi - there -expected-stderr: ! - YYYY ---- - -name: regression-51 -description: - Check that set allows both +o and -o options on same command line. -stdin: - set a b c - set -o noglob +o allexport - echo A: $*, * -expected-stdout: - A: a b c, * ---- - -name: regression-52 -description: - Check that globing works in pipelined commands -file-setup: file 644 "env" - PS1=P -file-setup: file 644 "abc" - stuff -env-setup: !ENV=./env! -arguments: !-i! -stdin: - sed 's/^/X /' < ab* - echo mark 1 - sed 's/^/X /' < ab* | sed 's/^/Y /' - echo mark 2 -expected-stdout: - X stuff - mark 1 - Y X stuff - mark 2 -expected-stderr: ! - PPPPP ---- - -name: regression-53 -description: - Check that getopts works in functions -stdin: - #!/bin/ksh - - bfunc() { - echo bfunc: enter "(args: $*; OPTIND=$OPTIND)" - while getopts B oc; do - case $oc in - (B) - echo bfunc: B option - ;; - (*) - echo bfunc: odd option "($oc)" - ;; - esac - done - echo bfunc: leave - } - - function kfunc { - echo kfunc: enter "(args: $*; OPTIND=$OPTIND)" - while getopts K oc; do - case $oc in - (K) - echo kfunc: K option - ;; - (*) - echo bfunc: odd option "($oc)" - ;; - esac - done - echo kfunc: leave - } - - set -- -f -b -k -l - echo "line 1: OPTIND=$OPTIND" - getopts kbfl optc - echo "line 2: ret=$?, optc=$optc, OPTIND=$OPTIND" - bfunc -BBB blah - echo "line 3: OPTIND=$OPTIND" - getopts kbfl optc - echo "line 4: ret=$?, optc=$optc, OPTIND=$OPTIND" - kfunc -KKK blah - echo "line 5: OPTIND=$OPTIND" - getopts kbfl optc - echo "line 6: ret=$?, optc=$optc, OPTIND=$OPTIND" - echo - - OPTIND=1 - set -- -fbkl - echo "line 10: OPTIND=$OPTIND" - getopts kbfl optc - echo "line 20: ret=$?, optc=$optc, OPTIND=$OPTIND" - bfunc -BBB blah - echo "line 30: OPTIND=$OPTIND" - getopts kbfl optc - echo "line 40: ret=$?, optc=$optc, OPTIND=$OPTIND" - kfunc -KKK blah - echo "line 50: OPTIND=$OPTIND" - getopts kbfl optc - echo "line 60: ret=$?, optc=$optc, OPTIND=$OPTIND" -expected-stdout: - line 1: OPTIND=1 - line 2: ret=0, optc=f, OPTIND=2 - bfunc: enter (args: -BBB blah; OPTIND=2) - bfunc: B option - bfunc: B option - bfunc: leave - line 3: OPTIND=2 - line 4: ret=0, optc=b, OPTIND=3 - kfunc: enter (args: -KKK blah; OPTIND=1) - kfunc: K option - kfunc: K option - kfunc: K option - kfunc: leave - line 5: OPTIND=3 - line 6: ret=0, optc=k, OPTIND=4 - - line 10: OPTIND=1 - line 20: ret=0, optc=f, OPTIND=2 - bfunc: enter (args: -BBB blah; OPTIND=2) - bfunc: B option - bfunc: B option - bfunc: leave - line 30: OPTIND=2 - line 40: ret=1, optc=?, OPTIND=2 - kfunc: enter (args: -KKK blah; OPTIND=1) - kfunc: K option - kfunc: K option - kfunc: K option - kfunc: leave - line 50: OPTIND=2 - line 60: ret=1, optc=?, OPTIND=2 ---- - - -name: regression-54 -description: - Check that ; is not required before the then in if (( ... )) then ... -stdin: - if (( 1 )) then - echo ok dparen - fi - if [[ -n 1 ]] then - echo ok dbrackets - fi -expected-stdout: - ok dparen - ok dbrackets ---- - - -name: regression-55 -description: - Check ${foo:%bar} is allowed (ksh88 allows it...) -stdin: - x=fooXbarXblah - echo 1 ${x%X*} - echo 2 ${x:%X*} - echo 3 ${x%%X*} - echo 4 ${x:%%X*} - echo 5 ${x#*X} - echo 6 ${x:#*X} - echo 7 ${x##*X} - echo 8 ${x:##*X} -expected-stdout: - 1 fooXbar - 2 fooXbar - 3 foo - 4 foo - 5 barXblah - 6 barXblah - 7 blah - 8 blah ---- - - -name: regression-56 -description: - Check eval vs substitution exit codes - (this is what ksh88 does) -stdin: - eval $(false) - echo A $? - eval ' $(false)' - echo B $? - eval " $(false)" - echo C $? - eval "eval $(false)" - echo D $? - eval 'eval '"$(false)" - echo E $? - IFS="$IFS:" - eval $(echo :; false) - echo F $? -expected-stdout: - A 1 - B 1 - C 1 - D 0 - E 0 - F 1 ---- - -name: regression-57 -description: - Check if typeset output is correct for - uninitialized array elements. -stdin: - typeset -i xxx[4] - echo A - typeset -i | grep xxx | sed 's/^/ /' - echo B - typeset | grep xxx | sed 's/^/ /' - - xxx[1]=2+5 - echo M - typeset -i | grep xxx | sed 's/^/ /' - echo N - typeset | grep xxx | sed 's/^/ /' -expected-stdout: - A - xxx - B - typeset -i xxx - M - xxx[1]=7 - N - typeset -i xxx ---- - -name: regression-58 -description: - Check if trap exit is ok (exit not mistaken for signal name) -stdin: - trap 'echo hi' exit - trap exit 1 -expected-stdout: - hi ---- - -name: regression-59 -description: - Check if ${#array[*]} is calculated correctly. -stdin: - a[12]=hi - a[8]=there - echo ${#a[*]} -expected-stdout: - 2 ---- - -name: regression-60 -description: - Check if default exit status is previous command -stdin: - (true; exit) - echo A $? - (false; exit) - echo B $? - ( (exit 103) ; exit) - echo C $? -expected-stdout: - A 0 - B 1 - C 103 ---- - -name: regression-61 -description: - Check if EXIT trap is executed for sub shells. -stdin: - trap 'echo parent exit' EXIT - echo start - (echo A; echo A last) - echo B - (echo C; trap 'echo sub exit' EXIT; echo C last) - echo parent last -expected-stdout: - start - A - A last - B - C - C last - sub exit - parent last - parent exit ---- - -name: regression-62 -description: - Check if test -nt/-ot succeeds if second(first) file is missing. -stdin: - touch a - test a -nt b && echo nt OK || echo nt BAD - test b -ot a && echo ot OK || echo ot BAD -expected-stdout: - nt OK - ot OK ---- diff --git a/tests/syntax.t b/tests/syntax.t deleted file mode 100644 index 3f3853f..0000000 --- a/tests/syntax.t +++ /dev/null @@ -1,9 +0,0 @@ -name: syntax-1 -description: - Check that lone ampersand is a syntax error -stdin: - & -expected-exit: e != 0 -expected-stderr-pattern: - /syntax error/ ---- diff --git a/tests/th b/tests/th deleted file mode 100644 index f541b2b..0000000 --- a/tests/th +++ /dev/null @@ -1,1205 +0,0 @@ -#!/usr/bin/perl -# $MirOS$ -# $OpenBSD: th,v 1.10 2003/09/01 05:16:46 fgsch Exp $ -# -# Test harness for pdksh tests. -# -# Example test: -# name: a-test -# description: -# a test to show how tests are done -# arguments: !-x!-f! -# stdin: -# echo -n * -# false -# expected-stdout: ! -# * -# expected-stderr: -# + echo -n * -# + false -# expected-exit: 1 -# --- -# This runs the test-program (eg, pdksh) with the arguments -x and -f, -# standard input is a file containing "echo hi*\nfalse\n". The program -# is expected to produce "hi*" (no trailing newline) on standard output, -# "+ echo hi*\n+false\n" on standard error, and an exit code of 1. -# -# -# Format of test files: -# - blank lines and lines starting with # are ignored -# - a test file contains a series of tests -# - a test is a series of tag:value pairs ended with a "---" line -# (leading/trailing spaces are stripped from the first line of value) -# - test tags are: -# Tag Flag Description -# ----- ---- ----------- -# name r The name of the test; should be unique -# description m What test does -# arguments M Arguments to pass to the program; -# default is no arguments. -# script m Value is written to a file which -# is passed as an argument to the program -# (after the arguments arguments) -# stdin m Value is written to a file which is -# used as standard-input for the program; -# default is to use /dev/null. -# perl-setup m Value is a perl script which is executed -# just before the test is run. Try to -# avoid using this... -# perl-cleanup m Value is a perl script which is executed -# just after the test is run. Try to -# avoid using this... -# env-setup M Value is a list of NAME=VALUE elements -# which are put in the environment before -# the test is run. If the =VALUE is -# missing, NAME is removed from the -# environment. Programs are run with -# the following minimal environment: -# USER, LOGNAME, HOME, PATH, SHELL -# (values taken from the environment of -# the test harness). -# file-setup mps Used to create files, directories -# and symlinks. First word is either -# file, dir or symlink; second word is -# permissions; this is followed by a -# quoted word that is the name of the -# file; the end-quote should be followed -# by a newline, then the file data -# (if any). The first word may be -# preceeded by a ! to strip the trailing -# newline in a symlink. -# file-result mps Used to verify a file, symlink or -# directory is created correctly. -# The first word is either -# file, dir or symlink; second word is -# expected permissions; third word -# is user-id; fourth is group-id; -# fifth is "exact" or "pattern" -# indicating whether the file contents -# which follow is to be matched exactly -# or if it is a regular expression. -# The fifth argument is the quoted name -# of the file that should be created. -# The end-quote should be followed -# by a newline, then the file data -# (if any). The first word may be -# preceeded by a ! to strip the trailing -# newline in the file contents. -# The permissions, user and group fields -# may be * meaning accept any value. -# time-limit Time limit - the program is sent a -# SIGKILL N seconds. Default is no -# limit. -# expected-fail 'yes' if the test is expected to fail. -# expected-exit expected exit code. Can be a number, -# or a C expression using the variables -# e, s and w (exit code, termination -# signal, and status code). -# expected-stdout m What the test should generate on stdout; -# default is to expect no output. -# expected-stdout-pattern m A perl pattern which matches the -# expected output. -# expected-stderr m What the test should generate on stderr; -# default is to expect no output. -# expected-stderr-pattern m A perl pattern which matches the -# expected standard error. -# category m Specify a comma separated list of -# 'categories' of program that the test -# is to be run for. A category can be -# negated by prefixing the name with a !. -# The idea is that some tests in a -# test suite may apply to a particular -# program version and shouldn't be run -# on other versions. The category(s) of -# the program being tested can be -# specified on the command line. -# One category os:XXX is predefined -# (XXX is the operating system name, -# eg, linux, dec_osf). -# Flag meanings: -# r tag is required (eg, a test must have a name tag). -# m value can be multiple lines. Lines must be prefixed with -# a tab. If the value part of the initial tag:value line is -# - empty: the initial blank line is stripped. -# - a lone !: the last newline in the value is stripped; -# M value can be multiple lines (prefixed by a tab) and consists -# of multiple fields, delimited by a field separator character. -# The value must start and end with the f-s-c. -# p tag takes parameters (used with m). -# s tag can be used several times. -# - -$os = defined $^O ? $^O : 'unknown'; - -require 'signal.ph' unless $os eq 'os2'; -require 'errno.ph' unless $os eq 'os2'; -require 'getopts.pl'; - -($prog = $0) =~ s#.*/##; - -$Usage = < 0): $opt_t\n" - if $opt_t !~ /^\d+$/ || $opt_t <= 0; - $default_time_limit = $opt_t; -} -$program_kludge = defined $opt_P ? $opt_P : 0; - -if (defined $opt_C) { - foreach $c (split(',', $opt_C)) { - $c =~ s/\s+//; - die "$prog: categories can't be negated on the command line\n" - if ($c =~ /^!/); - $categories{$c} = 1; - } -} - -# Note which tests are to be run. -%do_test = (); -grep($do_test{$_} = 1, @ARGV); -$all_tests = @ARGV == 0; - -# Set up a very minimal environment -%new_env = (); -foreach $env (('USER', 'LOGNAME', 'HOME', 'PATH', 'SHELL')) { - $new_env{$env} = $ENV{$env} if defined $ENV{$env}; -} -if (defined $opt_e) { - # XXX need a way to allow many -e arguments... - if ($opt_e =~ /^([a-zA-Z_]\w*)(|=(.*))$/) { - $new_env{$1} = $2 eq '' ? $ENV{$1} : $3; - } else { - die "$0: bad -e argument: $opt_e\n"; - } -} -%old_env = %ENV; - -# The following doesn't work with perl5... Need to do it explicitly - yuck. -#%ENV = %new_env; -foreach $k (keys(%ENV)) { - delete $ENV{$k}; -} -$ENV{$k} = $v while ($k,$v) = each %new_env; - -die "$prog: couldn't make directory $tempdir - $!\n" if !mkdir($tempdir, 0777); - -chop($pwd = `pwd 2> /dev/null`); -die "$prog: couldn't get current working directory\n" if $pwd eq ''; -die "$prog: couldn't cd to $pwd - $!\n" if !chdir($pwd); - -if (!$program_kludge) { - $test_prog = "$pwd/$test_prog" if substr($test_prog, 0, 1) ne '/'; - die "$prog: $test_prog is not executable - bye\n" - if (! -x $test_prog && $os ne 'os2'); -} - -@trap_sigs = ('TERM', 'QUIT', 'INT', 'PIPE', 'HUP'); -@SIG{@trap_sigs} = ('cleanup_exit') x @trap_sigs; -$child_kill_ok = 0; -$SIG{'ALRM'} = 'catch_sigalrm'; - -$| = 1; - -if (-d $test_set) { - $file_prefix_skip = length($test_set) + 1; - $ret = &process_test_dir($test_set); -} else { - $file_prefix_skip = 0; - $ret = &process_test_file($test_set); -} -&cleanup_exit() if !defined $ret; - -$tot_failed = $nfailed + $nxfailed; -$tot_passed = $npassed + $nxpassed; -if ($tot_failed || $tot_passed) { - print "Total failed: $tot_failed"; - print " ($nxfailed unexpected)" if $nxfailed; - print " (as expected)" if $nfailed && !$nxfailed; - print "\nTotal passed: $tot_passed"; - print " ($nxpassed unexpected)" if $nxpassed; - print "\n"; -} - -&cleanup_exit('ok'); - -sub -cleanup_exit -{ - local($sig, $exitcode) = ('', 1); - - if ($_[0] eq 'ok') { - $exitcode = 0; - } elsif ($_[0] ne '') { - $sig = $_[0]; - } - - unlink($tempi, $tempo, $tempe, $temps); - &scrub_dir($tempdir) if defined $tempdir; - rmdir($tempdir) if defined $tempdir; - - if ($sig) { - $SIG{$sig} = 'DEFAULT'; - kill $sig, $$; - return; - } - exit $exitcode; -} - -sub -catch_sigalrm -{ - $SIG{'ALRM'} = 'catch_sigalrm'; - kill(9, $child_pid) if $child_kill_ok; - $child_killed = 1; -} - -sub -process_test_dir -{ - local($dir) = @_; - local($ret, $file); - local(@todo) = (); - - if (!opendir(DIR, $dir)) { - print STDERR "$prog: can't open directory $dir - $!\n"; - return undef; - } - while (defined ($file = readdir(DIR))) { - push(@todo, $file) if $file =~ /^[^.].*\.t$/; - } - closedir(DIR); - - foreach $file (@todo) { - $file = "$dir/$file"; - if (-d $file) { - $ret = &process_test_dir($file); - } elsif (-f _) { - $ret = &process_test_file($file); - } - last if !defined $ret; - } - - return $ret; -} - -sub -process_test_file -{ - local($file) = @_; - local($ret); - - if (!open(IN, $file)) { - print STDERR "$prog: can't open $file - $!\n"; - return undef; - } - while (1) { - $ret = &read_test($file, IN, *test); - last if !defined $ret || !$ret; - next if !$all_tests && !$do_test{$test{'name'}}; - next if !&category_check(*test); - $ret = &run_test(*test); - last if !defined $ret; - } - close(IN); - - return $ret; -} - -sub -run_test -{ - local(*test) = @_; - local($name) = $test{':full-name'}; - - #print "Running test $name...\n" if $verbose; - - if (defined $test{'stdin'}) { - return undef if !&write_file($tempi, $test{'stdin'}); - $ifile = $tempi; - } else { - $ifile = '/dev/null'; - } - - if (defined $test{'script'}) { - return undef if !&write_file($temps, $test{'script'}); - } - - return undef if !&scrub_dir($tempdir); - - if (!chdir($tempdir)) { - print STDERR "$prog: couldn't cd to $tempdir - $!\n"; - return undef; - } - - if (defined $test{'file-setup'}) { - local($i); - local($type, $perm, $rest, $c, $len, $name); - - for ($i = 0; $i < $test{'file-setup'}; $i++) { - $val = $test{"file-setup:$i"}; - # - # format is: type perm "name" - # - ($type, $perm, $rest) = - split(' ', $val, 3); - $c = substr($rest, 0, 1); - $len = index($rest, $c, 1) - 1; - $name = substr($rest, 1, $len); - $rest = substr($rest, 2 + $len); - $perm = oct($perm) if $perm =~ /^\d+$/; - if ($type eq 'file') { - return undef if !&write_file($name, $rest); - if (!chmod($perm, $name)) { - print STDERR - "$prog:$test{':long-name'}: can't chmod $perm $name - $!\n"; - return undef; - } - } elsif ($type eq 'dir') { - if (!mkdir($name, $perm)) { - print STDERR - "$prog:$test{':long-name'}: can't mkdir $perm $name - $!\n"; - return undef; - } - } elsif ($type eq 'symlink') { - local($oumask) = umask($perm); - local($ret) = symlink($rest, $name); - umask($oumask); - if (!$ret) { - print STDERR - "$prog:$test{':long-name'}: couldn't create symlink $name - $!\n"; - return undef; - } - } - } - } - - if (defined $test{'perl-setup'}) { - eval $test{'perl-setup'}; - if ($@ ne '') { - print STDERR "$prog:$test{':long-name'}: error running perl-setup - $@\n"; - return undef; - } - } - - $pid = fork; - if (!defined $pid) { - print STDERR "$prog: can't fork - $!\n"; - return undef; - } - if (!$pid) { - @SIG{@trap_sigs} = ('DEFAULT') x @trap_sigs; - $SIG{'ALRM'} = 'DEFAULT'; - if (defined $test{'env-setup'}) { - local($var, $val, $i); - - foreach $var (split(substr($test{'env-setup'}, 0, 1), - $test{'env-setup'})) - { - $i = index($var, '='); - next if $i == 0 || $var eq ''; - if ($i < 0) { - delete $ENV{$var}; - } else { - $ENV{substr($var, 0, $i)} = substr($var, $i + 1); - } - } - } - if (!open(STDIN, "< $ifile")) { - print STDERR "$prog: couldn't open $ifile in child - $!\n"; - kill('TERM', $$); - } - if (!open(STDOUT, "> $tempo")) { - print STDERR "$prog: couldn't open $tempo in child - $!\n"; - kill('TERM', $$); - } - if (!open(STDERR, "> $tempe")) { - print STDOUT "$prog: couldn't open $tempe in child - $!\n"; - kill('TERM', $$); - } - if ($program_kludge) { - @argv = split(' ', $test_prog); - } else { - @argv = ($test_prog); - } - if (defined $test{'arguments'}) { - push(@argv, - split(substr($test{'arguments'}, 0, 1), - substr($test{'arguments'}, 1))); - } - push(@argv, $temps) if defined $test{'script'}; - exec(@argv); - print STDERR "$prog: couldn't execute $test_prog - $!\n"; - kill('TERM', $$); - exit(95); - } - $child_pid = $pid; - $child_killed = 0; - $child_kill_ok = 1; - alarm($test{'time-limit'}) if defined $test{'time-limit'}; - while (1) { - $xpid = waitpid($pid, 0); - $child_kill_ok = 0; - if ($xpid < 0) { - next if $! == &EINTR; - print STDERR "$prog: error waiting for child - $!\n"; - return undef; - } - last; - } - $status = $?; - alarm(0) if defined $test{'time-limit'}; - - $failed = 0; - $why = ''; - - if ($child_killed) { - $failed = 1; - $why .= "\ttest timed out (limit of $test{'time-limit'} seconds)\n"; - } - - $ret = &eval_exit($test{'long-name'}, $status, $test{'expected-exit'}); - return undef if !defined $ret; - if (!$ret) { - local($expl); - - $failed = 1; - if (($status & 0xff) == 0x7f) { - $expl = "stopped"; - } elsif (($status & 0xff)) { - $expl = "signal " . ($status & 0x7f); - } else { - $expl = "exit-code " . (($status >> 8) & 0xff); - } - $why .= - "\tunexpected exit status $status ($expl), expected $test{'expected-exit'}\n"; - } - - $tmp = &check_output($test{'long-name'}, $tempo, 'stdout', - $test{'expected-stdout'}, $test{'expected-stdout-pattern'}); - return undef if !defined $tmp; - if ($tmp ne '') { - $failed = 1; - $why .= $tmp; - } - - $tmp = &check_output($test{'long-name'}, $tempe, 'stderr', - $test{'expected-stderr'}, $test{'expected-stderr-pattern'}); - return undef if !defined $tmp; - if ($tmp ne '') { - $failed = 1; - $why .= $tmp; - } - - $tmp = &check_file_result(*test); - return undef if !defined $tmp; - if ($tmp ne '') { - $failed = 1; - $why .= $tmp; - } - - if (defined $test{'perl-cleanup'}) { - eval $test{'perl-cleanup'}; - if ($@ ne '') { - print STDERR "$prog:$test{':long-name'}: error running perl-cleanup - $@\n"; - return undef; - } - } - - if (!chdir($pwd)) { - print STDERR "$prog: couldn't cd to $pwd - $!\n"; - return undef; - } - - if ($failed) { - if (!$test{'expected-fail'}) { - print "FAIL $name\n"; - $nxfailed++; - } else { - print "fail $name (as expected)\n"; - $nfailed++; - } - $why = "\tDescription" - . &wrap_lines($test{'description'}, " (missing)\n") - . $why; - } elsif ($test{'expected-fail'}) { - print "PASS $name (unexpectedly)\n"; - $nxpassed++; - } else { - print "pass $name\n"; - $npassed++; - } - print $why if $verbose; - return 0; -} - -sub -category_check -{ - local(*test) = @_; - local($c); - - return 1 if (!defined $test{'category'}); - local($ok) = 0; - foreach $c (split(',', $test{'category'})) { - $c =~ s/\s+//; - if ($c =~ /^!/) { - $c = $'; - return 0 if (defined $categories{$c}); - } else { - $ok = 1 if (defined $categories{$c}); - } - } - return $ok; -} - -sub -scrub_dir -{ - local($dir) = @_; - local(@todo) = (); - local($file); - - if (!opendir(DIR, $dir)) { - print STDERR "$prog: couldn't open directory $dir - $!\n"; - return undef; - } - while (defined ($file = readdir(DIR))) { - push(@todo, $file) if $file ne '.' && $file ne '..'; - } - closedir(DIR); - foreach $file (@todo) { - $file = "$dir/$file"; - if (-d $file) { - return undef if !&scrub_dir($file); - if (!rmdir($file)) { - print STDERR "$prog: couldn't rmdir $file - $!\n"; - return undef; - } - } else { - if (!unlink($file)) { - print STDERR "$prog: couldn't unlink $file - $!\n"; - return undef; - } - } - } - return 1; -} - -sub -write_file -{ - local($file, $str) = @_; - - if (!open(TEMP, "> $file")) { - print STDERR "$prog: can't open $file - $!\n"; - return undef; - } - print TEMP $str; - if (!close(TEMP)) { - print STDERR "$prog: error writing $file - $!\n"; - return undef; - } - return 1; -} - -sub -check_output -{ - local($name, $file, $what, $expect, $expect_pat) = @_; - local($got) = ''; - local($why) = ''; - local($ret); - - if (!open(TEMP, "< $file")) { - print STDERR "$prog:$name($what): couldn't open $file after running program - $!\n"; - return undef; - } - while () { - $got .= $_; - } - close(TEMP); - return compare_output($name, $what, $expect, $expect_pat, $got); -} - -sub -compare_output -{ - local($name, $what, $expect, $expect_pat, $got) = @_; - local($why) = ''; - - if (defined $expect_pat) { - $_ = $got; - $ret = eval "$expect_pat"; - if ($@ ne '') { - print STDERR "$prog:$name($what): error evaluating $what pattern: $expect_pat - $@\n"; - return undef; - } - if (!$ret) { - $why = "\tunexpected $what - wanted pattern"; - $why .= &wrap_lines($expect_pat); - $why .= "\tgot"; - $why .= &wrap_lines($got); - } - } else { - $expect = '' if !defined $expect; - if ($got ne $expect) { - $why .= "\tunexpected $what - " . &first_diff($expect, $got) . "\n"; - $why .= "\twanted"; - $why .= &wrap_lines($expect); - $why .= "\tgot"; - $why .= &wrap_lines($got); - } - } - return $why; -} - -sub -wrap_lines -{ - local($str, $empty) = @_; - local($nonl) = substr($str, -1, 1) ne "\n"; - - return (defined $empty ? $empty : " nothing\n") if $str eq ''; - substr($str, 0, 0) = ":\n"; - $str =~ s/\n/\n\t\t/g; - if ($nonl) { - $str .= "\n\t[incomplete last line]\n"; - } else { - chop($str); - chop($str); - } - return $str; -} - -sub -first_diff -{ - local($exp, $got) = @_; - local($lineno, $char) = (1, 1); - local($i, $exp_len, $got_len); - local($ce, $cg); - - $exp_len = length($exp); - $got_len = length($got); - if ($exp_len != $got_len) { - if ($exp_len < $got_len) { - if (substr($got, 0, $exp_len) eq $exp) { - return "got too much output"; - } - } elsif (substr($exp, 0, $got_len) eq $got) { - return "got too little output"; - } - } - for ($i = 0; $i < $exp_len; $i++) { - $ce = substr($exp, $i, 1); - $cg = substr($got, $i, 1); - last if $ce ne $cg; - $char++; - if ($ce eq "\n") { - $lineno++; - $char = 1; - } - } - return "first difference: line $lineno, char $char (wanted '" - . &format_char($ce) . "', got '" - . &format_char($cg) . "'"; -} - -sub -format_char -{ - local($ch, $s); - - $ch = ord($_[0]); - if ($ch == 10) { - return '\n'; - } elsif ($ch == 13) { - return '\r'; - } elsif ($ch == 8) { - return '\b'; - } elsif ($ch == 9) { - return '\t'; - } elsif ($ch > 127) { - $ch -= 127; - $s = "M-"; - } else { - $s = ''; - } - if ($ch < 32) { - $s .= '^'; - $ch += ord('@'); - } elsif ($ch == 127) { - return $s . "^?"; - } - return $s . sprintf("%c", $ch); -} - -sub -eval_exit -{ - local($name, $status, $expect) = @_; - local($expr); - local($w, $e, $s) = ($status, ($status >> 8) & 0xff, $status & 0x7f); - - $e = -1000 if $status & 0xff; - $s = -1000 if $s == 0x7f; - if (!defined $expect) { - $expr = '$w == 0'; - } elsif ($expect =~ /^(|-)\d+$/) { - $expr = "\$e == $expect"; - } else { - $expr = $expect; - $expr =~ s/\b([wse])\b/\$$1/g; - $expr =~ s/\b(SIG[A-Z0-9]+)\b/&$1/g; - } - $w = eval $expr; - if ($@ ne '') { - print STDERR "$prog:$test{':long-name'}: bad expected-exit expression: $expect ($@)\n"; - return undef; - } - return $w; -} - -sub -read_test -{ - local($file, $in, *test) = @_; - local($field, $val, $flags, $do_chop, $need_redo, $start_lineno); - local(%cnt, $sfield); - - %test = (); - %cnt = (); - while (<$in>) { - next if /^\s*$/; - next if /^ *#/; - last if /^\s*---\s*$/; - $start_lineno = $. if !defined $start_lineno; - if (!/^([-\w]+):\s*(|\S|\S.*\S)\s*$/) { - print STDERR "$prog:$file:$.: unrecognized line\n"; - return undef; - } - ($field, $val) = ($1, $2); - $sfield = $field; - $flags = $test_fields{$field}; - if (!defined $flags) { - print STDERR "$prog:$file:$.: unrecognized field \"$field\"\n"; - return undef; - } - if ($flags =~ /s/) { - local($cnt) = $cnt{$field}++; - $test{$field} = $cnt{$field}; - $cnt = 0 if $cnt eq ''; - $sfield .= ":$cnt"; - } elsif (defined $test{$field}) { - print STDERR "$prog:$file:$.: multiple \"$field\" fields\n"; - return undef; - } - $do_chop = $flags !~ /m/; - $need_redo = 0; - if ($val eq '' || $val eq '!' || $flags =~ /p/) { - if ($flags =~ /[Mm]/) { - if ($flags =~ /p/) { - if ($val =~ /^!/) { - $do_chop = 1; - $val = $'; - } else { - $do_chop = 0; - } - if ($val eq '') { - print STDERR - "$prog:$file:$.: no parameters given for field \"$field\"\n"; - return undef; - } - } else { - if ($val eq '!') { - $do_chop = 1; - } - $val = ''; - } - while (<$in>) { - last if !/^\t/; - $val .= $'; - } - chop $val if $do_chop; - $do_chop = 1; - $need_redo = 1; - # - # Syntax check on fields that can several instances - # (can give useful line numbers this way) - # - if ($field eq 'file-setup') { - local($type, $perm, $rest, $c, $len, $name); - # - # format is: type perm "name" - # - if ($val !~ /^[ \t]*(\S+)[ \t]+(\S+)[ \t]+([^ \t].*)/) { - print STDERR - "$prog:$file:$.: bad paramter line for file-setup field\n"; - return undef; - } - ($type, $perm, $rest) = ($1, $2, $3); - if ($type !~ /^(file|dir|symlink)$/) { - print STDERR - "$prog:$file:$.: bad file type for file-setup: $type\n"; - return undef; - } - if ($perm !~ /^\d+$/) { - print STDERR - "$prog:$file:$.: bad permissions for file-setup: $type\n"; - return undef; - } - $c = substr($rest, 0, 1); - if (($len = index($rest, $c, 1) - 1) <= 0) { - print STDERR - "$prog:$file:$.: missing end quote for file name in file-setup: $rest\n"; - return undef; - } - $name = substr($rest, 1, $len); - if ($name =~ /^\// || $name =~ /(^|\/)\.\.(\/|$)/) { - # Note: this is not a security thing - just a sanity - # check - a test can still use symlinks to get at files - # outside the test directory. - print STDERR -"$prog:$file:$.: file name in file-setup is absolute or contains ..: $name\n"; - return undef; - } - } - if ($field eq 'file-result') { - local($type, $perm, $uid, $gid, $matchType, - $rest, $c, $len, $name); - # - # format is: type perm uid gid matchType "name" - # - if ($val !~ /^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S.*)/) { - print STDERR - "$prog:$file:$.: bad paramter line for file-result field\n"; - return undef; - } - ($type, $perm, $uid, $gid, $matchType, $rest) - = ($1, $2, $3, $4, $5, $6); - if ($type !~ /^(file|dir|symlink)$/) { - print STDERR - "$prog:$file:$.: bad file type for file-result: $type\n"; - return undef; - } - if ($perm !~ /^\d+$/ && $perm ne '*') { - print STDERR - "$prog:$file:$.: bad permissions for file-result: $perm\n"; - return undef; - } - if ($uid !~ /^\d+$/ && $uid ne '*') { - print STDERR - "$prog:$file:$.: bad user-id for file-result: $uid\n"; - return undef; - } - if ($gid !~ /^\d+$/ && $gid ne '*') { - print STDERR - "$prog:$file:$.: bad group-id for file-result: $gid\n"; - return undef; - } - if ($matchType !~ /^(exact|pattern)$/) { - print STDERR - "$prog:$file:$.: bad match type for file-result: $matchType\n"; - return undef; - } - $c = substr($rest, 0, 1); - if (($len = index($rest, $c, 1) - 1) <= 0) { - print STDERR - "$prog:$file:$.: missing end quote for file name in file-result: $rest\n"; - return undef; - } - $name = substr($rest, 1, $len); - if ($name =~ /^\// || $name =~ /(^|\/)\.\.(\/|$)/) { - # Note: this is not a security thing - just a sanity - # check - a test can still use symlinks to get at files - # outside the test directory. - print STDERR -"$prog:$file:$.: file name in file-result is absolute or contains ..: $name\n"; - return undef; - } - } - } elsif ($val eq '') { - print STDERR - "$prog:$file:$.: no value given for field \"$field\"\n"; - return undef; - } - } - $val .= "\n" if !$do_chop; - $test{$sfield} = $val; - redo if $need_redo; - } - if ($_ eq '') { - if (%test) { - print STDERR - "$prog:$file:$start_lineno: end-of-file while reading test\n"; - return undef; - } - return 0; - } - - while (($field, $val) = each %test_fields) { - if ($val =~ /r/ && !defined $test{$field}) { - print STDERR - "$prog:$file:$start_lineno: required field \"$field\" missing\n"; - return undef; - } - } - - $test{':full-name'} = substr($file, $file_prefix_skip) . ":$test{'name'}"; - $test{':long-name'} = "$file:$start_lineno:$test{'name'}"; - - # Syntax check on specific fields - if (defined $test{'expected-fail'}) { - if ($test{'expected-fail'} !~ /^(yes|no)$/) { - print STDERR - "$prog:$test{':long-name'}: bad value for expected-fail field\n"; - return undef; - } - $test{'expected-fail'} = $1 eq 'yes'; - } else { - $test{'expected-fail'} = 0; - } - if (defined $test{'arguments'}) { - local($firstc) = substr($test{'arguments'}, 0, 1); - - if (substr($test{'arguments'}, -1, 1) ne $firstc) { - print STDERR "$prog:$test{':long-name'}: arguments field doesn't start and end with the same character\n"; - return undef; - } - } - if (defined $test{'env-setup'}) { - local($firstc) = substr($test{'env-setup'}, 0, 1); - - if (substr($test{'env-setup'}, -1, 1) ne $firstc) { - print STDERR "$prog:$test{':long-name'}: env-setup field doesn't start and end with the same character\n"; - return undef; - } - } - if (defined $test{'expected-exit'}) { - local($val) = $test{'expected-exit'}; - - if ($val =~ /^(|-)\d+$/) { - if ($val < 0 || $val > 255) { - print STDERR "$prog:$test{':long-name'}: expected-exit value $val not in 0..255\n"; - return undef; - } - } elsif ($val !~ /^([\s<>+-=*%\/&|!()]|\b[wse]\b|\bSIG[A-Z0-9]+\b)+$/) { - print STDERR "$prog:$test{':long-name'}: bad expected-exit expression: $val\n"; - return undef; - } - } else { - $test{'expected-exit'} = 0; - } - if (defined $test{'expected-stdout'} - && defined $test{'expected-stdout-pattern'}) - { - print STDERR "$prog:$test{':long-name'}: can't use both expected-stdout and expected-stdout-pattern\n"; - return undef; - } - if (defined $test{'expected-stderr'} - && defined $test{'expected-stderr-pattern'}) - { - print STDERR "$prog:$test{':long-name'}: can't use both expected-stderr and expected-stderr-pattern\n"; - return undef; - } - if (defined $test{'time-limit'}) { - if ($test{'time-limit'} !~ /^\d+$/ || $test{'time-limit'} == 0) { - print STDERR - "$prog:$test{':long-name'}: bad value for time-limit field\n"; - return undef; - } - } elsif (defined $default_time_limit) { - $test{'time-limit'} = $default_time_limit; - } - - if (defined $known_tests{$test{'name'}}) { - print STDERR "$prog:$test{':long-name'}: warning: duplicate test name ${test{'name'}}\n"; - } - $known_tests{$test{'name'}} = 1; - - return 1; -} - -sub -tty_msg -{ - local($msg) = @_; - - open(TTY, "> /dev/tty") || return 0; - print TTY $msg; - close(TTY); - return 1; -} - -sub -never_called_funcs -{ - return 0; - &tty_msg("hi\n"); - &never_called_funcs(); - &catch_sigalrm(); - $old_env{'foo'} = 'bar'; - $internal_test_fields{'foo'} = 'bar'; -} - -sub -check_file_result -{ - local(*test) = @_; - - return '' if (!defined $test{'file-result'}); - - local($why) = ''; - local($i); - local($type, $perm, $uid, $gid, $rest, $c, $len, $name); - local(@stbuf); - - for ($i = 0; $i < $test{'file-result'}; $i++) { - $val = $test{"file-result:$i"}; - # - # format is: type perm "name" - # - ($type, $perm, $uid, $gid, $matchType, $rest) = - split(' ', $val, 6); - $c = substr($rest, 0, 1); - $len = index($rest, $c, 1) - 1; - $name = substr($rest, 1, $len); - $rest = substr($rest, 2 + $len); - $perm = oct($perm) if $perm =~ /^\d+$/; - - @stbuf = lstat($name); - if (!@stbuf) { - $why .= "\texpected $type \"$name\" not created\n"; - next; - } - if ($perm ne '*' && ($stbuf[2] & 07777) != $perm) { - $why .= "\t$type \"$name\" has unexpected permissions\n"; - $why .= sprintf("\t\texpected 0%o, found 0%o\n", - $perm, $stbuf[2] & 07777); - } - if ($uid ne '*' && $stbuf[4] != $uid) { - $why .= "\t$type \"$name\" has unexpected user-id\n"; - $why .= sprintf("\t\texpected %d, found %d\n", - $uid, $stbuf[4]); - } - if ($gid ne '*' && $stbuf[5] != $gid) { - $why .= "\t$type \"$name\" has unexpected group-id\n"; - $why .= sprintf("\t\texpected %d, found %d\n", - $gid, $stbuf[5]); - } - - if ($type eq 'file') { - if (-l _ || ! -f _) { - $why .= "\t$type \"$name\" is not a regular file\n"; - } else { - local $tmp = &check_output($test{'long-name'}, $name, - "$type contents in \"$name\"", - $matchType eq 'exact' ? $rest : undef - $matchType eq 'pattern' ? $rest : undef); - return undef if (!defined $tmp); - $why .= $tmp; - } - } elsif ($type eq 'dir') { - if ($rest !~ /^\s*$/) { - print STDERR "$prog:$test{':long-name'}: file-result test for directory $name should not have content specified\n"; - return undef; - } - if (-l _ || ! -d _) { - $why .= "\t$type \"$name\" is not a directory\n"; - } - } elsif ($type eq 'symlink') { - if (!-l _) { - $why .= "\t$type \"$name\" is not a symlink\n"; - } else { - local $content = readlink($name); - if (!defined $content) { - print STDERR "$prog:$test{':long-name'}: file-result test for $type $name failed - could not readlink - $!\n"; - return undef; - } - local $tmp = &compare_output($test{'long-name'}, - "$type contents in \"$name\"", - $matchType eq 'exact' ? $rest : undef - $matchType eq 'pattern' ? $rest : undef); - return undef if (!defined $tmp); - $why .= $tmp; - } - } - } - - return $why; -} diff --git a/tests/unclass1.t b/tests/unclass1.t deleted file mode 100644 index 258c206..0000000 --- a/tests/unclass1.t +++ /dev/null @@ -1,99 +0,0 @@ -name: xxx-quoted-newline-1 -description: - Check that \ works inside of ${} -stdin: - abc=2 - echo ${ab\ - c} -expected-stdout: - 2 ---- - -name: xxx-quoted-newline-2 -description: - Check that \ works at the start of a here document -stdin: - cat << EO\ - F - hi - EOF -expected-stdout: - hi ---- - -name: xxx-quoted-newline-3 -description: - Check that \ works at the end of a here document -stdin: - cat << EOF - hi - EO\ - F -expected-stdout: - hi ---- - -name: xxx-multi-assignment-cmd -description: - Check that assignments in a command affect subsequent assignments - in the same command -stdin: - FOO=abc - FOO=123 BAR=$FOO - echo $BAR -expected-stdout: - 123 ---- - -name: xxx-exec-environment-1 -description: - Check to see if exec sets it's environment correctly -stdin: - FOO=bar exec env -expected-stdout-pattern: - /(^|.*\n)FOO=bar\n/ ---- - -name: xxx-exec-environment-2 -description: - Check to make sure exec doesn't change environment if a program - isn't exec-ed -# Under os/2, _emx_sig environment variable changes. -category: !os:os2 -stdin: - env > bar1 - FOO=bar exec; env > bar2 - cmp -s bar1 bar2 ---- - -name: xxx-what-do-you-call-this-1 -stdin: - echo "${foo:-"a"}*" -expected-stdout: - a* ---- - -name: xxx-prefix-strip-1 -stdin: - foo='a cdef' - echo ${foo#a c} -expected-stdout: - def ---- - -name: xxx-prefix-strip-2 -stdin: - set a c - x='a cdef' - echo ${x#$*} -expected-stdout: - def ---- - -name: xxx-variable-syntax-1 -stdin: - echo ${:} -expected-stderr-pattern: - /bad substitution/ -expected-exit: 1 ---- diff --git a/tests/unclass2.t b/tests/unclass2.t deleted file mode 100644 index f893d20..0000000 --- a/tests/unclass2.t +++ /dev/null @@ -1,162 +0,0 @@ -name: xxx-subsitution-eval-order -description: - Check order of evaluation of expressions -stdin: - i=1 x= y= - set -A A abc def GHI j G k - echo ${A[x=(i+=1)]#${A[y=(i+=2)]}} - echo $x $y -expected-stdout: - HI - 2 4 ---- - -name: xxx-set-option-1 -description: - Check option parsing in set -stdin: - set -vsA foo -- A 1 3 2 - echo ${foo[*]} -expected-stderr: - echo ${foo[*]} -expected-stdout: - 1 2 3 A ---- - -name: xxx-exec-1 -description: - Check that exec exits for built-ins -arguments: !-i! -stdin: - exec print hi - echo still herre -expected-stdout: - hi -expected-stderr-pattern: /.*/ ---- - -name: xxx-while-1 -description: - Check the return value of while loops - XXX need to do same for for/select/until loops -stdin: - i=x - while [ $i != xxx ] ; do - i=x$i - if [ $i = xxx ] ; then - false - continue - fi - done - echo loop1=$? - - i=x - while [ $i != xxx ] ; do - i=x$i - if [ $i = xxx ] ; then - false - break - fi - done - echo loop2=$? - - i=x - while [ $i != xxx ] ; do - i=x$i - false - done - echo loop3=$? -expected-stdout: - loop1=0 - loop2=0 - loop3=1 ---- - -name: xxx-status-1 -description: - Check that blank lines don't clear $? -arguments: !-i! -stdin: - (exit 1) - echo $? - (exit 1) - - echo $? - true -expected-stdout: - 1 - 1 -expected-stderr-pattern: /.*/ ---- - -name: xxx-status-2 -description: - Check that $? is preserved in subshells, includes, traps. -stdin: - (exit 1) - - echo blank: $? - - (exit 2) - (echo subshell: $?) - - echo 'echo include: $?' > foo - (exit 3) - . ./foo - - trap 'echo trap: $?' ERR - (exit 4) - echo exit: $? -expected-stdout: - blank: 1 - subshell: 2 - include: 3 - trap: 4 - exit: 4 ---- - -name: xxx-clean-chars-1 -description: - Check MAGIC character is stuffed correctly -stdin: - echo `echo [£` -expected-stdout: - [£ ---- - -name: xxx-param-subst-qmark-1 -description: - Check suppresion of error message with null string. According to - POSIX, it shouldn't print the error as 'word' isn't ommitted. -stdin: - unset foo - x= - echo x${foo?$x} -expected-exit: 1 -expected-fail: yes -expected-stderr-pattern: !/not set/ ---- - -name: xxx-param-_-1 -description: - Check c flag is set. -arguments: !-c!echo "[$-]"! -expected-stdout-pattern: /^\[.*c.*\]$/ ---- - -name: env-prompt -description: - Check that prompt not printed when processing ENV -env-setup: !ENV=./foo! -file-setup: file 644 "foo" - XXX=_ - PS1=X - false && echo hmmm -arguments: !-i! -stdin: - echo hi${XXX}there -expected-stdout: - hi_there -expected-stderr: ! - XX ---- diff --git a/tests/version.t b/tests/version.t deleted file mode 100644 index aa4ddbe..0000000 --- a/tests/version.t +++ /dev/null @@ -1,9 +0,0 @@ -name: version-1 -description: - Check version of shell. -category: pdksh -stdin: - echo $KSH_VERSION -expected-stdout-pattern: - /PD KSH v5\.2\.14 MirOS R20u in (native )?KSH mode( as mksh)?/ ---- diff --git a/trap.c b/trap.c deleted file mode 100644 index dedd65c..0000000 --- a/trap.c +++ /dev/null @@ -1,426 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: trap.c,v 1.19 2004/12/22 17:47:03 millert Exp $ */ - -/* - * signal handling - */ - -/* Kludge to avoid bogus re-declaration of sigtraps[] error on AIX 3.2.5 */ -#define FROM_TRAP_C -#include "sh.h" - -__RCSID("$MirOS$"); - -Trap sigtraps[NSIG + 1]; - -static struct sigaction Sigact_ign, Sigact_trap; - -void -inittraps(void) -{ - int i; - - /* Populate sigtraps based on sys_signame and sys_siglist. */ - for (i = 0; i <= NSIG; i++) { - sigtraps[i].signal = i; - if (i == SIGERR_) { - sigtraps[i].name = "ERR"; - sigtraps[i].mess = "Error handler"; - } else { - sigtraps[i].name = sys_signame[i]; - sigtraps[i].mess = sys_siglist[i]; - } - } - sigtraps[SIGEXIT_].name = "EXIT"; /* our name for signal 0 */ - - sigemptyset(&Sigact_ign.sa_mask); - Sigact_ign.sa_flags = 0; /* interruptible */ - Sigact_ign.sa_handler = SIG_IGN; - Sigact_trap = Sigact_ign; - Sigact_trap.sa_handler = trapsig; - - sigtraps[SIGINT].flags |= TF_DFL_INTR | TF_TTY_INTR; - sigtraps[SIGQUIT].flags |= TF_DFL_INTR | TF_TTY_INTR; - sigtraps[SIGTERM].flags |= TF_DFL_INTR;/* not fatal for interactive */ - sigtraps[SIGHUP].flags |= TF_FATAL; - sigtraps[SIGCHLD].flags |= TF_SHELL_USES; - - /* these are always caught so we can clean up any temporary files. */ - setsig(&sigtraps[SIGINT], trapsig, SS_RESTORE_ORIG); - setsig(&sigtraps[SIGQUIT], trapsig, SS_RESTORE_ORIG); - setsig(&sigtraps[SIGTERM], trapsig, SS_RESTORE_ORIG); - setsig(&sigtraps[SIGHUP], trapsig, SS_RESTORE_ORIG); -} - -static RETSIGTYPE alarm_catcher(int sig); - -void -alarm_init(void) -{ - sigtraps[SIGALRM].flags |= TF_SHELL_USES; - setsig(&sigtraps[SIGALRM], alarm_catcher, - SS_RESTORE_ORIG|SS_FORCE|SS_SHTRAP); -} - -static RETSIGTYPE -alarm_catcher(int sig GCC_FUNC_ATTR(unused)) -{ - int errno_ = errno; - - if (ksh_tmout_state == TMOUT_READING) { - int left = alarm(0); - - if (left == 0) { - ksh_tmout_state = TMOUT_LEAVING; - intrsig = 1; - } else - alarm(left); - } - errno = errno_; - return RETSIGVAL; -} - -Trap * -gettrap(const char *name, int igncase) -{ - int i; - Trap *p; - - if (digit(*name)) { - int n; - - if (getn(name, &n) && 0 <= n && n < NSIG) - return &sigtraps[n]; - return NULL; - } - for (p = sigtraps, i = NSIG+1; --i >= 0; p++) - if (p->name) { - if (igncase) { - if (p->name && (!strcasecmp(p->name, name) || - (strlen(name) > 3 && !strncasecmp("SIG", - p->name, 3) && - !strcasecmp(p->name, name + 3)))) - return p; - } else { - if (p->name && (!strcmp(p->name, name) || - (strlen(name) > 3 && !strncmp("SIG", - p->name, 3) && !strcmp(p->name, name + 3)))) - return p; - } - } - return NULL; -} - -/* - * trap signal handler - */ -RETSIGTYPE -trapsig(int i) -{ - Trap *p = &sigtraps[i]; - int errno_ = errno; - - trap = p->set = 1; - if (p->flags & TF_DFL_INTR) - intrsig = 1; - if ((p->flags & TF_FATAL) && !p->trap) { - fatal_trap = 1; - intrsig = 1; - } - if (p->shtrap) - (*p->shtrap)(i); - errno = errno_; - return RETSIGVAL; -} - -/* called when we want to allow the user to ^C out of something - won't - * work if user has trapped SIGINT. - */ -void -intrcheck(void) -{ - if (intrsig) - runtraps(TF_DFL_INTR|TF_FATAL); -} - -/* called after EINTR to check if a signal with normally causes process - * termination has been received. - */ -int -fatal_trap_check(void) -{ - int i; - Trap *p; - - /* todo: should check if signal is fatal, not the TF_DFL_INTR flag */ - for (p = sigtraps, i = NSIG + 1; --i >= 0; p++) - if (p->set && (p->flags & (TF_DFL_INTR|TF_FATAL))) - /* return value is used as an exit code */ - return 128 + p->signal; - return 0; -} - -/* Returns the signal number of any pending traps: ie, a signal which has - * occurred for which a trap has been set or for which the TF_DFL_INTR flag - * is set. - */ -int -trap_pending(void) -{ - int i; - Trap *p; - - for (p = sigtraps, i = NSIG + 1; --i >= 0; p++) - if (p->set && ((p->trap && p->trap[0]) - || ((p->flags & (TF_DFL_INTR|TF_FATAL)) - && !p->trap))) - return p->signal; - return 0; -} - -/* - * run any pending traps. If intr is set, only run traps that - * can interrupt commands. - */ -void -runtraps(int flag) -{ - int i; - Trap *p; - - if (ksh_tmout_state == TMOUT_LEAVING) { - ksh_tmout_state = TMOUT_EXECUTING; - warningf(false, "timed out waiting for input"); - unwind(LEXIT); - } else - /* XXX: this means the alarm will have no effect if a trap - * is caught after the alarm() was started...not good. - */ - ksh_tmout_state = TMOUT_EXECUTING; - if (!flag) - trap = 0; - if (flag & TF_DFL_INTR) - intrsig = 0; - if (flag & TF_FATAL) - fatal_trap = 0; - for (p = sigtraps, i = NSIG + 1; --i >= 0; p++) - if (p->set && (!flag - || ((p->flags & flag) && p->trap == NULL))) - runtrap(p); -} - -void -runtrap(Trap *p) -{ - int i = p->signal; - char *trapstr = p->trap; - int oexstat; - int UNINITIALIZED(old_changed); - - p->set = 0; - if (trapstr == NULL) { /* SIG_DFL */ - if (p->flags & TF_FATAL) { - /* eg, SIGHUP */ - exstat = 128 + i; - unwind(LLEAVE); - } - if (p->flags & TF_DFL_INTR) { - /* eg, SIGINT, SIGQUIT, SIGTERM, etc. */ - exstat = 128 + i; - unwind(LINTR); - } - return; - } - if (trapstr[0] == '\0') /* SIG_IGN */ - return; - if (i == SIGEXIT_ || i == SIGERR_) { /* avoid recursion on these */ - old_changed = p->flags & TF_CHANGED; - p->flags &= ~TF_CHANGED; - p->trap = NULL; - } - oexstat = exstat; - /* Note: trapstr is fully parsed before anything is executed, thus - * no problem with afree(p->trap) in settrap() while still in use. - */ - command(trapstr); - exstat = oexstat; - if (i == SIGEXIT_ || i == SIGERR_) { - if (p->flags & TF_CHANGED) - /* don't clear TF_CHANGED */ - afree(trapstr, APERM); - else - p->trap = trapstr; - p->flags |= old_changed; - } -} - -/* clear pending traps and reset user's trap handlers; used after fork(2) */ -void -cleartraps(void) -{ - int i; - Trap *p; - - trap = 0; - intrsig = 0; - fatal_trap = 0; - for (i = NSIG + 1, p = sigtraps; --i >= 0; p++) { - p->set = 0; - if ((p->flags & TF_USER_SET) && (p->trap && p->trap[0])) - settrap(p, NULL); - } -} - -/* restore signals just before an exec(2) */ -void -restoresigs(void) -{ - int i; - Trap *p; - - for (i = NSIG + 1, p = sigtraps; --i >= 0; p++) - if (p->flags & (TF_EXEC_IGN|TF_EXEC_DFL)) - setsig(p, (p->flags & TF_EXEC_IGN) ? SIG_IGN : SIG_DFL, - SS_RESTORE_CURR|SS_FORCE); -} - -void -settrap(Trap *p, char *s) -{ - sig_t f; - - if (p->trap) - afree(p->trap, APERM); - p->trap = str_save(s, APERM); /* handles s == 0 */ - p->flags |= TF_CHANGED; - f = !s ? SIG_DFL : s[0] ? trapsig : SIG_IGN; - - p->flags |= TF_USER_SET; - if ((p->flags & (TF_DFL_INTR|TF_FATAL)) && f == SIG_DFL) - f = trapsig; - else if (p->flags & TF_SHELL_USES) { - if (!(p->flags & TF_ORIG_IGN) || Flag(FTALKING)) { - /* do what user wants at exec time */ - p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL); - if (f == SIG_IGN) - p->flags |= TF_EXEC_IGN; - else - p->flags |= TF_EXEC_DFL; - } - /* assumes handler already set to what shell wants it - * (normally trapsig, but could be j_sigchld() or SIG_IGN) - */ - return; - } - - /* todo: should we let user know signal is ignored? how? */ - setsig(p, f, SS_RESTORE_CURR|SS_USER); -} - -/* Called by c_print() when writing to a co-process to ensure SIGPIPE won't - * kill shell (unless user catches it and exits) - */ -int -block_pipe(void) -{ - int restore_dfl = 0; - Trap *p = &sigtraps[SIGPIPE]; - - if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) { - setsig(p, SIG_IGN, SS_RESTORE_CURR); - if (p->flags & TF_ORIG_DFL) - restore_dfl = 1; - } else if (p->cursig == SIG_DFL) { - setsig(p, SIG_IGN, SS_RESTORE_CURR); - restore_dfl = 1; /* restore to SIG_DFL */ - } - return restore_dfl; -} - -/* Called by c_print() to undo whatever block_pipe() did */ -void -restore_pipe(int restore_dfl) -{ - if (restore_dfl) - setsig(&sigtraps[SIGPIPE], SIG_DFL, SS_RESTORE_CURR); -} - -/* Set action for a signal. Action may not be set if original - * action was SIG_IGN, depending on the value of flags and - * FTALKING. - */ -int -setsig(Trap *p, sig_t f, int flags) -{ - struct sigaction sigact; - - if (p->signal == SIGEXIT_ || p->signal == SIGERR_) - return 1; - - /* First time setting this signal? If so, get and note the current - * setting. - */ - if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) { - sigaction(p->signal, &Sigact_ign, &sigact); - p->flags |= sigact.sa_handler == SIG_IGN ? - TF_ORIG_IGN : TF_ORIG_DFL; - p->cursig = SIG_IGN; - } - - /* Generally, an ignored signal stays ignored, except if - * - the user of an interactive shell wants to change it - * - the shell wants for force a change - */ - if ((p->flags & TF_ORIG_IGN) && !(flags & SS_FORCE) - && (!(flags & SS_USER) || !Flag(FTALKING))) - return 0; - - setexecsig(p, flags & SS_RESTORE_MASK); - - /* This is here 'cause there should be a way of clearing shtraps, but - * don't know if this is a sane way of doing it. At the moment, - * all users of shtrap are lifetime users (SIGCHLD, SIGALRM, SIGWINCH). - */ - if (!(flags & SS_USER)) - p->shtrap = NULL; - if (flags & SS_SHTRAP) { - p->shtrap = f; - f = trapsig; - } - - if (p->cursig != f) { - p->cursig = f; - sigemptyset(&sigact.sa_mask); - sigact.sa_flags = 0 /* interruptible */; - sigact.sa_handler = f; - sigaction(p->signal, &sigact, NULL); - } - - return 1; -} - -/* control what signal is set to before an exec() */ -void -setexecsig(Trap *p, int restore) -{ - /* XXX debugging */ - if (!(p->flags & (TF_ORIG_IGN|TF_ORIG_DFL))) - internal_errorf(1, "setexecsig: unset signal %d(%s)", - p->signal, p->name); - - /* restore original value for exec'd kids */ - p->flags &= ~(TF_EXEC_IGN|TF_EXEC_DFL); - switch (restore & SS_RESTORE_MASK) { - case SS_RESTORE_CURR: /* leave things as they currently are */ - break; - case SS_RESTORE_ORIG: - p->flags |= p->flags & TF_ORIG_IGN ? TF_EXEC_IGN : TF_EXEC_DFL; - break; - case SS_RESTORE_DFL: - p->flags |= TF_EXEC_DFL; - break; - case SS_RESTORE_IGN: - p->flags |= TF_EXEC_IGN; - break; - } -} diff --git a/tree.c b/tree.c deleted file mode 100644 index 5f637dd..0000000 --- a/tree.c +++ /dev/null @@ -1,695 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: tree.c,v 1.10 2002/02/27 19:37:09 dhartmei Exp $ */ - -/* - * command tree climbing - */ - -#include "sh.h" - -__RCSID("$MirOS$"); - -#define INDENT 4 - -#define tputc(c, shf) shf_putchar(c, shf); -static void ptree(struct op *t, int indent, struct shf *f); -static void pioact(struct shf *f, int indent, struct ioword *iop); -static void tputC(int c, struct shf *shf); -static void tputS(char *wp, struct shf *shf); -static void vfptreef(struct shf *shf, int indent, const char *fmt, va_list va); -static struct ioword **iocopy(struct ioword **iow, Area *ap); -static void iofree(struct ioword **iow, Area *ap); - -/* - * print a command tree - */ - -static void -ptree(struct op *t, int indent, struct shf *shf) -{ - char **w; - struct ioword **ioact; - struct op *t1; - - Chain: - if (t == NULL) - return; - switch (t->type) { - case TCOM: - if (t->vars) - for (w = t->vars; *w != NULL; ) - fptreef(shf, indent, "%S ", *w++); - else - fptreef(shf, indent, "#no-vars# "); - if (t->args) - for (w = t->args; *w != NULL; ) - fptreef(shf, indent, "%S ", *w++); - else - fptreef(shf, indent, "#no-args# "); - break; - case TEXEC: - t = t->left; - goto Chain; - case TPAREN: - fptreef(shf, indent + 2, "( %T) ", t->left); - break; - case TPIPE: - fptreef(shf, indent, "%T| ", t->left); - t = t->right; - goto Chain; - case TLIST: - fptreef(shf, indent, "%T%;", t->left); - t = t->right; - goto Chain; - case TOR: - case TAND: - fptreef(shf, indent, "%T%s %T", - t->left, (t->type==TOR) ? "||" : "&&", t->right); - break; - case TBANG: - fptreef(shf, indent, "! "); - t = t->right; - goto Chain; - case TDBRACKET: - { - int i; - - fptreef(shf, indent, "[["); - for (i = 0; t->args[i]; i++) - fptreef(shf, indent, " %S", t->args[i]); - fptreef(shf, indent, " ]] "); - break; - } - case TSELECT: - fptreef(shf, indent, "select %s ", t->str); - /* fall through */ - case TFOR: - if (t->type == TFOR) - fptreef(shf, indent, "for %s ", t->str); - if (t->vars != NULL) { - fptreef(shf, indent, "in "); - for (w = t->vars; *w; ) - fptreef(shf, indent, "%S ", *w++); - fptreef(shf, indent, "%;"); - } - fptreef(shf, indent + INDENT, "do%N%T", t->left); - fptreef(shf, indent, "%;done "); - break; - case TCASE: - fptreef(shf, indent, "case %S in", t->str); - for (t1 = t->left; t1 != NULL; t1 = t1->right) { - fptreef(shf, indent, "%N("); - for (w = t1->vars; *w != NULL; w++) - fptreef(shf, indent, "%S%c", *w, - (w[1] != NULL) ? '|' : ')'); - fptreef(shf, indent + INDENT, "%;%T%N;;", t1->left); - } - fptreef(shf, indent, "%Nesac "); - break; - case TIF: - case TELIF: - /* 3 == strlen("if ") */ - fptreef(shf, indent + 3, "if %T", t->left); - for (;;) { - t = t->right; - if (t->left != NULL) { - fptreef(shf, indent, "%;"); - fptreef(shf, indent + INDENT, "then%N%T", - t->left); - } - if (t->right == NULL || t->right->type != TELIF) - break; - t = t->right; - fptreef(shf, indent, "%;"); - /* 5 == strlen("elif ") */ - fptreef(shf, indent + 5, "elif %T", t->left); - } - if (t->right != NULL) { - fptreef(shf, indent, "%;"); - fptreef(shf, indent + INDENT, "else%;%T", t->right); - } - fptreef(shf, indent, "%;fi "); - break; - case TWHILE: - case TUNTIL: - /* 6 == strlen("while"/"until") */ - fptreef(shf, indent + 6, "%s %T", - (t->type==TWHILE) ? "while" : "until", - t->left); - fptreef(shf, indent, "%;do"); - fptreef(shf, indent + INDENT, "%;%T", t->right); - fptreef(shf, indent, "%;done "); - break; - case TBRACE: - fptreef(shf, indent + INDENT, "{%;%T", t->left); - fptreef(shf, indent, "%;} "); - break; - case TCOPROC: - fptreef(shf, indent, "%T|& ", t->left); - break; - case TASYNC: - fptreef(shf, indent, "%T& ", t->left); - break; - case TFUNCT: - fptreef(shf, indent, - t->u.ksh_func ? "function %s %T" : "%s() %T", - t->str, t->left); - break; - case TTIME: - fptreef(shf, indent, "time %T", t->left); - break; - default: - fptreef(shf, indent, ""); - break; - } - if ((ioact = t->ioact) != NULL) { - int need_nl = 0; - - while (*ioact != NULL) - pioact(shf, indent, *ioact++); - /* Print here documents after everything else... */ - for (ioact = t->ioact; *ioact != NULL; ) { - struct ioword *iop = *ioact++; - - /* heredoc is 0 when tracing (set -x) */ - if ((iop->flag & IOTYPE) == IOHERE && iop->heredoc) { - tputc('\n', shf); - shf_puts(iop->heredoc, shf); - fptreef(shf, indent, "%s", - evalstr(iop->delim, 0)); - need_nl = 1; - } - } - /* Last delimiter must be followed by a newline (this often - * leads to an extra blank line, but its not worth worrying - * about) - */ - if (need_nl) - tputc('\n', shf); - } -} - -static void -pioact(struct shf *shf, int indent, struct ioword *iop) -{ - int flag = iop->flag; - int type = flag & IOTYPE; - int expected; - - expected = (type == IOREAD || type == IORDWR || type == IOHERE) ? 0 - : (type == IOCAT || type == IOWRITE) ? 1 - : (type == IODUP && (iop->unit == !(flag & IORDUP))) ? - iop->unit - : iop->unit + 1; - if (iop->unit != expected) - tputc('0' + iop->unit, shf); - - switch (type) { - case IOREAD: - fptreef(shf, indent, "< "); - break; - case IOHERE: - if (flag&IOSKIP) - fptreef(shf, indent, "<<- "); - else - fptreef(shf, indent, "<< "); - break; - case IOCAT: - fptreef(shf, indent, ">> "); - break; - case IOWRITE: - if (flag&IOCLOB) - fptreef(shf, indent, ">| "); - else - fptreef(shf, indent, "> "); - break; - case IORDWR: - fptreef(shf, indent, "<> "); - break; - case IODUP: - if (flag & IORDUP) - fptreef(shf, indent, "<&"); - else - fptreef(shf, indent, ">&"); - break; - } - /* name/delim are 0 when printing syntax errors */ - if (type == IOHERE) { - if (iop->delim) - fptreef(shf, indent, "%S ", iop->delim); - } else if (iop->name) - fptreef(shf, indent, (iop->flag & IONAMEXP) ? "%s " : "%S ", - iop->name); -} - - -/* - * variants of fputc, fputs for ptreef and snptreef - */ - -static void -tputC(int c, struct shf *shf) -{ - if ((c&0x60) == 0) { /* C0|C1 */ - tputc((c&0x80) ? '$' : '^', shf); - tputc(((c&0x7F)|0x40), shf); - } else if ((c&0x7F) == 0x7F) { /* DEL */ - tputc((c&0x80) ? '$' : '^', shf); - tputc('?', shf); - } else - tputc(c, shf); -} - -static void -tputS(char *wp, struct shf *shf) -{ - int c, quoted=0; - - /* problems: - * `...` -> $(...) - * 'foo' -> "foo" - * could change encoding to: - * OQUOTE ["'] ... CQUOTE ["'] - * COMSUB [(`] ...\0 (handle $ ` \ and maybe " in `...` case) - */ - while (1) - switch ((c = *wp++)) { - case EOS: - return; - case CHAR: - tputC(*wp++, shf); - break; - case QCHAR: - c = *wp++; - if (!quoted || (c == '"' || c == '`' || c == '$')) - tputc('\\', shf); - tputC(c, shf); - break; - case COMSUB: - tputc('$', shf); - tputc('(', shf); - while (*wp != 0) - tputC(*wp++, shf); - tputc(')', shf); - wp++; - break; - case EXPRSUB: - tputc('$', shf); - tputc('(', shf); - tputc('(', shf); - while (*wp != 0) - tputC(*wp++, shf); - tputc(')', shf); - tputc(')', shf); - wp++; - break; - case OQUOTE: - quoted = 1; - tputc('"', shf); - break; - case CQUOTE: - quoted = 0; - tputc('"', shf); - break; - case OSUBST: - tputc('$', shf); - if (*wp++ == '{') - tputc('{', shf); - while ((c = *wp++) != 0) - tputC(c, shf); - break; - case CSUBST: - if (*wp++ == '}') - tputc('}', shf); - break; - case OPAT: - tputc(*wp++, shf); - tputc('(', shf); - break; - case SPAT: - tputc('|', shf); - break; - case CPAT: - tputc(')', shf); - break; - } -} - -/* - * this is the _only_ way to reliably handle - * variable args with an ANSI compiler - */ -/* VARARGS */ -int -fptreef(struct shf *shf, int indent, const char *fmt, ...) -{ - va_list va; - - SH_VA_START(va, fmt); - - vfptreef(shf, indent, fmt, va); - va_end(va); - return 0; -} - -/* VARARGS */ -char * -snptreef(char *s, int n, const char *fmt, ...) -{ - va_list va; - struct shf shf; - - shf_sopen(s, n, SHF_WR | (s ? 0 : SHF_DYNAMIC), &shf); - - SH_VA_START(va, fmt); - vfptreef(&shf, 0, fmt, va); - va_end(va); - - return shf_sclose(&shf); /* null terminates */ -} - -static void -vfptreef(struct shf *shf, int indent, const char *fmt, va_list va) -{ - int c; - - while ((c = *fmt++)) - if (c == '%') { - long n; - char *p; - int neg; - - switch ((c = *fmt++)) { - case 'c': - tputc(va_arg(va, int), shf); - break; - case 's': - p = va_arg(va, char *); - while (*p) - tputc(*p++, shf); - break; - case 'S': /* word */ - p = va_arg(va, char *); - tputS(p, shf); - break; - case 'd': case 'u': /* decimal */ - n = (c == 'd') ? (long)(va_arg(va, int)) - : (long)(va_arg(va, unsigned int)); - neg = (c == 'd') && (n < 0); - p = ulton((neg) ? -n : n, 10); - if (neg) - *--p = '-'; - while (*p) - tputc(*p++, shf); - break; - case 'T': /* format tree */ - ptree(va_arg(va, struct op *), indent, shf); - break; - case ';': /* newline or ; */ - case 'N': /* newline or space */ - if (shf->flags & SHF_STRING) { - if (c == ';') - tputc(';', shf); - tputc(' ', shf); - } else { - int i; - - tputc('\n', shf); - for (i = indent; i >= 8; i -= 8) - tputc('\t', shf); - for (; i > 0; --i) - tputc(' ', shf); - } - break; - case 'R': - pioact(shf, indent, va_arg(va, struct ioword *)); - break; - default: - tputc(c, shf); - break; - } - } else - tputc(c, shf); -} - -/* - * copy tree (for function definition) - */ - -struct op * -tcopy(struct op *t, Area *ap) -{ - struct op *r; - char **tw, **rw; - - if (t == NULL) - return NULL; - - r = (struct op *) alloc(sizeof(struct op), ap); - - r->type = t->type; - r->u.evalflags = t->u.evalflags; - - r->str = t->type == TCASE ? wdcopy(t->str, ap) : str_save(t->str, ap); - - if (t->vars == NULL) - r->vars = NULL; - else { - for (tw = t->vars; *tw++ != NULL; ) - ; - rw = r->vars = (char **) - alloc((tw - t->vars + 1) * sizeof(*tw), ap); - for (tw = t->vars; *tw != NULL; ) - *rw++ = wdcopy(*tw++, ap); - *rw = NULL; - } - - if (t->args == NULL) - r->args = NULL; - else { - for (tw = t->args; *tw++ != NULL; ) - ; - rw = r->args = (char **) - alloc((tw - t->args + 1) * sizeof(*tw), ap); - for (tw = t->args; *tw != NULL; ) - *rw++ = wdcopy(*tw++, ap); - *rw = NULL; - } - - r->ioact = (t->ioact == NULL) ? NULL : iocopy(t->ioact, ap); - - r->left = tcopy(t->left, ap); - r->right = tcopy(t->right, ap); - r->lineno = t->lineno; - - return r; -} - -char * -wdcopy(const char *wp, Area *ap) -{ - size_t len = wdscan(wp, EOS) - wp; - return memcpy(alloc(len, ap), wp, len); -} - -/* return the position of prefix c in wp plus 1 */ -char * -wdscan(const char *wp, int c) -{ - int nest = 0; - - while (1) - switch (*wp++) { - case EOS: - return (char *) wp; - case CHAR: - case QCHAR: - wp++; - break; - case COMSUB: - case EXPRSUB: - while (*wp++ != 0) - ; - break; - case OQUOTE: - case CQUOTE: - break; - case OSUBST: - nest++; - while (*wp++ != '\0') - ; - break; - case CSUBST: - wp++; - if (c == CSUBST && nest == 0) - return (char *) wp; - nest--; - break; - case OPAT: - nest++; - wp++; - break; - case SPAT: - case CPAT: - if (c == wp[-1] && nest == 0) - return (char *) wp; - if (wp[-1] == CPAT) - nest--; - break; - default: - internal_errorf(0, - "wdscan: unknown char 0x%x (carrying on)", - wp[-1]); - } -} - -/* return a copy of wp without any of the mark up characters and - * with quote characters (" ' \) stripped. - * (string is allocated from ATEMP) - */ -char * -wdstrip(const char *wp) -{ - struct shf shf; - int c; - - shf_sopen(NULL, 32, SHF_WR | SHF_DYNAMIC, &shf); - - /* problems: - * `...` -> $(...) - * x${foo:-"hi"} -> x${foo:-hi} - * x${foo:-'hi'} -> x${foo:-hi} - */ - while (1) - switch ((c = *wp++)) { - case EOS: - return shf_sclose(&shf); /* null terminates */ - case CHAR: - case QCHAR: - shf_putchar(*wp++, &shf); - break; - case COMSUB: - shf_putchar('$', &shf); - shf_putchar('(', &shf); - while (*wp != 0) - shf_putchar(*wp++, &shf); - shf_putchar(')', &shf); - break; - case EXPRSUB: - shf_putchar('$', &shf); - shf_putchar('(', &shf); - shf_putchar('(', &shf); - while (*wp != 0) - shf_putchar(*wp++, &shf); - shf_putchar(')', &shf); - shf_putchar(')', &shf); - break; - case OQUOTE: - break; - case CQUOTE: - break; - case OSUBST: - shf_putchar('$', &shf); - if (*wp++ == '{') - shf_putchar('{', &shf); - while ((c = *wp++) != 0) - shf_putchar(c, &shf); - break; - case CSUBST: - if (*wp++ == '}') - shf_putchar('}', &shf); - break; - case OPAT: - shf_putchar(*wp++, &shf); - shf_putchar('(', &shf); - break; - case SPAT: - shf_putchar('|', &shf); - break; - case CPAT: - shf_putchar(')', &shf); - break; - } -} - -static struct ioword ** -iocopy(struct ioword **iow, Area *ap) -{ - struct ioword **ior; - int i; - - for (ior = iow; *ior++ != NULL; ) - ; - ior = (struct ioword **) alloc((ior - iow + 1) * sizeof(*ior), ap); - - for (i = 0; iow[i] != NULL; i++) { - struct ioword *p, *q; - - p = iow[i]; - q = (struct ioword *) alloc(sizeof(*p), ap); - ior[i] = q; - *q = *p; - if (p->name != NULL) - q->name = wdcopy(p->name, ap); - if (p->delim != NULL) - q->delim = wdcopy(p->delim, ap); - if (p->heredoc != NULL) - q->heredoc = str_save(p->heredoc, ap); - } - ior[i] = NULL; - - return ior; -} - -/* - * free tree (for function definition) - */ - -void -tfree(struct op *t, Area *ap) -{ - char **w; - - if (t == NULL) - return; - - if (t->str != NULL) - afree((void*)t->str, ap); - - if (t->vars != NULL) { - for (w = t->vars; *w != NULL; w++) - afree((void*)*w, ap); - afree((void*)t->vars, ap); - } - - if (t->args != NULL) { - for (w = t->args; *w != NULL; w++) - afree((void*)*w, ap); - afree((void*)t->args, ap); - } - - if (t->ioact != NULL) - iofree(t->ioact, ap); - - tfree(t->left, ap); - tfree(t->right, ap); - - afree((void*)t, ap); -} - -static void -iofree(struct ioword **iow, Area *ap) -{ - struct ioword **iop; - struct ioword *p; - - for (iop = iow; (p = *iop++) != NULL; ) { - if (p->name != NULL) - afree((void*)p->name, ap); - if (p->delim != NULL) - afree((void*)p->delim, ap); - if (p->heredoc != NULL) - afree((void*)p->heredoc, ap); - afree((void*)p, ap); - } -} diff --git a/tree.h b/tree.h deleted file mode 100644 index 63a6494..0000000 --- a/tree.h +++ /dev/null @@ -1,146 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: tree.h,v 1.8 2003/10/22 07:40:38 jmc Exp $ */ -/* $From: tree.h,v 1.3 1994/05/31 13:34:34 michael Exp $ */ - -#ifndef TREE_H -#define TREE_H - -/* - * command trees for compile/execute - */ - -#define NOBLOCK ((struct op *)NULL) -#define NOWORD ((char *)NULL) -#define NOWORDS ((char **)NULL) - -/* - * Description of a command or an operation on commands. - */ -struct op { - short type; /* operation type, see below */ - union { /* WARNING: newtp(), tcopy() use evalflags = 0 to clear union */ - short evalflags; /* TCOM: arg expansion eval() flags */ - short ksh_func; /* TFUNC: function x (vs x()) */ - } u; - char **args; /* arguments to a command */ - char **vars; /* variable assignments */ - struct ioword **ioact; /* IO actions (eg, < > >>) */ - struct op *left, *right; /* descendents */ - char *str; /* word for case; identifier for for, - * select, and functions; - * path to execute for TEXEC; - * time hook for TCOM. - */ - int lineno; /* TCOM/TFUNC: LINENO for this */ -}; - -/* Tree.type values */ -#define TEOF 0 -#define TCOM 1 /* command */ -#define TPAREN 2 /* (c-list) */ -#define TPIPE 3 /* a | b */ -#define TLIST 4 /* a ; b */ -#define TOR 5 /* || */ -#define TAND 6 /* && */ -#define TBANG 7 /* ! */ -#define TDBRACKET 8 /* [[ .. ]] */ -#define TFOR 9 -#define TSELECT 10 -#define TCASE 11 -#define TIF 12 -#define TWHILE 13 -#define TUNTIL 14 -#define TELIF 15 -#define TPAT 16 /* pattern in case */ -#define TBRACE 17 /* {c-list} */ -#define TASYNC 18 /* c & */ -#define TFUNCT 19 /* function name { command; } */ -#define TTIME 20 /* time pipeline */ -#define TEXEC 21 /* fork/exec eval'd TCOM */ -#define TCOPROC 22 /* coprocess |& */ - -/* - * prefix codes for words in command tree - */ -#define EOS 0 /* end of string */ -#define CHAR 1 /* unquoted character */ -#define QCHAR 2 /* quoted character */ -#define COMSUB 3 /* $() substitution (0 terminated) */ -#define EXPRSUB 4 /* $(()) substitution (0 terminated) */ -#define OQUOTE 5 /* opening " or ' */ -#define CQUOTE 6 /* closing " or ' */ -#define OSUBST 7 /* opening ${ subst (followed by { or X) */ -#define CSUBST 8 /* closing } of above (followed by } or X) */ -#define OPAT 9 /* open pattern: *(, @(, etc. */ -#define SPAT 10 /* separate pattern: | */ -#define CPAT 11 /* close pattern: ) */ - -/* - * IO redirection - */ -struct ioword { - int unit; /* unit affected */ - int flag; /* action (below) */ - char *name; /* file name (unused if heredoc) */ - char *delim; /* delimiter for <<,<<- */ - char *heredoc;/* content of heredoc */ -}; - -/* ioword.flag - type of redirection */ -#define IOTYPE 0xF /* type: bits 0:3 */ -#define IOREAD 0x1 /* < */ -#define IOWRITE 0x2 /* > */ -#define IORDWR 0x3 /* <>: todo */ -#define IOHERE 0x4 /* << (here file) */ -#define IOCAT 0x5 /* >> */ -#define IODUP 0x6 /* <&/>& */ -#define IOEVAL BIT(4) /* expand in << */ -#define IOSKIP BIT(5) /* <<-, skip ^\t* */ -#define IOCLOB BIT(6) /* >|, override -o noclobber */ -#define IORDUP BIT(7) /* x<&y (as opposed to x>&y) */ -#define IONAMEXP BIT(8) /* name has been expanded */ - -/* execute/exchild flags */ -#define XEXEC BIT(0) /* execute without forking */ -#define XFORK BIT(1) /* fork before executing */ -#define XBGND BIT(2) /* command & */ -#define XPIPEI BIT(3) /* input is pipe */ -#define XPIPEO BIT(4) /* output is pipe */ -#define XPIPE (XPIPEI|XPIPEO) /* member of pipe */ -#define XXCOM BIT(5) /* `...` command */ -#define XPCLOSE BIT(6) /* exchild: close close_fd in parent */ -#define XCCLOSE BIT(7) /* exchild: close close_fd in child */ -#define XERROK BIT(8) /* non-zero exit ok (for set -e) */ -#define XCOPROC BIT(9) /* starting a co-process */ -#define XTIME BIT(10) /* timing TCOM command */ - -/* - * flags to control expansion of words (assumed by t->evalflags to fit - * in a short) - */ -#define DOBLANK BIT(0) /* perform blank interpretation */ -#define DOGLOB BIT(1) /* expand [?* */ -#define DOPAT BIT(2) /* quote *?[ */ -#define DOTILDE BIT(3) /* normal ~ expansion (first char) */ -#define DONTRUNCOMMAND BIT(4) /* do not run $(command) things */ -#define DOASNTILDE BIT(5) /* assignment ~ expansion (after =, :) */ -#define DOBRACE_ BIT(6) /* used by expand(): do brace expansion */ -#define DOMAGIC_ BIT(7) /* used by expand(): string contains MAGIC */ -#define DOTEMP_ BIT(8) /* ditto : in word part of ${..[%#=?]..} */ -#define DOVACHECK BIT(9) /* var assign check (for typeset, set, etc) */ -#define DOMARKDIRS BIT(10) /* force markdirs behaviour */ - -/* - * The arguments of [[ .. ]] expressions are kept in t->args[] and flags - * indicating how the arguments have been munged are kept in t->vars[]. - * The contents of t->vars[] are stuffed strings (so they can be treated - * like all other t->vars[]) in which the second character is the one that - * is examined. The DB_* defines are the values for these second characters. - */ -#define DB_NORM 1 /* normal argument */ -#define DB_OR 2 /* || -> -o conversion */ -#define DB_AND 3 /* && -> -a conversion */ -#define DB_BE 4 /* an inserted -BE */ -#define DB_PAT 5 /* a pattern argument */ - -#endif /* ndef TREE_H */ diff --git a/tty.c b/tty.c deleted file mode 100644 index e02366a..0000000 --- a/tty.c +++ /dev/null @@ -1,63 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: tty.c,v 1.5 2004/12/18 22:35:41 millert Exp $ */ - -#include "sh.h" -#include "ksh_stat.h" -#define EXTERN -#include "tty.h" -#undef EXTERN - -__RCSID("$MirOS$"); - -/* Initialize tty_fd. Used for saving/reseting tty modes upon - * foreground job completion and for setting up tty process group. - */ -void -tty_init(int init_ttystate) -{ - int do_close = 1; - int tfd; - - if (tty_fd >= 0) { - close(tty_fd); - tty_fd = -1; - } - tty_devtty = 1; - - if ((tfd = open("/dev/tty", O_RDWR, 0)) < 0) { - tty_devtty = 0; - warningf(false, - "No controlling tty (open /dev/tty: %s)", - strerror(errno)); - do_close = 0; - if (isatty(0)) - tfd = 0; - else if (isatty(2)) - tfd = 2; - else { - warningf(false, "Can't find tty file descriptor"); - return; - } - } - if ((tty_fd = ksh_dupbase(tfd, FDBASE)) < 0) { - warningf(false, "j_ttyinit: dup of tty fd failed: %s", - strerror(errno)); - } else if (fcntl(tty_fd, F_SETFD, FD_CLOEXEC) < 0) { - warningf(false, "j_ttyinit: can't set close-on-exec flag: %s", - strerror(errno)); - close(tty_fd); - tty_fd = -1; - } else if (init_ttystate) - tcgetattr(tty_fd, &tty_state); - if (do_close) - close(tfd); -} - -void -tty_close(void) -{ - if (tty_fd >= 0) { - close(tty_fd); - tty_fd = -1; - } -} diff --git a/tty.h b/tty.h deleted file mode 100644 index 3222604..0000000 --- a/tty.h +++ /dev/null @@ -1,44 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: tty.h,v 1.4 2004/12/18 22:12:23 millert Exp $ */ - -#ifndef TTY_H -#define TTY_H - -/* some useful #defines */ -#ifdef EXTERN -# define I__(i) = i -#else -# define I__(i) -# define EXTERN extern -# define EXTERN_DEFINED -#endif - -/* Don't know of a system on which including sys/ioctl.h with termios.h - * causes problems. If there is one, these lines need to be deleted and - * aclocal.m4 needs to have stuff un-commented. - */ -#ifdef SYS_IOCTL_WITH_TERMIOS -# define SYS_IOCTL_WITH_TERMIOS -#endif /* SYS_IOCTL_WITH_TERMIOS */ -#ifdef SYS_IOCTL_WITH_TERMIO -# define SYS_IOCTL_WITH_TERMIO -#endif /* SYS_IOCTL_WITH_TERMIO */ - -#include -typedef struct termios TTY_state; - -EXTERN int tty_fd I__(-1); /* dup'd tty file descriptor */ -EXTERN int tty_devtty; /* true if tty_fd is from /dev/tty */ -EXTERN struct termios tty_state; /* saved tty state */ - -extern void tty_init(int init_ttystate); -extern void tty_close(void); - -/* be sure not to interfere with anyone else's idea about EXTERN */ -#ifdef EXTERN_DEFINED -# undef EXTERN_DEFINED -# undef EXTERN -#endif -#undef I__ - -#endif /* ndef TTY_H */ diff --git a/var.c b/var.c deleted file mode 100644 index b5c1962..0000000 --- a/var.c +++ /dev/null @@ -1,1163 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: var.c,v 1.17 2004/05/08 19:42:35 deraadt Exp $ */ - -#include "sh.h" -#include -#include "ksh_stat.h" -#include - -__RCSID("$MirOS$"); - -/* - * Variables - * - * WARNING: unreadable code, needs a rewrite - * - * if (flag&INTEGER), val.i contains integer value, and type contains base. - * otherwise, (val.s + type) contains string value. - * if (flag&EXPORT), val.s contains "name=value" for E-Z exporting. - */ -static struct tbl vtemp; -static struct table specials; -static char *formatstr(struct tbl *vp, const char *s); -static void export(struct tbl *vp, const char *val); -static int special(const char *name); -static void unspecial(const char *name); -static void getspec(struct tbl *vp); -static void setspec(struct tbl *vp); -static void unsetspec(struct tbl *vp); -static struct tbl *arraysearch(struct tbl *, int); - -/* - * create a new block for function calls and simple commands - * assume caller has allocated and set up e->loc - */ -void -newblock(void) -{ - struct block *l; - static char *const empty[] = {null}; - - l = (struct block *) alloc(sizeof(struct block), ATEMP); - l->flags = 0; - ainit(&l->area); /* todo: could use e->area (l->area => l->areap) */ - if (!e->loc) { - l->argc = 0; - l->argv = (char **) empty; - } else { - l->argc = e->loc->argc; - l->argv = e->loc->argv; - } - l->exit = l->error = NULL; - tinit(&l->vars, &l->area, 0); - tinit(&l->funs, &l->area, 0); - l->next = e->loc; - e->loc = l; -} - -/* - * pop a block handling special variables - */ -void -popblock(void) -{ - struct block *l = e->loc; - struct tbl *vp, **vpp = l->vars.tbls, *vq; - int i; - - e->loc = l->next; /* pop block */ - for (i = l->vars.size; --i >= 0; ) - if ((vp = *vpp++) != NULL && (vp->flag&SPECIAL)) { - if ((vq = global(vp->name))->flag & ISSET) - setspec(vq); - else - unsetspec(vq); - } - if (l->flags & BF_DOGETOPTS) - user_opt = l->getopts_state; - afreeall(&l->area); - afree(l, ATEMP); -} - -/* called by main() to initialize variable data structures */ -void -initvar(void) -{ - static const struct { - const char *name; - int v; - } names[] = { - { "COLUMNS", V_COLUMNS }, - { "IFS", V_IFS }, - { "OPTIND", V_OPTIND }, - { "PATH", V_PATH }, - { "POSIXLY_CORRECT", V_POSIXLY_CORRECT }, - { "TMPDIR", V_TMPDIR }, - { "HISTFILE", V_HISTFILE }, - { "HISTSIZE", V_HISTSIZE }, - { "EDITOR", V_EDITOR }, - { "VISUAL", V_VISUAL }, - { "RANDOM", V_RANDOM }, - { "SECONDS", V_SECONDS }, - { "TMOUT", V_TMOUT }, - { "LINENO", V_LINENO }, - { "PGRP", V_PGRP }, - { NULL, 0 } - }; - int i; - struct tbl *tp; - - tinit(&specials, APERM, 32); /* must be 2^n (currently 17 specials) */ - for (i = 0; names[i].name; i++) { - tp = tenter(&specials, names[i].name, hash(names[i].name)); - tp->flag = DEFINED|ISSET; - tp->type = names[i].v; - } -} - -/* Used to calculate an array index for global()/local(). Sets *arrayp to - * non-zero if this is an array, sets *valp to the array index, returns - * the basename of the array. - */ -static const char *array_index_calc(const char *, bool *, int *); - -static const char * -array_index_calc(const char *n, bool *arrayp, int *valp) -{ - const char *p; - int len; - - *arrayp = false; - p = skip_varname(n, false); - if (p != n && *p == '[' && (len = array_ref_len(p))) { - char *sub, *tmp; - long rval; - - /* Calculate the value of the subscript */ - *arrayp = true; - tmp = str_nsave(p+1, len-2, ATEMP); - sub = substitute(tmp, 0); - afree(tmp, ATEMP); - n = str_nsave(n, p - n, ATEMP); - evaluate(sub, &rval, KSH_UNWIND_ERROR); - if (rval < 0 || rval > 2147483647) - errorf("%s: subscript out of range", n); - *valp = rval; - afree(sub, ATEMP); - } - return n; -} - -/* - * Search for variable, if not found create globally. - */ -struct tbl * -global(const char *n) -{ - struct block *l = e->loc; - struct tbl *vp; - int c; - unsigned h; - bool array; - int val; - - /* Check to see if this is an array */ - n = array_index_calc(n, &array, &val); - h = hash(n); - c = n[0]; - if (!letter(c)) { - if (array) - errorf("bad substitution"); - vp = &vtemp; - vp->flag = DEFINED; - vp->type = 0; - vp->areap = ATEMP; - *vp->name = c; - if (digit(c)) { - for (c = 0; digit(*n); n++) - c = c*10 + *n-'0'; - if (c <= l->argc) - /* setstr can't fail here */ - setstr(vp, l->argv[c], KSH_RETURN_ERROR); - vp->flag |= RDONLY; - return vp; - } - vp->flag |= RDONLY; - if (n[1] != '\0') - return vp; - vp->flag |= ISSET|INTEGER; - switch (c) { - case '$': - vp->val.i = kshpid; - break; - case '!': - /* If no job, expand to nothing */ - if ((vp->val.i = j_async()) == 0) - vp->flag &= ~(ISSET|INTEGER); - break; - case '?': - vp->val.i = exstat; - break; - case '#': - vp->val.i = l->argc; - break; - case '-': - vp->flag &= ~INTEGER; - vp->val.s = getoptions(); - break; - default: - vp->flag &= ~(ISSET|INTEGER); - } - return vp; - } - for (l = e->loc; ; l = l->next) { - vp = tsearch(&l->vars, n, h); - if (vp != NULL) { - if (array) - return arraysearch(vp, val); - else - return vp; - } - if (l->next == NULL) - break; - } - vp = tenter(&l->vars, n, h); - if (array) - vp = arraysearch(vp, val); - vp->flag |= DEFINED; - if (special(n)) - vp->flag |= SPECIAL; - return vp; -} - -/* - * Search for local variable, if not found create locally. - */ -struct tbl * -local(const char *n, bool copy) -{ - struct block *l = e->loc; - struct tbl *vp; - unsigned h; - bool array; - int val; - - /* Check to see if this is an array */ - n = array_index_calc(n, &array, &val); - h = hash(n); - if (!letter(*n)) { - vp = &vtemp; - vp->flag = DEFINED|RDONLY; - vp->type = 0; - vp->areap = ATEMP; - return vp; - } - vp = tenter(&l->vars, n, h); - if (copy && !(vp->flag & DEFINED)) { - struct block *ll = l; - struct tbl *vq = NULL; - - while ((ll = ll->next) && !(vq = tsearch(&ll->vars, n, h))) - ; - if (vq) { - vp->flag |= vq->flag & (EXPORT|INTEGER|RDONLY - |LJUST|RJUST|ZEROFIL - |LCASEV|UCASEV_AL|INT_U|INT_L); - if (vq->flag & INTEGER) - vp->type = vq->type; - vp->u2.field = vq->u2.field; - } - } - if (array) - vp = arraysearch(vp, val); - vp->flag |= DEFINED; - if (special(n)) - vp->flag |= SPECIAL; - return vp; -} - -/* get variable string value */ -char * -str_val(struct tbl *vp) -{ - char *s; - - if ((vp->flag&SPECIAL)) - getspec(vp); - if (!(vp->flag&ISSET)) - s = null; /* special to dollar() */ - else if (!(vp->flag&INTEGER)) /* string source */ - s = vp->val.s + vp->type; - else { /* integer source */ - /* worst case number length is when base=2, so use BITS(long) */ - /* minus base # number null */ - static char strbuf[1 + 2 + 1 + 8*sizeof(long) + 1]; - const char *digits = (vp->flag & UCASEV_AL) ? - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" - : "0123456789abcdefghijklmnopqrstuvwxyz"; - unsigned long n; - int base; - - s = strbuf + sizeof(strbuf); - if (vp->flag & INT_U) - n = (unsigned long) vp->val.i; - else - n = (vp->val.i < 0) ? -vp->val.i : vp->val.i; - base = (vp->type == 0) ? 10 : vp->type; - - *--s = '\0'; - do { - *--s = digits[n % base]; - n /= base; - } while (n != 0); - if (base != 10) { - *--s = '#'; - *--s = digits[base % 10]; - if (base >= 10) - *--s = digits[base / 10]; - } - if (!(vp->flag & INT_U) && vp->val.i < 0) - *--s = '-'; - if (vp->flag & (RJUST|LJUST)) /* case already dealt with */ - s = formatstr(vp, s); - } - return s; -} - -/* get variable integer value, with error checking */ -long -intval(struct tbl *vp) -{ - long num; - int base; - - base = getint(vp, &num); - if (base == -1) - /* XXX check calls - is error here ok by POSIX? */ - errorf("%s: bad number", str_val(vp)); - return num; -} - -/* set variable to string value */ -int -setstr(struct tbl *vq, const char *s, int error_ok) -{ - int no_ro_check = error_ok & 0x4; - error_ok &= ~0x4; - if ((vq->flag & RDONLY) && !no_ro_check) { - warningf(true, "%s: is read only", vq->name); - if (!error_ok) - errorf(null); - return 0; - } - if (!(vq->flag&INTEGER)) { /* string dest */ - if ((vq->flag&ALLOC)) { - /* debugging */ - if (s >= vq->val.s - && s <= vq->val.s + strlen(vq->val.s)) - internal_errorf(true, - "setstr: %s=%s: assigning to self", - vq->name, s); - afree((void*)vq->val.s, vq->areap); - } - vq->flag &= ~(ISSET|ALLOC); - vq->type = 0; - if (s && (vq->flag & (UCASEV_AL|LCASEV|LJUST|RJUST))) - s = formatstr(vq, s); - if ((vq->flag&EXPORT)) - export(vq, s); - else { - vq->val.s = str_save(s, vq->areap); - vq->flag |= ALLOC; - } - } else /* integer dest */ - if (!v_evaluate(vq, s, error_ok)) - return 0; - vq->flag |= ISSET; - if ((vq->flag&SPECIAL)) - setspec(vq); - return 1; -} - -/* set variable to integer */ -void -setint(struct tbl *vq, long int n) -{ - if (!(vq->flag&INTEGER)) { - struct tbl *vp = &vtemp; - vp->flag = (ISSET|INTEGER); - vp->type = 0; - vp->areap = ATEMP; - vp->val.i = n; - /* setstr can't fail here */ - setstr(vq, str_val(vp), KSH_RETURN_ERROR); - } else - vq->val.i = n; - vq->flag |= ISSET; - if ((vq->flag&SPECIAL)) - setspec(vq); -} - -int -getint(struct tbl *vp, long int *nump) -{ - char *s; - int c; - int base, neg; - int have_base = 0; - long num; - - if (vp->flag&SPECIAL) - getspec(vp); - /* XXX is it possible for ISSET to be set and val.s to be 0? */ - if (!(vp->flag&ISSET) || (!(vp->flag&INTEGER) && vp->val.s == NULL)) - return -1; - if (vp->flag&INTEGER) { - *nump = vp->val.i; - return vp->type; - } - s = vp->val.s + vp->type; - if (s == NULL) /* redundant given initial test */ - s = null; - base = 10; - num = 0; - neg = 0; - for (c = *s++; c ; c = *s++) { - if (c == '-') { - neg++; - } else if (c == '#') { - base = (int) num; - if (have_base || base < 2 || base > 36) - return -1; - num = 0; - have_base = 1; - } else if (letnum(c)) { - if (isdigit(c)) - c -= '0'; - else if (islower(c)) - c -= 'a' - 10; /* todo: assumes ascii */ - else if (isupper(c)) - c -= 'A' - 10; /* todo: assumes ascii */ - else - c = -1; /* _: force error */ - if (c < 0 || c >= base) - return -1; - num = num * base + c; - } else - return -1; - } - if (neg) - num = -num; - *nump = num; - return base; -} - -/* convert variable vq to integer variable, setting its value from vp - * (vq and vp may be the same) - */ -struct tbl * -setint_v(struct tbl *vq, struct tbl *vp) -{ - int base; - long num; - - if ((base = getint(vp, &num)) == -1) - return NULL; - if (!(vq->flag & INTEGER) && (vq->flag & ALLOC)) { - vq->flag &= ~ALLOC; - afree(vq->val.s, vq->areap); - } - vq->val.i = num; - if (vq->type == 0) /* default base */ - vq->type = base; - vq->flag |= ISSET|INTEGER; - if (vq->flag&SPECIAL) - setspec(vq); - return vq; -} - -static char * -formatstr(struct tbl *vp, const char *s) -{ - int olen, nlen; - char *p, *q; - - olen = strlen(s); - - if (vp->flag & (RJUST|LJUST)) { - if (!vp->u2.field) /* default field width */ - vp->u2.field = olen; - nlen = vp->u2.field; - } else - nlen = olen; - - p = (char *) alloc(nlen + 1, ATEMP); - if (vp->flag & (RJUST|LJUST)) { - int slen; - - if (vp->flag & RJUST) { - const char *q = s + olen; - /* strip trailing spaces (at&t ksh uses q[-1] == ' ') */ - while (q > s && isspace(q[-1])) - --q; - slen = q - s; - if (slen > vp->u2.field) { - s += slen - vp->u2.field; - slen = vp->u2.field; - } - shf_snprintf(p, nlen + 1, - ((vp->flag & ZEROFIL) && digit(*s)) ? - "%0*s%.*s" : "%*s%.*s", - vp->u2.field - slen, null, slen, s); - } else { - /* strip leading spaces/zeros */ - while (isspace(*s)) - s++; - if (vp->flag & ZEROFIL) - while (*s == '0') - s++; - shf_snprintf(p, nlen + 1, "%-*.*s", - vp->u2.field, vp->u2.field, s); - } - } else - memcpy(p, s, olen + 1); - - if (vp->flag & UCASEV_AL) { - for (q = p; *q; q++) - if (islower(*q)) - *q = toupper(*q); - } else if (vp->flag & LCASEV) { - for (q = p; *q; q++) - if (isupper(*q)) - *q = tolower(*q); - } - - return p; -} - -/* - * make vp->val.s be "name=value" for quick exporting. - */ -static void -export(struct tbl *vp, const char *val) -{ - char *xp; - char *op = (vp->flag&ALLOC) ? vp->val.s : NULL; - int namelen = strlen(vp->name); - int vallen = strlen(val) + 1; - - vp->flag |= ALLOC; - xp = (char*)alloc(namelen + 1 + vallen, vp->areap); - memcpy(vp->val.s = xp, vp->name, namelen); - xp += namelen; - *xp++ = '='; - vp->type = xp - vp->val.s; /* offset to value */ - memcpy(xp, val, vallen); - if (op != NULL) - afree((void*)op, vp->areap); -} - -/* - * lookup variable (according to (set&LOCAL)), - * set its attributes (INTEGER, RDONLY, EXPORT, TRACE, LJUST, RJUST, ZEROFIL, - * LCASEV, UCASEV_AL), and optionally set its value if an assignment. - */ -struct tbl * -typeset(const char *var, Tflag set, Tflag clr, int field, int base) -{ - struct tbl *vp; - struct tbl *vpbase, *t; - char *tvar; - const char *val; - - /* check for valid variable name, search for value */ - val = skip_varname(var, false); - if (val == var) - return NULL; - if (*val == '[') { - int len; - - len = array_ref_len(val); - if (len == 0) - return NULL; - /* IMPORT is only used when the shell starts up and is - * setting up its environment. Allow only simple array - * references at this time since parameter/command substitution - * is preformed on the [expression], which would be a major - * security hole. - */ - if (set & IMPORT) { - int i; - for (i = 1; i < len - 1; i++) - if (!digit(val[i])) - return NULL; - } - val += len; - } - if (*val == '=') - tvar = str_nsave(var, val++ - var, ATEMP); - else { - /* Importing from original environment: must have an = */ - if (set & IMPORT) - return NULL; - tvar = (char *) var; - val = NULL; - } - - /* Prevent typeset from creating a local PATH/ENV/SHELL */ - if (Flag(FRESTRICTED) && (strcmp(tvar, "PATH") == 0 - || strcmp(tvar, "ENV") == 0 - || strcmp(tvar, "SHELL") == 0)) - errorf("%s: restricted", tvar); - - vp = (set&LOCAL) ? local(tvar, (set & LOCAL_COPY) ? true : false) - : global(tvar); - set &= ~(LOCAL|LOCAL_COPY); - - vpbase = (vp->flag & ARRAY) ? global(arrayname(var)) : vp; - - /* only allow export flag to be set. at&t ksh allows any attribute to - * be changed, which means it can be truncated or modified - * (-L/-R/-Z/-i). - */ - if ((vpbase->flag&RDONLY) - && (val || clr || (set & ~EXPORT))) - /* XXX check calls - is error here ok by POSIX? */ - errorf("%s: is read only", tvar); - if (val) - afree(tvar, ATEMP); - - /* most calls are with set/clr == 0 */ - if (set | clr) { - int ok = 1; - /* XXX if x[0] isn't set, there will be problems: need to have - * one copy of attributes for arrays... - */ - for (t = vpbase; t; t = t->u.array) { - int fake_assign; - char UNINITIALIZED(*s); - char UNINITIALIZED(*free_me); - - fake_assign = (t->flag & ISSET) && (!val || t != vp) - && ((set & (UCASEV_AL|LCASEV|LJUST|RJUST|ZEROFIL)) - || ((t->flag & INTEGER) && (clr & INTEGER)) - || (!(t->flag & INTEGER) && (set & INTEGER))); - if (fake_assign) { - if (t->flag & INTEGER) { - s = str_val(t); - free_me = NULL; - } else { - s = t->val.s + t->type; - free_me = (t->flag & ALLOC) ? t->val.s - : NULL; - } - t->flag &= ~ALLOC; - } - if (!(t->flag & INTEGER) && (set & INTEGER)) { - t->type = 0; - t->flag &= ~ALLOC; - } - t->flag = (t->flag | set) & ~clr; - /* Don't change base if assignment is to be done, - * in case assignment fails. - */ - if ((set & INTEGER) && base > 0 && (!val || t != vp)) - t->type = base; - if (set & (LJUST|RJUST|ZEROFIL)) - t->u2.field = field; - if (fake_assign) { - if (!setstr(t, s, KSH_RETURN_ERROR)) { - /* Somewhat arbitrary action here: - * zap contents of variable, but keep - * the flag settings. - */ - ok = 0; - if (t->flag & INTEGER) - t->flag &= ~ISSET; - else { - if (t->flag & ALLOC) - afree((void*) t->val.s, - t->areap); - t->flag &= ~(ISSET|ALLOC); - t->type = 0; - } - } - if (free_me) - afree((void *) free_me, t->areap); - } - } - if (!ok) - errorf(null); - } - - if (val != NULL) { - if (vp->flag&INTEGER) { - /* do not zero base before assignment */ - setstr(vp, val, KSH_UNWIND_ERROR | 0x4); - /* Done after assignment to override default */ - if (base > 0) - vp->type = base; - } else - /* setstr can't fail (readonly check already done) */ - setstr(vp, val, KSH_RETURN_ERROR | 0x4); - } - - /* only x[0] is ever exported, so use vpbase */ - if ((vpbase->flag&EXPORT) && !(vpbase->flag&INTEGER) - && vpbase->type == 0) - export(vpbase, (vpbase->flag&ISSET) ? vpbase->val.s : null); - - return vp; -} - -/* Unset a variable. array_ref is set if there was an array reference in - * the name lookup (eg, x[2]). - */ -void -unset(struct tbl *vp, int array_ref) -{ - if (vp->flag & ALLOC) - afree((void*)vp->val.s, vp->areap); - if ((vp->flag & ARRAY) && !array_ref) { - struct tbl *a, *tmp; - - /* Free up entire array */ - for (a = vp->u.array; a; ) { - tmp = a; - a = a->u.array; - if (tmp->flag & ALLOC) - afree((void *) tmp->val.s, tmp->areap); - afree(tmp, tmp->areap); - } - vp->u.array = NULL; - } - /* If foo[0] is being unset, the remainder of the array is kept... */ - vp->flag &= SPECIAL | (array_ref ? ARRAY|DEFINED : 0); - if (vp->flag & SPECIAL) - unsetspec(vp); /* responsible for 'unspecial'ing var */ -} - -/* return a pointer to the first char past a legal variable name (returns the - * argument if there is no legal name, returns * a pointer to the terminating - * null if whole string is legal). - */ -char * -skip_varname(const char *s, int aok) -{ - int alen; - - if (s && letter(*s)) { - while (*++s && letnum(*s)) - ; - if (aok && *s == '[' && (alen = array_ref_len(s))) - s += alen; - } - return (char *) s; -} - -/* Return a pointer to the first character past any legal variable name. */ -char * -skip_wdvarname(const char *s, int aok) - - /* skip array de-reference? */ -{ - if (s[0] == CHAR && letter(s[1])) { - do - s += 2; - while (s[0] == CHAR && letnum(s[1])); - if (aok && s[0] == CHAR && s[1] == '[') { - /* skip possible array de-reference */ - const char *p = s; - char c; - int depth = 0; - - while (1) { - if (p[0] != CHAR) - break; - c = p[1]; - p += 2; - if (c == '[') - depth++; - else if (c == ']' && --depth == 0) { - s = p; - break; - } - } - } - } - return (char *) s; -} - -/* Check if coded string s is a variable name */ -int -is_wdvarname(const char *s, int aok) -{ - char *p = skip_wdvarname(s, aok); - - return p != s && p[0] == EOS; -} - -/* Check if coded string s is a variable assignment */ -int -is_wdvarassign(const char *s) -{ - char *p = skip_wdvarname(s, true); - - return p != s && p[0] == CHAR && p[1] == '='; -} - -/* - * Make the exported environment from the exported names in the dictionary. - */ -char ** -makenv(void) -{ - struct block *l = e->loc; - XPtrV env; - struct tbl *vp, **vpp; - int i; - - XPinit(env, 64); - for (l = e->loc; l != NULL; l = l->next) - for (vpp = l->vars.tbls, i = l->vars.size; --i >= 0; ) - if ((vp = *vpp++) != NULL - && (vp->flag&(ISSET|EXPORT)) == (ISSET|EXPORT)) { - struct block *l2; - struct tbl *vp2; - unsigned h = hash(vp->name); - - /* unexport any redefined instances */ - for (l2 = l->next; l2 != NULL; l2 = l2->next) { - vp2 = tsearch(&l2->vars, vp->name, h); - if (vp2 != NULL) - vp2->flag &= ~EXPORT; - } - if ((vp->flag&INTEGER)) { - /* integer to string */ - char *val; - val = str_val(vp); - vp->flag &= ~(INTEGER|RDONLY); - /* setstr can't fail here */ - setstr(vp, val, KSH_RETURN_ERROR); - } - XPput(env, vp->val.s); - } - XPput(env, NULL); - return (char **) XPclose(env); -} - -/* - * Called after a fork in parent to bump the random number generator. - */ -void -change_random(void) -{ - rnd_seed(time(NULL) * getpid()); -} - -/* - * handle special variables with side effects - PATH, SECONDS. - */ - -/* Test if name is a special parameter */ -static int -special(const char *name) -{ - struct tbl *tp; - - tp = tsearch(&specials, name, hash(name)); - return tp && (tp->flag & ISSET) ? tp->type : V_NONE; -} - -/* Make a variable non-special */ -static void -unspecial(const char *name) -{ - struct tbl *tp; - - tp = tsearch(&specials, name, hash(name)); - if (tp) - tdelete(tp); -} - -static time_t seconds; /* time SECONDS last set */ -static int user_lineno; /* what user set $LINENO to */ - -static void -getspec(struct tbl *vp) -{ - switch (special(vp->name)) { - case V_SECONDS: - vp->flag &= ~SPECIAL; - /* On start up the value of SECONDS is used before seconds - * has been set - don't do anything in this case - * (see initcoms[] in main.c). - */ - if (vp->flag & ISSET) - setint(vp, (long) (time(NULL) - seconds)); - vp->flag |= SPECIAL; - break; - case V_RANDOM: - vp->flag &= ~SPECIAL; - setint(vp, rnd_get()); - vp->flag |= SPECIAL; - break; - case V_HISTSIZE: - vp->flag &= ~SPECIAL; - setint(vp, (long) histsize); - vp->flag |= SPECIAL; - break; - case V_OPTIND: - vp->flag &= ~SPECIAL; - setint(vp, (long) user_opt.uoptind); - vp->flag |= SPECIAL; - break; - case V_LINENO: - vp->flag &= ~SPECIAL; - setint(vp, (long) current_lineno + user_lineno); - vp->flag |= SPECIAL; - break; - case V_PGRP: -#ifdef BSD_PGRP -#define getpgID() getpgrp(0) -#else -#define getpgID() getpgrp() -#endif - vp->flag &= ~SPECIAL; - setint(vp, getpgID()); - vp->flag |= SPECIAL; - break; - } -} - -static void -setspec(struct tbl *vp) -{ - char *s; - - switch (special(vp->name)) { - case V_PATH: - if (path) - afree(path, APERM); - path = str_save(str_val(vp), APERM); - flushcom(1); /* clear tracked aliases */ - break; - case V_IFS: - setctypes(s = str_val(vp), C_IFS); - ifs0 = *s; - break; - case V_OPTIND: - vp->flag &= ~SPECIAL; - getopts_reset((int) intval(vp)); - vp->flag |= SPECIAL; - break; - case V_POSIXLY_CORRECT: - change_flag(FPOSIX, OF_SPECIAL, 1); - break; - case V_TMPDIR: - if (tmpdir) { - afree(tmpdir, APERM); - tmpdir = NULL; - } - /* Use tmpdir iff it is an absolute path, is writable and - * searchable and is a directory... - */ - { - struct stat statb; - s = str_val(vp); - if (ISABSPATH(s) && eaccess(s, W_OK|X_OK) == 0 - && stat(s, &statb) == 0 && S_ISDIR(statb.st_mode)) - tmpdir = str_save(s, APERM); - } - break; - case V_HISTSIZE: - vp->flag &= ~SPECIAL; - sethistsize((int) intval(vp)); - vp->flag |= SPECIAL; - break; - case V_HISTFILE: - sethistfile(str_val(vp)); - break; - case V_VISUAL: - set_editmode(str_val(vp)); - break; - case V_EDITOR: - if (!(global("VISUAL")->flag & ISSET)) - set_editmode(str_val(vp)); - break; - case V_COLUMNS: - if ((x_cols = intval(vp)) <= MIN_COLS) - x_cols = MIN_COLS; - break; - case V_RANDOM: - vp->flag &= ~SPECIAL; - rnd_put(intval(vp)); - vp->flag |= SPECIAL; - break; - case V_SECONDS: - vp->flag &= ~SPECIAL; - seconds = time(NULL) - intval(vp); - vp->flag |= SPECIAL; - break; - case V_TMOUT: - /* at&t ksh seems to do this (only listen if integer) */ - if (vp->flag & INTEGER) - ksh_tmout = vp->val.i >= 0 ? vp->val.i : 0; - break; - case V_LINENO: - vp->flag &= ~SPECIAL; - /* The -1 is because line numbering starts at 1. */ - user_lineno = (unsigned int) intval(vp) - current_lineno - 1; - vp->flag |= SPECIAL; - break; - } -} - -static void -unsetspec(struct tbl *vp) -{ - switch (special(vp->name)) { - case V_PATH: - if (path) - afree(path, APERM); - path = str_save(def_path, APERM); - flushcom(1); /* clear tracked aliases */ - break; - case V_IFS: - setctypes(" \t\n", C_IFS); - ifs0 = ' '; - break; - case V_TMPDIR: - /* should not become unspecial */ - if (tmpdir) { - afree(tmpdir, APERM); - tmpdir = NULL; - } - break; - case V_LINENO: - case V_RANDOM: - case V_SECONDS: - case V_TMOUT: /* at&t ksh leaves previous value in place */ - unspecial(vp->name); - break; - - /* at&t ksh man page says OPTIND, OPTARG and _ lose special meaning, - * but OPTARG does not (still set by getopts) and _ is also still - * set in various places. - * Don't know what at&t does for: - * HISTSIZE, HISTFILE, - * Unsetting these in at&t ksh does not loose the 'specialness': - * no effect: IFS, COLUMNS, PATH, TMPDIR, - * VISUAL, EDITOR, - * pdkshisms: no effect: - * POSIXLY_CORRECT (use set +o posix instead) - */ - } -} - -/* - * Search for (and possibly create) a table entry starting with - * vp, indexed by val. - */ -static struct tbl * -arraysearch(struct tbl *vp, int val) -{ - struct tbl *prev, *curr, *new; - size_t namelen = strlen(vp->name) + 1; - - vp->flag |= ARRAY|DEFINED; - - /* The table entry is always [0] */ - if (val == 0) { - vp->index = 0; - return vp; - } - prev = vp; - curr = vp->u.array; - while (curr && curr->index < val) { - prev = curr; - curr = curr->u.array; - } - if (curr && curr->index == val) { - if (curr->flag&ISSET) - return curr; - else - new = curr; - } else - new = (struct tbl *)alloc(sizeof(struct tbl) + namelen, - vp->areap); - strlcpy(new->name, vp->name, namelen); - new->flag = vp->flag & ~(ALLOC|DEFINED|ISSET|SPECIAL); - new->type = vp->type; - new->areap = vp->areap; - new->u2.field = vp->u2.field; - new->index = val; - if (curr != new) { /* not reusing old array entry */ - prev->u.array = new; - new->u.array = curr; - } - return new; -} - -/* Return the length of an array reference (eg, [1+2]) - cp is assumed - * to point to the open bracket. Returns 0 if there is no matching closing - * bracket. - */ -int -array_ref_len(const char *cp) -{ - const char *s = cp; - int c; - int depth = 0; - - while ((c = *s++) && (c != ']' || --depth)) - if (c == '[') - depth++; - if (!c) - return 0; - return s - cp; -} - -/* - * Make a copy of the base of an array name - */ -char * -arrayname(const char *str) -{ - const char *p; - - if ((p = strchr(str, '[')) == 0) - /* Shouldn't happen, but why worry? */ - return (char *) str; - - return str_nsave(str, p - str, ATEMP); -} - -/* Set (or overwrite, if !reset) the array variable var to the values in vals. - */ -void -set_array(const char *var, int reset, char **vals) -{ - struct tbl *vp, *vq; - int i; - - /* to get local array, use "typeset foo; set -A foo" */ - vp = global(var); - - /* Note: at&t ksh allows set -A but not set +A of a read-only var */ - if ((vp->flag&RDONLY)) - errorf("%s: is read only", var); - /* This code is quite non-optimal */ - if (reset > 0) - /* trash existing values and attributes */ - unset(vp, 0); - /* todo: would be nice for assignment to completely succeed or - * completely fail. Only really effects integer arrays: - * evaluation of some of vals[] may fail... - */ - for (i = 0; vals[i]; i++) { - vq = arraysearch(vp, i); - /* would be nice to deal with errors here... (see above) */ - setstr(vq, vals[i], KSH_RETURN_ERROR); - } -} diff --git a/vi.c b/vi.c deleted file mode 100644 index e915585..0000000 --- a/vi.c +++ /dev/null @@ -1,2114 +0,0 @@ -/** $MirOS$ */ -/* $OpenBSD: vi.c,v 1.13 2004/05/10 16:28:47 pvalchev Exp $ */ - -/* - * vi command editing - * written by John Rochester (initially for nsh) - * bludgeoned to fit pdksh by Larry Bouzane, Jeff Sparkes & Eric Gisin - */ - -#include "config.h" - -#include "sh.h" -#include -#include "ksh_stat.h" /* completion */ -#include "edit.h" - -__RCSID("$MirOS$"); - -#define Ctrl(c) (c&0x1f) -#define is_wordch(c) (letnum(c)) - -struct edstate { - int winleft; - char *cbuf; - int cbufsize; - int linelen; - int cursor; -}; - - -static int vi_hook(int ch); -static void vi_reset(char *buf, size_t len); -static int nextstate(int ch); -static int vi_insert(int ch); -static int vi_cmd(int argcnt, const char *cmd); -static int domove(int argcnt, const char *cmd, int sub); -static int redo_insert(int count); -static void yank_range(int a, int b); -static int bracktype(int ch); -static void save_cbuf(void); -static void restore_cbuf(void); -static void edit_reset(char *buf, size_t len); -static int putbuf(const char *buf, int len, int repl); -static void del_range(int a, int b); -static int findch(int ch, int cnt, int forw, int incl); -static int forwword(int argcnt); -static int backword(int argcnt); -static int endword(int argcnt); -static int Forwword(int argcnt); -static int Backword(int argcnt); -static int Endword(int argcnt); -static int grabhist(int save, int n); -static int grabsearch(int save, int start, int fwd, char *pat); -static void redraw_line(int newline); -static void refresh(int leftside); -static int outofwin(void); -static void rewindow(void); -static int newcol(int ch, int col); -static void display(char *wb1, char *wb2, int leftside); -static void ed_mov_opt(int col, char *wb); -static int expand_word(int command); -static int complete_word(int command, int count); -static int print_expansions(struct edstate *e, int command); -static int char_len(int c); -static void x_vi_zotc(int c); -static void vi_pprompt(int full); -static void vi_error(void); -static void vi_macro_reset(void); -static int x_vi_putbuf(const char *s, size_t len); - -#define C_ 0x1 /* a valid command that isn't a M_, E_, U_ */ -#define M_ 0x2 /* movement command (h, l, etc.) */ -#define E_ 0x4 /* extended command (c, d, y) */ -#define X_ 0x8 /* long command (@, f, F, t, T, etc.) */ -#define U_ 0x10 /* an UN-undoable command (that isn't a M_) */ -#define B_ 0x20 /* bad command (^@) */ -#define Z_ 0x40 /* repeat count defaults to 0 (not 1) */ -#define S_ 0x80 /* search (/, ?) */ - -#define is_bad(c) (classify[(c)&0x7f]&B_) -#define is_cmd(c) (classify[(c)&0x7f]&(M_|E_|C_|U_)) -#define is_move(c) (classify[(c)&0x7f]&M_) -#define is_extend(c) (classify[(c)&0x7f]&E_) -#define is_long(c) (classify[(c)&0x7f]&X_) -#define is_undoable(c) (!(classify[(c)&0x7f]&U_)) -#define is_srch(c) (classify[(c)&0x7f]&S_) -#define is_zerocount(c) (classify[(c)&0x7f]&Z_) - -const unsigned char classify[128] = { - /* 0 1 2 3 4 5 6 7 */ - /* 0 ^@ ^A ^B ^C ^D ^E ^F ^G */ - B_, 0, 0, 0, 0, C_|U_, C_|Z_, 0, - /* 01 ^H ^I ^J ^K ^L ^M ^N ^O */ - M_, C_|Z_, 0, 0, C_|U_, 0, C_, 0, - /* 02 ^P ^Q ^R ^S ^T ^U ^V ^W */ - C_, 0, C_|U_, 0, 0, 0, C_, 0, - /* 03 ^X ^Y ^Z ^[ ^\ ^] ^^ ^_ */ - C_, 0, 0, C_|Z_, 0, 0, 0, 0, - /* 04 ! " # $ % & ' */ - M_, 0, 0, C_, M_, M_, 0, 0, - /* 05 ( ) * + , - . / */ - 0, 0, C_, C_, M_, C_, 0, C_|S_, - /* 06 0 1 2 3 4 5 6 7 */ - M_, 0, 0, 0, 0, 0, 0, 0, - /* 07 8 9 : ; < = > ? */ - 0, 0, 0, M_, 0, C_, 0, C_|S_, - /* 010 @ A B C D E F G */ - C_|X_, C_, M_, C_, C_, M_, M_|X_, C_|U_|Z_, - /* 011 H I J K L M N O */ - 0, C_, 0, 0, 0, 0, C_|U_, 0, - /* 012 P Q R S T U V W */ - C_, 0, C_, C_, M_|X_, C_, 0, M_, - /* 013 X Y Z [ \ ] ^ _ */ - C_, C_|U_, 0, 0, C_|Z_, 0, M_, C_|Z_, - /* 014 ` a b c d e f g */ - 0, C_, M_, E_, E_, M_, M_|X_, C_|Z_, - /* 015 h i j k l m n o */ - M_, C_, C_|U_, C_|U_, M_, 0, C_|U_, 0, - /* 016 p q r s t u v w */ - C_, 0, X_, C_, M_|X_, C_|U_, C_|U_|Z_,M_, - /* 017 x y z { | } ~ ^? */ - C_, E_|U_, 0, 0, M_|Z_, 0, C_, 0 -}; - -#define MAXVICMD 3 -#define SRCHLEN 40 - -#define INSERT 1 -#define REPLACE 2 - -#define VNORMAL 0 /* command, insert or replace mode */ -#define VARG1 1 /* digit prefix (first, eg, 5l) */ -#define VEXTCMD 2 /* cmd + movement (eg, cl) */ -#define VARG2 3 /* digit prefix (second, eg, 2c3l) */ -#define VXCH 4 /* f, F, t, T, @ */ -#define VFAIL 5 /* bad command */ -#define VCMD 6 /* single char command (eg, X) */ -#define VREDO 7 /* . */ -#define VLIT 8 /* ^V */ -#define VSEARCH 9 /* /, ? */ -#define VVERSION 10 /* ^V */ - -static char undocbuf[LINE]; - -static struct edstate *save_edstate(struct edstate *old); -static void restore_edstate(struct edstate *old, struct edstate *new); -static void free_edstate(struct edstate *old); - -static struct edstate ebuf; -static struct edstate undobuf = { 0, undocbuf, LINE, 0, 0 }; - -static struct edstate *es; /* current editor state */ -static struct edstate *undo; - -static char ibuf[LINE]; /* input buffer */ -static int first_insert; /* set when starting in insert mode */ -static int saved_inslen; /* saved inslen for first insert */ -static int inslen; /* length of input buffer */ -static int srchlen; /* length of current search pattern */ -static char ybuf[LINE]; /* yank buffer */ -static int yanklen; /* length of yank buffer */ -static int fsavecmd = ' '; /* last find command */ -static int fsavech; /* character to find */ -static char lastcmd[MAXVICMD]; /* last non-move command */ -static int lastac; /* argcnt for lastcmd */ -static int lastsearch = ' '; /* last search command */ -static char srchpat[SRCHLEN]; /* last search pattern */ -static int insert; /* non-zero in insert mode */ -static int hnum; /* position in history */ -static int ohnum; /* history line copied (after mod) */ -static int hlast; /* 1 past last position in history */ -static int modified; /* buffer has been "modified" */ -static int state; - -/* Information for keeping track of macros that are being expanded. - * The format of buf is the alias contents followed by a null byte followed - * by the name (letter) of the alias. The end of the buffer is marked by - * a double null. The name of the alias is stored so recursive macros can - * be detected. - */ -struct macro_state { - unsigned char *p; /* current position in buf */ - unsigned char *buf; /* pointer to macro(s) being expanded */ - int len; /* how much data in buffer */ -}; -static struct macro_state macro; - -enum expand_mode { NONE, EXPAND, COMPLETE, PRINT }; -static enum expand_mode expanded = NONE;/* last input was expanded */ - -int -x_vi(char *buf, size_t len) -{ - int c; - - vi_reset(buf, len > LINE ? LINE : len); - vi_pprompt(1); - x_flush(); - while (1) { - if (macro.p) { - c = *macro.p++; - /* end of current macro? */ - if (!c) { - /* more macros left to finish? */ - if (*macro.p++) - continue; - /* must be the end of all the macros */ - vi_macro_reset(); - c = x_getc(); - } - } else { - c = x_getc(); - } - if (c == -1) - break; - if (state != VLIT) { - if (c == edchars.intr || c == edchars.quit) { - /* pretend we got an interrupt */ - x_vi_zotc(c); - x_flush(); - trapsig(c == edchars.intr ? SIGINT : SIGQUIT); - x_mode(false); - unwind(LSHELL); - } else if (c == edchars.eof && state != VVERSION) { - if (es->linelen == 0) { - x_vi_zotc(edchars.eof); - c = -1; - break; - } - continue; - } - } - if (vi_hook(c)) - break; - x_flush(); - } - - x_putc('\r'); x_putc('\n'); x_flush(); - - if (c == -1 || len <= (size_t)es->linelen) - return -1; - - if (es->cbuf != buf) - memmove(buf, es->cbuf, es->linelen); - - buf[es->linelen++] = '\n'; - - return es->linelen; -} - -static int -vi_hook(int ch) -{ - static char curcmd[MAXVICMD]; - static char locpat[SRCHLEN]; - static int cmdlen; - static int argc1, argc2; - - switch (state) { - - case VNORMAL: - if (insert != 0) { - if (ch == Ctrl('v')) { - state = VLIT; - ch = '^'; - } - switch (vi_insert(ch)) { - case -1: - vi_error(); - state = VNORMAL; - break; - case 0: - if (state == VLIT) { - es->cursor--; - refresh(0); - } else - refresh(insert != 0); - break; - case 1: - return 1; - } - } else { - if (ch == '\r' || ch == '\n') - return 1; - cmdlen = 0; - argc1 = 0; - if (ch >= '1' && ch <= '9') { - argc1 = ch - '0'; - state = VARG1; - } else { - curcmd[cmdlen++] = ch; - state = nextstate(ch); - if (state == VSEARCH) { - save_cbuf(); - es->cursor = 0; - es->linelen = 0; - if (ch == '/') { - if (putbuf("/", 1, 0) != 0) { - return -1; - } - } else if (putbuf("?", 1, 0) != 0) - return -1; - refresh(0); - } - if (state == VVERSION) { - save_cbuf(); - es->cursor = 0; - es->linelen = 0; - putbuf(ksh_version + 4, - strlen(ksh_version + 4), 0); - refresh(0); - } - } - } - break; - - case VLIT: - if (is_bad(ch)) { - del_range(es->cursor, es->cursor + 1); - vi_error(); - } else - es->cbuf[es->cursor++] = ch; - refresh(1); - state = VNORMAL; - break; - - case VVERSION: - restore_cbuf(); - state = VNORMAL; - refresh(0); - break; - - case VARG1: - if (isdigit(ch)) - argc1 = argc1 * 10 + ch - '0'; - else { - curcmd[cmdlen++] = ch; - state = nextstate(ch); - } - break; - - case VEXTCMD: - argc2 = 0; - if (ch >= '1' && ch <= '9') { - argc2 = ch - '0'; - state = VARG2; - return 0; - } else { - curcmd[cmdlen++] = ch; - if (ch == curcmd[0]) - state = VCMD; - else if (is_move(ch)) - state = nextstate(ch); - else - state = VFAIL; - } - break; - - case VARG2: - if (isdigit(ch)) - argc2 = argc2 * 10 + ch - '0'; - else { - if (argc1 == 0) - argc1 = argc2; - else - argc1 *= argc2; - curcmd[cmdlen++] = ch; - if (ch == curcmd[0]) - state = VCMD; - else if (is_move(ch)) - state = nextstate(ch); - else - state = VFAIL; - } - break; - - case VXCH: - if (ch == Ctrl('[')) - state = VNORMAL; - else { - curcmd[cmdlen++] = ch; - state = VCMD; - } - break; - - case VSEARCH: - if (ch == '\r' || ch == '\n' /*|| ch == Ctrl('[')*/ ) { - restore_cbuf(); - /* Repeat last search? */ - if (srchlen == 0) { - if (!srchpat[0]) { - vi_error(); - state = VNORMAL; - refresh(0); - return 0; - } - } else { - locpat[srchlen] = '\0'; - (void) strlcpy(srchpat, locpat, sizeof srchpat); - } - state = VCMD; - } else if (ch == edchars.erase || ch == Ctrl('h')) { - if (srchlen != 0) { - srchlen--; - es->linelen -= char_len((unsigned char) locpat[srchlen]); - es->cursor = es->linelen; - refresh(0); - return 0; - } - restore_cbuf(); - state = VNORMAL; - refresh(0); - } else if (ch == edchars.kill) { - srchlen = 0; - es->linelen = 1; - es->cursor = 1; - refresh(0); - return 0; - } else if (ch == edchars.werase) { - int i; - int n = srchlen; - - while (n > 0 && isspace(locpat[n - 1])) - n--; - while (n > 0 && !isspace(locpat[n - 1])) - n--; - for (i = srchlen; --i >= n; ) - es->linelen -= char_len((unsigned char) locpat[i]); - srchlen = n; - es->cursor = es->linelen; - refresh(0); - return 0; - } else { - if (srchlen == SRCHLEN - 1) - vi_error(); - else { - locpat[srchlen++] = ch; - if ((ch & 0x80) && Flag(FVISHOW8)) { - if (es->linelen + 2 > es->cbufsize) - vi_error(); - es->cbuf[es->linelen++] = 'M'; - es->cbuf[es->linelen++] = '-'; - ch &= 0x7f; - } - if (ch < ' ' || ch == 0x7f) { - if (es->linelen + 2 > es->cbufsize) - vi_error(); - es->cbuf[es->linelen++] = '^'; - es->cbuf[es->linelen++] = ch ^ '@'; - } else { - if (es->linelen >= es->cbufsize) - vi_error(); - es->cbuf[es->linelen++] = ch; - } - es->cursor = es->linelen; - refresh(0); - } - return 0; - } - break; - } - - switch (state) { - case VCMD: - state = VNORMAL; - switch (vi_cmd(argc1, curcmd)) { - case -1: - vi_error(); - refresh(0); - break; - case 0: - if (insert != 0) - inslen = 0; - refresh(insert != 0); - break; - case 1: - refresh(0); - return 1; - case 2: - /* back from a 'v' command - don't redraw the screen */ - return 1; - } - break; - - case VREDO: - state = VNORMAL; - if (argc1 != 0) - lastac = argc1; - switch (vi_cmd(lastac, lastcmd)) { - case -1: - vi_error(); - refresh(0); - break; - case 0: - if (insert != 0) { - if (lastcmd[0] == 's' || lastcmd[0] == 'c' || - lastcmd[0] == 'C') { - if (redo_insert(1) != 0) - vi_error(); - } else { - if (redo_insert(lastac) != 0) - vi_error(); - } - } - refresh(0); - break; - case 1: - refresh(0); - return 1; - case 2: - /* back from a 'v' command - can't happen */ - break; - } - break; - - case VFAIL: - state = VNORMAL; - vi_error(); - break; - } - return 0; -} - -static void -vi_reset(char *buf, size_t len) -{ - state = VNORMAL; - ohnum = hnum = hlast = histnum(-1) + 1; - insert = INSERT; - saved_inslen = inslen; - first_insert = 1; - inslen = 0; - modified = 1; - vi_macro_reset(); - edit_reset(buf, len); -} - -static int -nextstate(int ch) -{ - if (is_extend(ch)) - return VEXTCMD; - else if (is_srch(ch)) - return VSEARCH; - else if (is_long(ch)) - return VXCH; - else if (ch == '.') - return VREDO; - else if (ch == Ctrl('v')) - return VVERSION; - else if (is_cmd(ch)) - return VCMD; - else - return VFAIL; -} - -static int -vi_insert(int ch) -{ - int tcursor; - - if (ch == edchars.erase || ch == Ctrl('h')) { - if (insert == REPLACE) { - if (es->cursor == undo->cursor) { - vi_error(); - return 0; - } - if (inslen > 0) - inslen--; - es->cursor--; - if (es->cursor >= undo->linelen) - es->linelen--; - else - es->cbuf[es->cursor] = undo->cbuf[es->cursor]; - } else { - if (es->cursor == 0) { - /* x_putc(BEL); no annoying bell here */ - return 0; - } - if (inslen > 0) - inslen--; - es->cursor--; - es->linelen--; - memmove(&es->cbuf[es->cursor], &es->cbuf[es->cursor+1], - es->linelen - es->cursor + 1); - } - expanded = NONE; - return 0; - } - if (ch == edchars.kill) { - if (es->cursor != 0) { - inslen = 0; - memmove(es->cbuf, &es->cbuf[es->cursor], - es->linelen - es->cursor); - es->linelen -= es->cursor; - es->cursor = 0; - } - expanded = NONE; - return 0; - } - if (ch == edchars.werase) { - if (es->cursor != 0) { - tcursor = Backword(1); - memmove(&es->cbuf[tcursor], &es->cbuf[es->cursor], - es->linelen - es->cursor); - es->linelen -= es->cursor - tcursor; - if (inslen < es->cursor - tcursor) - inslen = 0; - else - inslen -= es->cursor - tcursor; - es->cursor = tcursor; - } - expanded = NONE; - return 0; - } - /* If any chars are entered before escape, trash the saved insert - * buffer (if user inserts & deletes char, ibuf gets trashed and - * we don't want to use it) - */ - if (first_insert && ch != Ctrl('[')) - saved_inslen = 0; - switch (ch) { - - case '\0': - return -1; - - case '\r': - case '\n': - return 1; - - case Ctrl('['): - expanded = NONE; - if (first_insert) { - first_insert = 0; - if (inslen == 0) { - inslen = saved_inslen; - return redo_insert(0); - } - lastcmd[0] = 'a'; - lastac = 1; - } - if (lastcmd[0] == 's' || lastcmd[0] == 'c' || - lastcmd[0] == 'C') - return redo_insert(0); - else - return redo_insert(lastac - 1); - - /* { Begin nonstandard vi commands */ - case Ctrl('x'): - expand_word(0); - break; - - case Ctrl('f'): - complete_word(0, 0); - break; - - case Ctrl('e'): - print_expansions(es, 0); - break; - - case Ctrl('i'): - if (Flag(FVITABCOMPLETE)) { - complete_word(0, 0); - break; - } - /* FALLTHROUGH */ - /* End nonstandard vi commands } */ - - default: - if (es->linelen >= es->cbufsize - 1) - return -1; - ibuf[inslen++] = ch; - if (insert == INSERT) { - memmove(&es->cbuf[es->cursor+1], &es->cbuf[es->cursor], - es->linelen - es->cursor); - es->linelen++; - } - es->cbuf[es->cursor++] = ch; - if (insert == REPLACE && es->cursor > es->linelen) - es->linelen++; - expanded = NONE; - } - return 0; -} - -static int -vi_cmd(int argcnt, const char *cmd) -{ - int ncursor; - int cur, c1, c2, c3 = 0; - int any; - struct edstate *t; - - if (argcnt == 0 && !is_zerocount(*cmd)) - argcnt = 1; - - if (is_move(*cmd)) { - if ((cur = domove(argcnt, cmd, 0)) >= 0) { - if (cur == es->linelen && cur != 0) - cur--; - es->cursor = cur; - } else - return -1; - } else { - /* Don't save state in middle of macro.. */ - if (is_undoable(*cmd) && !macro.p) { - undo->winleft = es->winleft; - memmove(undo->cbuf, es->cbuf, es->linelen); - undo->linelen = es->linelen; - undo->cursor = es->cursor; - lastac = argcnt; - memmove(lastcmd, cmd, MAXVICMD); - } - switch (*cmd) { - - case Ctrl('l'): - case Ctrl('r'): - redraw_line(1); - break; - - case '@': - { - static char alias[] = "_\0"; - struct tbl *ap; - int olen, nlen; - char *p, *nbuf; - - /* lookup letter in alias list... */ - alias[1] = cmd[1]; - ap = tsearch(&aliases, alias, hash(alias)); - if (!cmd[1] || !ap || !(ap->flag & ISSET)) - return -1; - /* check if this is a recursive call... */ - if ((p = (char *) macro.p)) - while ((p = strchr(p, '\0')) && p[1]) - if (*++p == cmd[1]) - return -1; - /* insert alias into macro buffer */ - nlen = strlen(ap->val.s) + 1; - olen = !macro.p ? 2 - : macro.len - (macro.p - macro.buf); - nbuf = alloc(nlen + 1 + olen, APERM); - memcpy(nbuf, ap->val.s, nlen); - nbuf[nlen++] = cmd[1]; - if (macro.p) { - memcpy(nbuf + nlen, macro.p, olen); - afree(macro.buf, APERM); - nlen += olen; - } else { - nbuf[nlen++] = '\0'; - nbuf[nlen++] = '\0'; - } - macro.p = macro.buf = (unsigned char *) nbuf; - macro.len = nlen; - } - break; - - case 'a': - modified = 1; hnum = hlast; - if (es->linelen != 0) - es->cursor++; - insert = INSERT; - break; - - case 'A': - modified = 1; hnum = hlast; - del_range(0, 0); - es->cursor = es->linelen; - insert = INSERT; - break; - - case 'S': - es->cursor = domove(1, "^", 1); - del_range(es->cursor, es->linelen); - modified = 1; hnum = hlast; - insert = INSERT; - break; - - case 'Y': - cmd = "y$"; - /* ahhhhhh... */ - case 'c': - case 'd': - case 'y': - if (*cmd == cmd[1]) { - c1 = *cmd == 'c' ? domove(1, "^", 1) : 0; - c2 = es->linelen; - } else if (!is_move(cmd[1])) - return -1; - else { - if ((ncursor = domove(argcnt, &cmd[1], 1)) < 0) - return -1; - if (*cmd == 'c' && - (cmd[1]=='w' || cmd[1]=='W') && - !isspace(es->cbuf[es->cursor])) { - while (isspace(es->cbuf[--ncursor])) - ; - ncursor++; - } - if (ncursor > es->cursor) { - c1 = es->cursor; - c2 = ncursor; - } else { - c1 = ncursor; - c2 = es->cursor; - if (cmd[1] == '%') - c2++; - } - } - if (*cmd != 'c' && c1 != c2) - yank_range(c1, c2); - if (*cmd != 'y') { - del_range(c1, c2); - es->cursor = c1; - } - if (*cmd == 'c') { - modified = 1; hnum = hlast; - insert = INSERT; - } - break; - - case 'p': - modified = 1; hnum = hlast; - if (es->linelen != 0) - es->cursor++; - while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0) - ; - if (es->cursor != 0) - es->cursor--; - if (argcnt != 0) - return -1; - break; - - case 'P': - modified = 1; hnum = hlast; - any = 0; - while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0) - any = 1; - if (any && es->cursor != 0) - es->cursor--; - if (argcnt != 0) - return -1; - break; - - case 'C': - modified = 1; hnum = hlast; - del_range(es->cursor, es->linelen); - insert = INSERT; - break; - - case 'D': - yank_range(es->cursor, es->linelen); - del_range(es->cursor, es->linelen); - if (es->cursor != 0) - es->cursor--; - break; - - case 'g': - if (!argcnt) - argcnt = hlast + 1; - /* fall through */ - case 'G': - if (!argcnt) - argcnt = 1; - else - argcnt = hlast - (source->line - argcnt); - if (grabhist(modified, argcnt - 1) < 0) - return -1; - else { - modified = 0; - hnum = argcnt - 1; - } - break; - - case 'i': - modified = 1; hnum = hlast; - insert = INSERT; - break; - - case 'I': - modified = 1; hnum = hlast; - es->cursor = domove(1, "^", 1); - insert = INSERT; - break; - - case 'j': - case '+': - case Ctrl('n'): - if (grabhist(modified, hnum + argcnt) < 0) - return -1; - else { - modified = 0; - hnum += argcnt; - } - break; - - case 'k': - case '-': - case Ctrl('p'): - if (grabhist(modified, hnum - argcnt) < 0) - return -1; - else { - modified = 0; - hnum -= argcnt; - } - break; - - case 'r': - if (es->linelen == 0) - return -1; - modified = 1; hnum = hlast; - if (cmd[1] == 0) - vi_error(); - else - es->cbuf[es->cursor] = cmd[1]; - break; - - case 'R': - modified = 1; hnum = hlast; - insert = REPLACE; - break; - - case 's': - if (es->linelen == 0) - return -1; - modified = 1; hnum = hlast; - if (es->cursor + argcnt > es->linelen) - argcnt = es->linelen - es->cursor; - del_range(es->cursor, es->cursor + argcnt); - insert = INSERT; - break; - - case 'v': - if (es->linelen == 0) - return -1; - if (!argcnt) { - if (modified) { - es->cbuf[es->linelen] = '\0'; - source->line++; - histsave(source->line, es->cbuf, 1); - } else - argcnt = source->line + 1 - - (hlast - hnum); - } - shf_snprintf(es->cbuf, es->cbufsize, - argcnt ? "%s %d" : "%s", - "fc -e ${VISUAL:-${EDITOR:-vi}} --", - argcnt); - es->linelen = strlen(es->cbuf); - return 2; - - case 'x': - if (es->linelen == 0) - return -1; - modified = 1; hnum = hlast; - if (es->cursor + argcnt > es->linelen) - argcnt = es->linelen - es->cursor; - yank_range(es->cursor, es->cursor + argcnt); - del_range(es->cursor, es->cursor + argcnt); - break; - - case 'X': - if (es->cursor > 0) { - modified = 1; hnum = hlast; - if (es->cursor < argcnt) - argcnt = es->cursor; - yank_range(es->cursor - argcnt, es->cursor); - del_range(es->cursor - argcnt, es->cursor); - es->cursor -= argcnt; - } else - return -1; - break; - - case 'u': - t = es; - es = undo; - undo = t; - break; - - case 'U': - if (!modified) - return -1; - if (grabhist(modified, ohnum) < 0) - return -1; - modified = 0; - hnum = ohnum; - break; - - case '?': - if (hnum == hlast) - hnum = -1; - /* ahhh */ - case '/': - c3 = 1; - srchlen = 0; - lastsearch = *cmd; - /* fall through */ - case 'n': - case 'N': - if (lastsearch == ' ') - return -1; - if (lastsearch == '?') - c1 = 1; - else - c1 = 0; - if (*cmd == 'N') - c1 = !c1; - if ((c2 = grabsearch(modified, hnum, - c1, srchpat)) < 0) { - if (c3) { - restore_cbuf(); - refresh(0); - } - return -1; - } else { - modified = 0; - hnum = c2; - ohnum = hnum; - } - break; - case '_': { - int inspace; - char *p, *sp; - - if (histnum(-1) < 0) - return -1; - p = *histpos(); -#define issp(c) (isspace((c)) || (c) == '\n') - if (argcnt) { - while (*p && issp(*p)) - p++; - while (*p && --argcnt) { - while (*p && !issp(*p)) - p++; - while (*p && issp(*p)) - p++; - } - if (!*p) - return -1; - sp = p; - } else { - sp = p; - inspace = 0; - while (*p) { - if (issp(*p)) - inspace = 1; - else if (inspace) { - inspace = 0; - sp = p; - } - p++; - } - p = sp; - } - modified = 1; hnum = hlast; - if (es->cursor != es->linelen) - es->cursor++; - while (*p && !issp(*p)) { - argcnt++; - p++; - } - if (putbuf(space, 1, 0) != 0) - argcnt = -1; - else if (putbuf(sp, argcnt, 0) != 0) - argcnt = -1; - if (argcnt < 0) { - if (es->cursor != 0) - es->cursor--; - return -1; - } - insert = INSERT; - } - break; - - case '~': { - char *p; - int i; - - if (es->linelen == 0) - return -1; - for (i = 0; i < argcnt; i++) { - p = &es->cbuf[es->cursor]; - if (islower(*p)) { - modified = 1; hnum = hlast; - *p = toupper(*p); - } else if (isupper(*p)) { - modified = 1; hnum = hlast; - *p = tolower(*p); - } - if (es->cursor < es->linelen - 1) - es->cursor++; - } - break; - } - - case '#': - { - int ret = x_do_comment(es->cbuf, es->cbufsize, - &es->linelen); - if (ret >= 0) - es->cursor = 0; - return ret; - } - - case '=': /* at&t ksh */ - case Ctrl('e'): /* Nonstandard vi/ksh */ - print_expansions(es, 1); - break; - - - case Ctrl('i'): /* Nonstandard vi/ksh */ - if (!Flag(FVITABCOMPLETE)) - return -1; - complete_word(1, argcnt); - break; - - case Ctrl('['): /* some annoying at&t ksh's */ - if (!Flag(FVIESCCOMPLETE)) - return -1; - case '\\': /* at&t ksh */ - case Ctrl('f'): /* Nonstandard vi/ksh */ - complete_word(1, argcnt); - break; - - - case '*': /* at&t ksh */ - case Ctrl('x'): /* Nonstandard vi/ksh */ - expand_word(1); - break; - } - if (insert == 0 && es->cursor != 0 && es->cursor >= es->linelen) - es->cursor--; - } - return 0; -} - -static int -domove(int argcnt, const char *cmd, int sub) -{ - int bcount, UNINITIALIZED(i), t; - int UNINITIALIZED(ncursor); - - switch (*cmd) { - - case 'b': - if (!sub && es->cursor == 0) - return -1; - ncursor = backword(argcnt); - break; - - case 'B': - if (!sub && es->cursor == 0) - return -1; - ncursor = Backword(argcnt); - break; - - case 'e': - if (!sub && es->cursor + 1 >= es->linelen) - return -1; - ncursor = endword(argcnt); - if (sub && ncursor < es->linelen) - ncursor++; - break; - - case 'E': - if (!sub && es->cursor + 1 >= es->linelen) - return -1; - ncursor = Endword(argcnt); - if (sub && ncursor < es->linelen) - ncursor++; - break; - - case 'f': - case 'F': - case 't': - case 'T': - fsavecmd = *cmd; - fsavech = cmd[1]; - /* drop through */ - - case ',': - case ';': - if (fsavecmd == ' ') - return -1; - i = fsavecmd == 'f' || fsavecmd == 'F'; - t = fsavecmd > 'a'; - if (*cmd == ',') - t = !t; - if ((ncursor = findch(fsavech, argcnt, t, i)) < 0) - return -1; - if (sub && t) - ncursor++; - break; - - case 'h': - case Ctrl('h'): - if (!sub && es->cursor == 0) - return -1; - ncursor = es->cursor - argcnt; - if (ncursor < 0) - ncursor = 0; - break; - - case ' ': - case 'l': - if (!sub && es->cursor + 1 >= es->linelen) - return -1; - if (es->linelen != 0) { - ncursor = es->cursor + argcnt; - if (ncursor > es->linelen) - ncursor = es->linelen; - } - break; - - case 'w': - if (!sub && es->cursor + 1 >= es->linelen) - return -1; - ncursor = forwword(argcnt); - break; - - case 'W': - if (!sub && es->cursor + 1 >= es->linelen) - return -1; - ncursor = Forwword(argcnt); - break; - - case '0': - ncursor = 0; - break; - - case '^': - ncursor = 0; - while (ncursor < es->linelen - 1 && isspace(es->cbuf[ncursor])) - ncursor++; - break; - - case '|': - ncursor = argcnt; - if (ncursor > es->linelen) - ncursor = es->linelen; - if (ncursor) - ncursor--; - break; - - case '$': - if (es->linelen != 0) - ncursor = es->linelen; - else - ncursor = 0; - break; - - case '%': - ncursor = es->cursor; - while (ncursor < es->linelen && - (i = bracktype(es->cbuf[ncursor])) == 0) - ncursor++; - if (ncursor == es->linelen) - return -1; - bcount = 1; - do { - if (i > 0) { - if (++ncursor >= es->linelen) - return -1; - } else { - if (--ncursor < 0) - return -1; - } - t = bracktype(es->cbuf[ncursor]); - if (t == i) - bcount++; - else if (t == -i) - bcount--; - } while (bcount != 0); - if (sub && i > 0) - ncursor++; - break; - - default: - return -1; - } - return ncursor; -} - -static int -redo_insert(int count) -{ - while (count-- > 0) - if (putbuf(ibuf, inslen, insert==REPLACE) != 0) - return -1; - if (es->cursor > 0) - es->cursor--; - insert = 0; - return 0; -} - -static void -yank_range(int a, int b) -{ - yanklen = b - a; - if (yanklen != 0) - memmove(ybuf, &es->cbuf[a], yanklen); -} - -static int -bracktype(int ch) -{ - switch (ch) { - - case '(': - return 1; - - case '[': - return 2; - - case '{': - return 3; - - case ')': - return -1; - - case ']': - return -2; - - case '}': - return -3; - - default: - return 0; - } -} - -/* - * Non user interface editor routines below here - */ - -static int cur_col; /* current column on line */ -static int pwidth; /* width of prompt */ -static int prompt_trunc; /* how much of prompt to truncate */ -static int prompt_skip; /* how much of prompt to skip */ -static int winwidth; /* width of window */ -static char *wbuf[2]; /* window buffers */ -static int wbuf_len; /* length of window buffers (x_cols-3)*/ -static int win; /* window buffer in use */ -static char morec; /* more character at right of window */ -static int lastref; /* argument to last refresh() */ -static char holdbuf[LINE]; /* place to hold last edit buffer */ -static int holdlen; /* length of holdbuf */ - -static void -save_cbuf(void) -{ - memmove(holdbuf, es->cbuf, es->linelen); - holdlen = es->linelen; - holdbuf[holdlen] = '\0'; -} - -static void -restore_cbuf(void) -{ - es->cursor = 0; - es->linelen = holdlen; - memmove(es->cbuf, holdbuf, holdlen); -} - -/* return a new edstate */ -static struct edstate * -save_edstate(struct edstate *old) -{ - struct edstate *new; - - new = (struct edstate *)alloc(sizeof(struct edstate), APERM); - new->cbuf = alloc(old->cbufsize, APERM); - memcpy(new->cbuf, old->cbuf, old->linelen); - new->cbufsize = old->cbufsize; - new->linelen = old->linelen; - new->cursor = old->cursor; - new->winleft = old->winleft; - return new; -} - -static void -restore_edstate(struct edstate *new, struct edstate *old) -{ - memcpy(new->cbuf, old->cbuf, old->linelen); - new->linelen = old->linelen; - new->cursor = old->cursor; - new->winleft = old->winleft; - free_edstate(old); -} - -static void -free_edstate(struct edstate *old) -{ - afree(old->cbuf, APERM); - afree((char *)old, APERM); -} - - - -static void -edit_reset(char *buf, size_t len) -{ - const char *p; - - es = &ebuf; - es->cbuf = buf; - es->cbufsize = len; - undo = &undobuf; - undo->cbufsize = len; - - es->linelen = undo->linelen = 0; - es->cursor = undo->cursor = 0; - es->winleft = undo->winleft = 0; - - cur_col = pwidth = promptlen(prompt, &p); - prompt_skip = p - prompt; - if (pwidth > x_cols - 3 - MIN_EDIT_SPACE) { - cur_col = x_cols - 3 - MIN_EDIT_SPACE; - prompt_trunc = pwidth - cur_col; - pwidth -= prompt_trunc; - } else - prompt_trunc = 0; - if (!wbuf_len || wbuf_len != x_cols - 3) { - wbuf_len = x_cols - 3; - wbuf[0] = aresize(wbuf[0], wbuf_len, APERM); - wbuf[1] = aresize(wbuf[1], wbuf_len, APERM); - } - (void) memset(wbuf[0], ' ', wbuf_len); - (void) memset(wbuf[1], ' ', wbuf_len); - winwidth = x_cols - pwidth - 3; - win = 0; - morec = ' '; - lastref = 1; - holdlen = 0; -} - -/* - * this is used for calling x_escape() in complete_word() - */ -static int -x_vi_putbuf(const char *s, size_t len) -{ - return putbuf(s, len, 0); -} - -static int -putbuf(const char *buf, int len, int repl) -{ - if (len == 0) - return 0; - if (repl) { - if (es->cursor + len >= es->cbufsize) - return -1; - if (es->cursor + len > es->linelen) - es->linelen = es->cursor + len; - } else { - if (es->linelen + len >= es->cbufsize) - return -1; - memmove(&es->cbuf[es->cursor + len], &es->cbuf[es->cursor], - es->linelen - es->cursor); - es->linelen += len; - } - memmove(&es->cbuf[es->cursor], buf, len); - es->cursor += len; - return 0; -} - -static void -del_range(int a, int b) -{ - if (es->linelen != b) - memmove(&es->cbuf[a], &es->cbuf[b], es->linelen - b); - es->linelen -= b - a; -} - -static int -findch(int ch, int cnt, int forw, int incl) -{ - int ncursor; - - if (es->linelen == 0) - return -1; - ncursor = es->cursor; - while (cnt--) { - do { - if (forw) { - if (++ncursor == es->linelen) - return -1; - } else { - if (--ncursor < 0) - return -1; - } - } while (es->cbuf[ncursor] != ch); - } - if (!incl) { - if (forw) - ncursor--; - else - ncursor++; - } - return ncursor; -} - -static int -forwword(int argcnt) -{ - int ncursor; - - ncursor = es->cursor; - while (ncursor < es->linelen && argcnt--) { - if (is_wordch(es->cbuf[ncursor])) - while (is_wordch(es->cbuf[ncursor]) && - ncursor < es->linelen) - ncursor++; - else if (!isspace(es->cbuf[ncursor])) - while (!is_wordch(es->cbuf[ncursor]) && - !isspace(es->cbuf[ncursor]) && - ncursor < es->linelen) - ncursor++; - while (isspace(es->cbuf[ncursor]) && ncursor < es->linelen) - ncursor++; - } - return ncursor; -} - -static int -backword(int argcnt) -{ - int ncursor; - - ncursor = es->cursor; - while (ncursor > 0 && argcnt--) { - while (--ncursor > 0 && isspace(es->cbuf[ncursor])) - ; - if (ncursor > 0) { - if (is_wordch(es->cbuf[ncursor])) - while (--ncursor >= 0 && - is_wordch(es->cbuf[ncursor])) - ; - else - while (--ncursor >= 0 && - !is_wordch(es->cbuf[ncursor]) && - !isspace(es->cbuf[ncursor])) - ; - ncursor++; - } - } - return ncursor; -} - -static int -endword(int argcnt) -{ - int ncursor; - - ncursor = es->cursor; - while (ncursor < es->linelen && argcnt--) { - while (++ncursor < es->linelen - 1 && - isspace(es->cbuf[ncursor])) - ; - if (ncursor < es->linelen - 1) { - if (is_wordch(es->cbuf[ncursor])) - while (++ncursor < es->linelen && - is_wordch(es->cbuf[ncursor])) - ; - else - while (++ncursor < es->linelen && - !is_wordch(es->cbuf[ncursor]) && - !isspace(es->cbuf[ncursor])) - ; - ncursor--; - } - } - return ncursor; -} - -static int -Forwword(int argcnt) -{ - int ncursor; - - ncursor = es->cursor; - while (ncursor < es->linelen && argcnt--) { - while (!isspace(es->cbuf[ncursor]) && ncursor < es->linelen) - ncursor++; - while (isspace(es->cbuf[ncursor]) && ncursor < es->linelen) - ncursor++; - } - return ncursor; -} - -static int -Backword(int argcnt) -{ - int ncursor; - - ncursor = es->cursor; - while (ncursor > 0 && argcnt--) { - while (--ncursor >= 0 && isspace(es->cbuf[ncursor])) - ; - while (ncursor >= 0 && !isspace(es->cbuf[ncursor])) - ncursor--; - ncursor++; - } - return ncursor; -} - -static int -Endword(int argcnt) -{ - int ncursor; - - ncursor = es->cursor; - while (ncursor < es->linelen - 1 && argcnt--) { - while (++ncursor < es->linelen - 1 && - isspace(es->cbuf[ncursor])) - ; - if (ncursor < es->linelen - 1) { - while (++ncursor < es->linelen && - !isspace(es->cbuf[ncursor])) - ; - ncursor--; - } - } - return ncursor; -} - -static int -grabhist(int save, int n) -{ - char *hptr; - - if (n < 0 || n > hlast) - return -1; - if (n == hlast) { - restore_cbuf(); - ohnum = n; - return 0; - } - (void) histnum(n); - if ((hptr = *histpos()) == NULL) { - internal_errorf(0, "grabhist: bad history array"); - return -1; - } - if (save) - save_cbuf(); - if ((es->linelen = strlen(hptr)) >= es->cbufsize) - es->linelen = es->cbufsize - 1; - memmove(es->cbuf, hptr, es->linelen); - es->cursor = 0; - ohnum = n; - return 0; -} - -static int -grabsearch(int save, int start, int fwd, char *pat) -{ - char *hptr; - int hist; - int anchored; - - if ((start == 0 && fwd == 0) || (start >= hlast-1 && fwd == 1)) - return -1; - if (fwd) - start++; - else - start--; - anchored = *pat == '^' ? (++pat, 1) : 0; - if ((hist = findhist(start, fwd, pat, anchored)) < 0) { - /* if (start != 0 && fwd && match(holdbuf, pat) >= 0) { */ - /* XXX should FILECMP be strncmp? */ - if (start != 0 && fwd && FILECMP(holdbuf, pat) >= 0) { - restore_cbuf(); - return 0; - } else - return -1; - } - if (save) - save_cbuf(); - histnum(hist); - hptr = *histpos(); - if ((es->linelen = strlen(hptr)) >= es->cbufsize) - es->linelen = es->cbufsize - 1; - memmove(es->cbuf, hptr, es->linelen); - es->cursor = 0; - return hist; -} - -static void -redraw_line(int newline) -{ - (void) memset(wbuf[win], ' ', wbuf_len); - if (newline) { - x_putc('\r'); - x_putc('\n'); - } - vi_pprompt(0); - cur_col = pwidth; - morec = ' '; -} - -static void -refresh(int leftside) -{ - if (leftside < 0) - leftside = lastref; - else - lastref = leftside; - if (outofwin()) - rewindow(); - display(wbuf[1 - win], wbuf[win], leftside); - win = 1 - win; -} - -static int -outofwin(void) -{ - int cur, col; - - if (es->cursor < es->winleft) - return 1; - col = 0; - cur = es->winleft; - while (cur < es->cursor) - col = newcol((unsigned char) es->cbuf[cur++], col); - if (col >= winwidth) - return 1; - return 0; -} - -static void -rewindow(void) -{ - int tcur, tcol; - int holdcur1, holdcol1; - int holdcur2, holdcol2; - - holdcur1 = holdcur2 = tcur = 0; - holdcol1 = holdcol2 = tcol = 0; - while (tcur < es->cursor) { - if (tcol - holdcol2 > winwidth / 2) { - holdcur1 = holdcur2; - holdcol1 = holdcol2; - holdcur2 = tcur; - holdcol2 = tcol; - } - tcol = newcol((unsigned char) es->cbuf[tcur++], tcol); - } - while (tcol - holdcol1 > winwidth / 2) - holdcol1 = newcol((unsigned char) es->cbuf[holdcur1++], - holdcol1); - es->winleft = holdcur1; -} - -static int -newcol(int ch, int col) -{ - if (ch == '\t') - return (col | 7) + 1; - return col + char_len(ch); -} - -static void -display(char *wb1, char *wb2, int leftside) -{ - unsigned char ch; - char *twb1, *twb2, mc; - int cur, col, cnt; - int UNINITIALIZED(ncol); - int moreright; - - col = 0; - cur = es->winleft; - moreright = 0; - twb1 = wb1; - while (col < winwidth && cur < es->linelen) { - if (cur == es->cursor && leftside) - ncol = col + pwidth; - if ((ch = es->cbuf[cur]) == '\t') { - do { - *twb1++ = ' '; - } while (++col < winwidth && (col & 7) != 0); - } else { - if ((ch & 0x80) && Flag(FVISHOW8)) { - *twb1++ = 'M'; - if (++col < winwidth) { - *twb1++ = '-'; - col++; - } - ch &= 0x7f; - } - if (col < winwidth) { - if (ch < ' ' || ch == 0x7f) { - *twb1++ = '^'; - if (++col < winwidth) { - *twb1++ = ch ^ '@'; - col++; - } - } else { - *twb1++ = ch; - col++; - } - } - } - if (cur == es->cursor && !leftside) - ncol = col + pwidth - 1; - cur++; - } - if (cur == es->cursor) - ncol = col + pwidth; - if (col < winwidth) { - while (col < winwidth) { - *twb1++ = ' '; - col++; - } - } else - moreright++; - *twb1 = ' '; - - col = pwidth; - cnt = winwidth; - twb1 = wb1; - twb2 = wb2; - while (cnt--) { - if (*twb1 != *twb2) { - if (cur_col != col) - ed_mov_opt(col, wb1); - x_putc(*twb1); - cur_col++; - } - twb1++; - twb2++; - col++; - } - if (es->winleft > 0 && moreright) - /* POSIX says to use * for this but that is a globbing - * character and may confuse people; + is more innocuous - */ - mc = '+'; - else if (es->winleft > 0) - mc = '<'; - else if (moreright) - mc = '>'; - else - mc = ' '; - if (mc != morec) { - ed_mov_opt(pwidth + winwidth + 1, wb1); - x_putc(mc); - cur_col++; - morec = mc; - } - if (cur_col != ncol) - ed_mov_opt(ncol, wb1); -} - -static void -ed_mov_opt(int col, char *wb) -{ - if (col < cur_col) { - if (col + 1 < cur_col - col) { - x_putc('\r'); - vi_pprompt(0); - cur_col = pwidth; - while (cur_col++ < col) - x_putc(*wb++); - } else { - while (cur_col-- > col) - x_putc('\b'); - } - } else { - wb = &wb[cur_col - pwidth]; - while (cur_col++ < col) - x_putc(*wb++); - } - cur_col = col; -} - - -/* replace word with all expansions (ie, expand word*) */ -static int -expand_word(int command) -{ - static struct edstate *buf; - int rval = 0; - int nwords; - int start, end; - char **words; - int i; - - /* Undo previous expansion */ - if (command == 0 && expanded == EXPAND && buf) { - restore_edstate(es, buf); - buf = 0; - expanded = NONE; - return 0; - } - if (buf) { - free_edstate(buf); - buf = 0; - } - - nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH, - es->cbuf, es->linelen, es->cursor, - &start, &end, &words, NULL); - if (nwords == 0) { - vi_error(); - return -1; - } - - buf = save_edstate(es); - expanded = EXPAND; - del_range(start, end); - es->cursor = start; - for (i = 0; i < nwords; ) { - if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) { - rval = -1; - break; - } - if (++i < nwords && putbuf(space, 1, 0) != 0) { - rval = -1; - break; - } - } - i = buf->cursor - end; - if (rval == 0 && i > 0) - es->cursor += i; - modified = 1; hnum = hlast; - insert = INSERT; - lastac = 0; - refresh(0); - return rval; -} - -static int -complete_word(int command, int count) -{ - static struct edstate *buf; - int rval = 0; - int nwords; - int start, end; - char **words; - char *match; - int match_len; - int is_unique; - int is_command; - - /* Undo previous completion */ - if (command == 0 && expanded == COMPLETE && buf) { - print_expansions(buf, 0); - expanded = PRINT; - return 0; - } - if (command == 0 && expanded == PRINT && buf) { - restore_edstate(es, buf); - buf = 0; - expanded = NONE; - return 0; - } - if (buf) { - free_edstate(buf); - buf = 0; - } - - /* XCF_FULLPATH for count 'cause the menu printed by print_expansions() - * was done this way. - */ - nwords = x_cf_glob(XCF_COMMAND_FILE | (count ? XCF_FULLPATH : 0), - es->cbuf, es->linelen, es->cursor, - &start, &end, &words, &is_command); - if (nwords == 0) { - vi_error(); - return -1; - } - if (count) { - int i; - - count--; - if (count >= nwords) { - vi_error(); - x_print_expansions(nwords, words, is_command); - x_free_words(nwords, words); - redraw_line(0); - return -1; - } - /* - * Expand the count'th word to its basename - */ - if (is_command) { - match = words[count] - + x_basename(words[count], NULL); - /* If more than one possible match, use full path */ - for (i = 0; i < nwords; i++) - if (i != count && - FILECMP(words[i] - + x_basename(words[i], NULL), - match) == 0) - { - match = words[count]; - break; - } - } else - match = words[count]; - match_len = strlen(match); - is_unique = 1; - /* expanded = PRINT; next call undo */ - } else { - match = words[0]; - match_len = x_longest_prefix(nwords, words); - expanded = COMPLETE; /* next call will list completions */ - is_unique = nwords == 1; - } - - buf = save_edstate(es); - del_range(start, end); - es->cursor = start; - - /* escape all shell-sensitive characters and put the result into - * command buffer */ - rval = x_escape(match, match_len, x_vi_putbuf); - - if (rval == 0 && is_unique) { - /* If exact match, don't undo. Allows directory completions - * to be used (ie, complete the next portion of the path). - */ - expanded = NONE; - - /* If not a directory, add a space to the end... */ - if (match_len > 0 && !ISDIRSEP(match[match_len - 1])) - rval = putbuf(space, 1, 0); - } - x_free_words(nwords, words); - - modified = 1; hnum = hlast; - insert = INSERT; - lastac = 0; /* prevent this from being redone... */ - refresh(0); - - return rval; -} - -static int -print_expansions(struct edstate *e, int command GCC_FUNC_ATTR(unused)) -{ - int nwords; - int start, end; - char **words; - int is_command; - - nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH, - e->cbuf, e->linelen, e->cursor, - &start, &end, &words, &is_command); - if (nwords == 0) { - vi_error(); - return -1; - } - x_print_expansions(nwords, words, is_command); - x_free_words(nwords, words); - redraw_line(0); - return 0; -} - -/* How long is char when displayed (not counting tabs) */ -static int -char_len(int c) -{ - int len = 1; - - if ((c & 0x80) && Flag(FVISHOW8)) { - len += 2; - c &= 0x7f; - } - if (c < ' ' || c == 0x7f) - len++; - return len; -} - -/* Similar to x_zotc(emacs.c), but no tab weirdness */ -static void -x_vi_zotc(int c) -{ - if (Flag(FVISHOW8) && (c & 0x80)) { - x_puts("M-"); - c &= 0x7f; - } - if (c < ' ' || c == 0x7f) { - x_putc('^'); - c ^= '@'; - } - x_putc(c); -} - -static void -vi_pprompt(int full) -{ - pprompt(prompt + (full ? 0 : prompt_skip), prompt_trunc); -} - -static void -vi_error(void) -{ - /* Beem out of any macros as soon as an error occurs */ - vi_macro_reset(); - x_putc(BEL); - x_flush(); -} - -static void -vi_macro_reset(void) -{ - if (macro.p) { - afree(macro.buf, APERM); - memset((char *) ¯o, 0, sizeof(macro)); - } -}