diff options
50 files changed, 2613 insertions, 853 deletions
diff --git a/bin/ksh/BUG-REPORTS b/bin/ksh/BUG-REPORTS index 1295a31fefb..af47d26dd2e 100644 --- a/bin/ksh/BUG-REPORTS +++ b/bin/ksh/BUG-REPORTS @@ -1,4 +1,4 @@ -$OpenBSD: BUG-REPORTS,v 1.5 1996/11/21 07:59:24 downsj Exp $ +$OpenBSD: BUG-REPORTS,v 1.6 1998/06/25 19:01:27 millert Exp $ List of reported problems (problems reported and fixed before 5.0.4 not included). Unresolved problems (may or may not still exist) marked by *, @@ -65,11 +65,6 @@ problems believed to be fixed marked by x. et al) don't work in shell scripts. [see Mail.10:49] -* pdksh 5.2.4, - (reported by Gabor Zahemszky): echo ${foo[*]#/} generates - bad substsitution error, newer ksh's don't (older ones do); - error includes {...#@(/)}. - [see Mail.XXX:XXX] - * pdksh 5.2.4, - (reported by Gabor Zahemszky): emacs: ^P steps through multiline commands - should go to start of command. [see Mail.XXX:XXX] @@ -82,12 +77,17 @@ problems believed to be fixed marked by x. select and read operations. [see Mail.XXX:XXX] -* pdksh 5.2.8, - : extended pattern globing doesn't handle nested parens (). - * pdksh 5.2.10, - (reported by Simon J. Gerraty): in emacs, <ESC><ESC> applied to a word with a macro does not complete the word (only expands the macro). [see Mail.XXX] +* pdksh 5.2.12, - (reported by Han Holl <jeholl@euronet.nl>): pdksh does not + parse the whole script before executing, so some syntax errors are not + detected (if the shell exits before reading the whole file). + [see Mail.XXX] + +* pdksh 5.2.12, - : MAILCHECK isn't preserved from the envirnment on startup. + --------------------- put fixed problems below this line --------------------- x pdksh 5.0.3, NetBSD 0.9a (reported by Simon J. Gerraty): pipelines @@ -1115,3 +1115,111 @@ x pdksh 5.2.11, - (reported by Herbert Thielen via Larry Daffner): shell leaves [see Mail.XXX] [fixed in 5.2.12: call main.c(cleanup_proc_env()) from exec.c(execute()).] +x pdksh 5.2.12, - (reported with fix by Eric J. Chet via Thomas Gellekum): + . can cause core dump when cleaning up. + [see Mail.XXX] + [fixed in 5.2.13: call shf_close() before quitenv()] + +x pdksh 5.2.12, - (reported by Bruce Burns): test with a single -t option + always returns true (does a string test instead of isatty(1)). + [see Mail.XXX] + [fixed in 5.2.13: do the isatty(1) unless in posix mode] + +x pdksh 5.2.12, - (reported by David Tamkin): aliases ending in "; " + cause continuation prompt to be printed. + [see Mail.XXX] + [fixed in 5.2.13: syn.c(c_list) now handles blank lines, removed cf=CONTIN + in syn.c(get_command)] + +x pdksh 5.2.12, - (reported by Herbert Thielen via Larry Daffner): set + does not allow +o and -o options in the same command line. + [see Mail.XXX] + [fixed in 5.2.13: changed ksh_getopt to remove exclusion code.] + +x pdksh 5.2.12, - (reported by Han Holl <jeholl@euronet.nl>): + set -A foo -- bar doesn't skip the --. + [see Mail.XXX] + [fixed in 5.2.13: changed misc.c(parse_args) so A takes an option] + +x pdksh 5.2.12, - (reported by David Tamkin): -e (errexit) should be ignored + when processing profile and ENV. + [see Mail.XXX] + [fixed in 5.2.13: errexit saved/cleared/restored in main()] + +x pdksh 5.2.12, - (reported by Han Holl <jeholl@euronet.nl>): + [ -x foo ] succeeds for root if foo exists. + [see Mail.XXX] + [fixed in 5.2.13: changed c_test.c(test_access) to use stat in this case] + +x pdksh 5.2.4, - (reported by Gabor Zahemszky): echo ${foo[*]#/} generates + bad substsitution error, newer ksh's don't (older ones do); + error includes {...#@(/)}. + [fixed in 5.2.13: moved the @(..) hack from yylex() to expand()] + +x pdksh 5.2.8, - : extended pattern globing doesn't handle nested parens (), + e.g., [[ aby = +(a|b(x|y)) ]] + +x pdksh 5.2.12, - (reported by Marc Olzheim <marcolz@stack.nl>): + "echo a | (echo b | echo c)" causes a core dump. + [see Mail.XXX] + [fixed in 5.2.13: clear XPIPEI,XPIPEO at start of jobs.c(exchild)] + +x pdksh 5.2.12, - (reported by Curt Finch <curt@pnk.com>): in an interactive + shell, globbing isn't done on redirections if the command is part of + a pipeline. e.g., in "cat < /tmp/*gz | grep foo", the /tmp/*gz is + not expanded. + [see Mail.XXX] + [fixed in 5.2.13: clear XPIPEI,XPIPEO at start of jobs.c(exchild)] + +x pdksh 5.2.12, - (reported with fix by Greg A. Woods <woods@most.weird.com>): + in emacs, ^[_ (aka ^[.) gets the word from the previous line relative to + where the current line came from in the history. It should get it from + the last executed line ala at&t ksh. + [see Mail.XXX] + [fixed in 5.2.13: use absolute last command] + +x pdksh 5.2.12, - (reported by Gabor Zahemszky): MAILCHECK and MAIL + don't report `new mail' unless MAIL is re-assigned. + [see Mail.XXX] + [fixed in 5.2.13: mail code was using variables memory, which was later + trashed by exporting the MAIL variable. Fixed by saving copy of value.] + +x pdksh 5.2.12, - (reported by Gabor Zahemszky): memory gets badly fragmented + when reversing a (large) file using a simple while read loop and variable + contatenation. + [see Mail.XXX] + [fixed in 5.2.13: alloc.c changed to allow malloc() to deal with large + blocks.] + +x pdksh 5.2.12, - (reported by Bernd Eggink <eggink@rrz.uni-hamburg.de>) + ksh style functions don't save/reset/restore OPTIND. + [see Mail.XXX] + [fixed in 5.2.13: save and restore user_opt for ksh-style functions.] + +x pdksh 5.2.12, - (reported by Marc Olzheim <marcolz@stack.nl>) + ${..%..} stuff don't work in SH mode. + [see Mail.XXX] + [fixed in 5.2.13: removed ifdef KSH from misc.c(do_gmatch)] + +x pdksh 5.2.12, - (reported with fix by George Robbins + <grr@shandakor.tharsis.com>) + in sh, "exec 3>&1" does not keep fd 3 open in executed commands. + [see Mail.XXX] + [fixed in 5.2.13: c_sh.c(c_exec): added ifdef KSH around fd_clexec() call] + +x pdksh 5.2.12, - (reported with fix by George White + <gwhite@bodnext.bio.dfo.ca>) + in remove_temps(main.c), space for name field is not allocated correctly. + [see Mail.XXX] + [fixed in 5.2.13: initialize t->name in new structure] + +x pdksh 5.2.12, - (reported with fix by Marc Olzheim <marcolz@stack.nl>): + when at (past) end of the line, word/command completion will skip back + [see Mail.XXX] + [fixed in 5.2.13: edit.c(x_locate_word): delete special handling of + end-of-buffer] + +x pdksh 5.2.12, - (reported by Mike Kelly <tfsmiles@ecst.csuchico.edu>): + doting a directory (or a empty path) is allowed. + [see Mail.XXX] + [fixed in 5.2.13: exec.c(search_access): don't do non-regular files for R_OK] diff --git a/bin/ksh/CONTRIBUTORS b/bin/ksh/CONTRIBUTORS index edf32c0db35..f04390e1229 100644 --- a/bin/ksh/CONTRIBUTORS +++ b/bin/ksh/CONTRIBUTORS @@ -1,4 +1,4 @@ -$OpenBSD: CONTRIBUTORS,v 1.3 1996/10/01 02:05:25 downsj Exp $ +$OpenBSD: CONTRIBUTORS,v 1.4 1998/06/25 19:01:29 millert Exp $ This is a partial history of this shell gleened from old change logs and readmes (most of which are still in the misc directory) and the source @@ -101,3 +101,11 @@ Other contributors: hidden characters (eg, escape codes). * Andrew S. Townley (<atownley@informix.com>): fixes for NeXT machines: get a controlling if one needed, use correct profile. + * Eric J. Chet (<ejc@bazzle.com>): fix for core dump in . (quitenv() called + too soon). + * Greg A. Woods <woods@most.weird.com>: fix to make ^[_ in emacs work + as in at&t ksh. + * George Robbins <grr@shandakor.tharsis.com>: fix for sh mode to + keep exec'd file descriptors open. + * George White <gwhite@bodnext.bio.dfo.ca>: fix here-doc problem under OS/2 + (memory allocated incorrectly). diff --git a/bin/ksh/ChangeLog b/bin/ksh/ChangeLog index b515993f3aa..8f2dd37c46d 100644 --- a/bin/ksh/ChangeLog +++ b/bin/ksh/ChangeLog @@ -1,4 +1,182 @@ -$OpenBSD: ChangeLog,v 1.5 1996/11/21 07:59:25 downsj Exp $ +Mon Oct 27 12:38:05 NST 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * made pdksh-5.2.13 distribution + +Mon Oct 27 12:21:51 NST 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * c_sh.c(c_dot): use search() error argument to report problem + correctly. + * exec.c(search_access): don't set *errnop if it is already set. + * exec.c(search_access): extended non-regular file check from + just X_OK to both X_OK and R_OK. + +Wed Oct 22 11:49:02 NDT 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * edit.c(x_locate_word): don't skip trailing space if at end + of buffer (based on fix from Marc Olzheim). + +Fri Aug 15 22:06:53 NDT 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * eval.c(varsub,expand), lex.c(yylex): allow :%, :#, :%% and :## + to be compatable with ksh88. + +Sat Aug 2 12:13:30 NDT 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * syn.c(get_command): case MDPAREN/DBRACKET: do not + clear KEYWORD|ALIAS from syniocf. + +Tue Jul 29 16:24:38 NDT 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * c_sh.c(c_exec): added ifdef KSH around fd_clexec() + (based on fix from George Robins). + +Tue Jun 3 12:52:05 NDT 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * misc.c(do_gmatch): removed ifdef KSH about @(..|..) code as it + is needed in SH mode for ${..%..} stuff. + +Mon May 19 16:10:06 NDT 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * table.h(struct block): added getopt_state and flags fields; + added BF_DOGETOPTS. + * sh.h,c_ksh.c: moved user_opt decl/defn from c_ksh.c to sh.h. + * var.c(getspec): added case for V_OPTIND. + * var.c(popblock): if BF_DOGETOPTS set, restore user_opt. + * exec.c(comexec): case CFUNC: save user_opt for ksh-style functions. + * c_ksh.c(getopts_reset,c_getopts): removed getopts_noreset variable + and code. + * sh.h(Getopts): added uoptind field. + +Fri May 16 11:40:22 NDT 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * io.c(error_prefix): don't print kshname if it is the + same as the source file name. + +Thu Mar 13 10:42:31 NST 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * mail.c(mbset): save a copy of the path so it can't get trashed + (eg, by exporting a varibale). + +Wed Feb 26 11:24:06 NST 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * emacs.c(x_prev_histword): get word from last command entered, + not from last command relative to current location in history + (fix from Greg A. Woods). + +Sun Feb 16 13:18:52 NST 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * sh.h(FTALKING_I): new define + * misc.c(options[]): added anonymous options for internal use: + changed all code using options to not assume null option name + is the end of options (use NELEM()) instead. + Added slot for FTALKING_I + + * c_sh.c(c_read), exec.c(iosetup): test FTALKING_I instead of FTALKING. + * main.c(main): set FTALKING_I. + +Fri Jan 10 16:36:36 NST 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * jobs.c(exchild): use orig_flags instead of flags when testing + XPIPEI/XPIPEO; clear all flags except XEXEC and XERROK. + +Tue Jan 7 11:16:08 NST 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * lex.h(yynerrs): deleted (not used); deleted all assignments of it. + +Fri Jan 3 13:40:29 NST 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * lex.c(yylex): case STBRACE: interpret ( | ) as patterns; + case SPATTERN: allow ( as an alias for @(. + +Thu Jan 2 15:44:07 NST 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * main.c(main): set PATH to def_path in startup. + +Thu Jan 2 10:19:43 NST 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c(comexec): ifdef KSH the setting of $_; only set $_ to + last arg if interactive. + +Wed Jan 1 13:38:26 NST 1997 Michael Rendell (michael@panda.cs.mun.ca) + + * lex.c(yylex),eval.c(expand),tree.c(tputS,wdscan,wdstrip): + changed OSUBST/CSUBST encoding to have { or x after xSUBST. + + * lex.c(yylex): case ${: don't prepend @( and append ) to trim patterns. + * eval.c(expand): prepend MAGIC @ and append MAGIC ) to trim patterns. + + * syn.c(function_body): call wdstrip(). + * tree.c(wdstrip): new function. + + * lex.c(yylex): moved handling of < and > into one location. + +Wed Dec 11 13:00:05 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * sh.h(ksheuid): new variable. + * main.c(main): set/use ksheuid. + * misc.c(change_flag): set ksheuid. + * c_test.c(test_eval): use ksheuid + * c_test.c(test_eaccess): if doing X_OK and user is root, use + stat to avoid false positives on files. + +Mon Dec 9 12:08:56 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * main.c(main): save/clear/restore FERREXIT flag while processing + profile and ENV. + +Wed Dec 4 12:25:23 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * misc.c(parse_args): change -A option handling - make getopts + gather the option (A: vs A). + +Thu Nov 21 15:42:57 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * c_ksh.c(c_alias): accept + options; don't print alias definition + if + option used; allow export flag to be cleared; added -p + option. + +Thu Nov 21 14:35:47 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * tree.c(ptree),c_ksh.c(c_typeset): print ksh functions as + "function foo...", sh functions as "foo()...". + + * c_ksh.c(c_typeset): accept -p flag (does nothing). + +Wed Nov 20 11:36:08 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * c_ksh.c(c_typeset): simplified option exclusion code. + + * misc.c(ksh_getopt): allow options in same command line to start + with either + or - (if appropriate). [code existed to similate + ksh88 typeset behaviour which disallowed "typeset +x -i foo"] + +Wed Nov 13 12:02:59 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * syn.c(c_list): added multi argument; changed all calls to pass + TRUE, except one in yyparse(); changed logic to accept and + ignore blank lines if multi flag is set. + * syn.c(get_command): removed multiline.on/cf=CONTIN test/assignment. + * syn.c(struct multiline_state,struct nesting_state,multiline,nesting, + multiline_push,multiline_pop,nesting_push,nesting_pop): renamed + *multiline* to *nesting*; removed struct multiline_state.on field + (deleted all references). + +Mon Nov 4 16:29:50 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * c_test.c(c_test): in special < 5 arg code: if single arg is + -t and not in FPOSIX mode, don't decide its a string test. + +Wed Oct 30 11:34:39 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * main.c(include): call quitenv() after shf_close() + (fix from Eric J. Chet). + +Wed Oct 30 11:23:17 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) + + * exec.c(comexec): case CFUNC: set $0 to kshname if non-function + function. + +$OpenBSD: ChangeLog,v 1.6 1998/06/25 19:01:31 millert Exp $ Tue Oct 29 11:34:58 NST 1996 Michael Rendell (michael@panda.cs.mun.ca) diff --git a/bin/ksh/ChangeLog.0 b/bin/ksh/ChangeLog.0 index 23dc38eb028..ef5a22321a4 100644 --- a/bin/ksh/ChangeLog.0 +++ b/bin/ksh/ChangeLog.0 @@ -1,4 +1,4 @@ -$OpenBSD: ChangeLog.0,v 1.1 1996/08/14 06:19:10 downsj Exp $ +$OpenBSD: ChangeLog.0,v 1.2 1998/06/25 19:01:32 millert Exp $ Thu Jun 15 11:02:06 NDT 1995 Michael Rendell (michael@panda.cs.mun.ca) @@ -3223,7 +3223,7 @@ Tue May 31 10:49:16 NDT 1994 Michael Rendell (michael@panda.cs.mun.ca) appropriately for BSD vs POSIX/SYSV getpgrp. * expand.h, ksh_dir.h, ksh_stat.h, ksh_time.h, ksh_wait.h, shf.h, - tty.h: added RCS $Id: ChangeLog.0,v 1.1 1996/08/14 06:19:10 downsj Exp $'s. + tty.h: added RCS Id's. * acconfig.h: updated SIGSET_T comment: unisgned int -> unsigned. diff --git a/bin/ksh/IAFA-PACKAGE b/bin/ksh/IAFA-PACKAGE index 879682c0a61..0c22b94073c 100644 --- a/bin/ksh/IAFA-PACKAGE +++ b/bin/ksh/IAFA-PACKAGE @@ -1,7 +1,7 @@ -$OpenBSD: IAFA-PACKAGE,v 1.5 1996/11/21 07:59:25 downsj Exp $ +$OpenBSD: IAFA-PACKAGE,v 1.6 1998/06/25 19:01:34 millert Exp $ Title: pdksh -Version: 5.2.12 +Version: 5.2.13 Description: A public domain implementation of the Korn shell (ksh88), a UNIX command line interpreter / scripting language; the few missing ksh features are being added and the shell is being diff --git a/bin/ksh/NEWS b/bin/ksh/NEWS index f1115015719..aecaedd8a45 100644 --- a/bin/ksh/NEWS +++ b/bin/ksh/NEWS @@ -1,4 +1,30 @@ -$OpenBSD: NEWS,v 1.5 1996/11/21 07:59:26 downsj Exp $ +Version 5.2.13 + +* bug fixes + * functions: $0 in sh-style functions is now the same as the shell's $0. + * .: fixed possible core dump on clean up. + * test: a lone -t argument now does a isatty(1) test if not in posix mode. + * alias: PS2 no longer printed when expanding alias foo="cmd; ". + * set/typeset/getopts: can have options prefixed with both + and -. + * typeset -f: now reproduces functions correctly ("function F" vs "F()"). + * alias: options can start with +. + * set -A: a -- marking end of options is now skipped. + * errexit option (-e) ignored when reading profile and $ENV files. + * test: '-x f' now fails for root if f is a file and has no x bits set. + * $_: set to last arg in interactive shells only. + * PATH: if $PATH not set on startup, it is set to the default path. + * extended globbing: allow (pat|pat) within @(...) and ${foo#...} patterns. + * emacs: ^[_ now behaves as it does in at&t ksh (word from _last_ command). + * MAIL/MAILCHECK: fixed bug that prevented the `new mail' messages. + * ${..%..} and ${..#..} now work if compiled as sh. + * sh: fd's greater than 2 are passed on to executed commands. + * syntax: accepts "if (( 1 )) then" (also [[ ]]): no ; required before then. + * substitution: accepts (and ignores) leading : in %, %%, #, ## substitions. + * .: doting directories no longer allowed. + * editing: completion after "cmd " now completes files (was command). + + +$OpenBSD: NEWS,v 1.6 1998/06/25 19:01:35 millert Exp $ Version 5.2.12 diff --git a/bin/ksh/NOTES b/bin/ksh/NOTES index 17f29108eda..3979a77a7b4 100644 --- a/bin/ksh/NOTES +++ b/bin/ksh/NOTES @@ -1,8 +1,7 @@ -$OpenBSD: NOTES,v 1.3 1996/10/01 02:05:29 downsj Exp $ +$OpenBSD: NOTES,v 1.4 1998/06/25 19:01:37 millert Exp $ -General features of at&t ksh that are not (yet) in pdksh: - - exported aliases. - - exported functions. +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. @@ -461,6 +460,15 @@ at&t ksh bugs: hi there ksh: echo: not found $ + - (SunOS M-12/28/93d): + $ cat -n << X $( + > echo foo + > ) + > X + > echo bar + ) + ./ksh93: X: cannot open [No such file or directory] + Memory fault (core dumped) POSIX sh questions (references are to POSIX 1003.2-1992) - arithmetic expressions: how are empty expressions treated? diff --git a/bin/ksh/PROJECTS b/bin/ksh/PROJECTS index 917960ca4a0..6a662d64b3e 100644 --- a/bin/ksh/PROJECTS +++ b/bin/ksh/PROJECTS @@ -1,4 +1,4 @@ -$OpenBSD: PROJECTS,v 1.3 1996/10/01 02:05:30 downsj Exp $ +$OpenBSD: PROJECTS,v 1.4 1998/06/25 19:01:38 millert Exp $ Things to be done in pdksh (see also the NOTES file): @@ -99,8 +99,7 @@ Things to be done in pdksh (see also the NOTES file): care of, but more needs doing. * remove static limits created by fixed sized arrays - (eg, getsc_(line[]), ident[], heres[], PATH, states(lex.c), - buffer size in emacs/vi code) + (eg, ident[], heres[], PATH, buffer size in emacs/vi code) * merge the emacs and vi code (should reduce the size of the shell and make maintenance easier); handle SIGWINCH while editing a line. diff --git a/bin/ksh/README b/bin/ksh/README index f12f85b0ede..3fbf0134fb7 100644 --- a/bin/ksh/README +++ b/bin/ksh/README @@ -1,6 +1,6 @@ -$OpenBSD: README,v 1.5 1996/11/21 07:59:26 downsj Exp $ +$OpenBSD: README,v 1.6 1998/06/25 19:01:42 millert Exp $ -Last updated October '96 for pdksh-5.2.12. +Last updated October '97 for pdksh-5.2.13. (check ftp://ftp.cs.mun.ca:/pub/pdksh/ or http://www.cs.mun.ca/~michael/pdksh/ for new versions/patches) @@ -187,4 +187,4 @@ as well as the following, if relevant (if you aren't sure, include them) * the contents of config.log (this is created by the configure script) * if you are using gcc (the GNU C compiler), which version it is. -Michael Rendell, michael@cs.mun.ca. +Michael Rendell, michael@cs.mun.ca diff --git a/bin/ksh/alloc.c b/bin/ksh/alloc.c index bc39b1bc045..de19068a73f 100644 --- a/bin/ksh/alloc.c +++ b/bin/ksh/alloc.c @@ -1,42 +1,131 @@ -/* $OpenBSD: alloc.c,v 1.1 1996/08/14 06:19:10 downsj Exp $ */ +/* $OpenBSD: alloc.c,v 1.2 1998/06/25 19:01:43 millert Exp $ */ /* * area-based allocation built on malloc/free */ #include "sh.h" + +#ifdef TEST_ALLOC +# define shellf printf +# ifndef DEBUG_ALLOC +# define DEBUG_ALLOC +# endif /* DEBUG_ALLOC */ +#endif /* TEST_ALLOC */ + #ifdef MEM_DEBUG -# undef alloc -# undef aresize -# undef afree -#endif /* MEM_DEBUG */ -#define ICELLS 100 /* number of Cells in small Block */ +/* + * Special versions of alloc routines if doing mem_debug + */ +Area * +_chmem_ainit(ap, file, line) + Area *ap; + const char *file; + int line; +{ + ap->freelist = (struct Block *) _chmem_newpool("ainit", (char *) 0, -1, + file, line); + if (!ap->freelist) + aerror(ap, "ainit failed (ie, newpool)"); + return ap; +} + +/* free all object in Area */ +void +_chmem_afreeall(ap, file, line) + Area *ap; + const char *file; + int line; +{ + _chmem_delpool((Chmem_poolp) ap->freelist, 0, file, line); + /* Kind of ugly, but it works */ + _chmem_ainit(ap, file, line); +} + +/* allocate object from Area */ +void * +_chmem_alloc(size, ap, file, line) + size_t size; + Area *ap; + const char *file; + int line; +{ + return _chmem_mallocp((Chmem_poolp) ap->freelist, size, file, line); +} + +/* change size of object -- like realloc */ +void * +_chmem_aresize(ptr, size, ap, file, line) + void *ptr; + size_t size; + Area *ap; + const char *file; + int line; +{ + if (!ptr) + /* Done as realloc(0, size) is not portable */ + return _chmem_mallocp((Chmem_poolp) ap->freelist, size, + file, line); + else + return _chmem_reallocp((Chmem_poolp) ap->freelist, ptr, size, + file, line); +} + +void +_chmem_afree(ptr, ap, file, line) + void *ptr; + Area *ap; + const char *file; + int line; +{ + return _chmem_freep((Chmem_poolp) ap->freelist, ptr, file, line); +} + +#else /* MEM_DEBUG */ + +# if DEBUG_ALLOC +void acheck ARGS((Area *ap)); +# define ACHECK(ap) acheck(ap) +# else /* DEBUG_ALLOC */ +# define ACHECK(ap) +# endif /* DEBUG_ALLOC */ + +#define ICELLS 200 /* number of Cells in small Block */ typedef union Cell Cell; typedef struct Block Block; /* * The Cells in a Block are organized as a set of objects. - * Each object (pointed to by dp) begins with a size in (dp-1)->size, + * Each object (pointed to by dp) begins with the block it is in + * (dp-2)->block, then has a size in (dp-1)->size, which is * followed with "size" data Cells. Free objects are * linked together via dp->next. */ +#define NOBJECT_FIELDS 2 /* the block and size `fields' */ + union Cell { size_t size; Cell *next; + Block *block; struct {int _;} junk; /* alignment */ + double djunk; /* alignment */ }; struct Block { Block *next; /* list of Blocks in Area */ + Block *prev; /* previous block in list */ Cell *freelist; /* object free list */ Cell *last; /* &b.cell[size] */ Cell cell [1]; /* [size] Cells for allocation */ }; -static Block aempty = {&aempty, aempty.cell, aempty.cell}; +static Block aempty = {&aempty, &aempty, aempty.cell, aempty.cell}; + +static void ablockfree ARGS((Block *bp, Area *ap)); +static void *asplit ARGS((Area *ap, Block *bp, Cell *fp, Cell *fpp, int cells)); /* create empty Area */ Area * @@ -44,6 +133,7 @@ ainit(ap) register Area *ap; { ap->freelist = &aempty; + ACHECK(ap); return ap; } @@ -55,15 +145,17 @@ afreeall(ap) register Block *bp; register Block *tmp; + ACHECK(ap); bp = ap->freelist; if (bp != NULL && bp != &aempty) { do { - tmp = bp->next; - free((void*)bp); - bp = tmp; + tmp = bp; + bp = bp->next; + free((void*)tmp); } while (bp != ap->freelist); ap->freelist = &aempty; } + ACHECK(ap); } /* allocate object from Area */ @@ -72,63 +164,99 @@ alloc(size, ap) size_t size; register Area *ap; { - int cells, split; - register Block *bp; - register Cell *dp, *fp, *fpp; + int cells, acells; + Block *bp = 0; + Cell *fp = 0, *fpp = 0; - if (size <= 0) { + ACHECK(ap); + if (size <= 0) aerror(ap, "allocate bad size"); - return NULL; - } cells = (unsigned)(size - 1) / sizeof(Cell) + 1; - /* find Cell large enough */ - for (bp = ap->freelist; ; bp = bp->next) { - for (fpp = NULL, fp = bp->freelist; - fp != bp->last; fpp = fp, fp = fpp->next) - if ((fp-1)->size >= cells) - goto Found; - - /* wrapped around Block list, create new Block */ - if (bp->next == ap->freelist) { - bp = (Block*) malloc(offsetof(Block, cell[ICELLS]) - + sizeof(bp->cell[0]) * cells); - if (bp == NULL) { - aerror(ap, "cannot allocate"); - return NULL; + /* allocate at least this many cells */ + acells = cells + NOBJECT_FIELDS; + + /* + * Only attempt to track small objects - let malloc deal + * with larger objects. (this way we don't have to deal with + * coalescing memory, or with releasing it to the system) + */ + if (cells <= ICELLS) { + /* find free Cell large enough */ + for (bp = ap->freelist; ; bp = bp->next) { + for (fpp = NULL, fp = bp->freelist; + fp != bp->last; fpp = fp, fp = fp->next) + { + if ((fp-1)->size >= cells) + goto Found; } - if (ap->freelist == &aempty) - bp->next = bp; - else { - bp->next = ap->freelist->next; - ap->freelist->next = bp; + /* wrapped around Block list, create new Block */ + if (bp->next == ap->freelist) { + bp = 0; + break; } - bp->last = bp->cell + ICELLS + cells; - fp = bp->freelist = bp->cell + 1; /* initial free list */ - (fp-1)->size = ICELLS + cells - 1; - fp->next = bp->last; - fpp = NULL; - break; } + /* Not much free space left? Allocate a big object this time */ + acells += ICELLS; } + if (bp == 0) { + bp = (Block*) malloc(offsetof(Block, cell[acells])); + if (bp == NULL) + aerror(ap, "cannot allocate"); + if (ap->freelist == &aempty) { + ap->freelist = bp->next = bp->prev = bp; + } else { + bp->next = ap->freelist->next; + ap->freelist->next->prev = bp; + ap->freelist->next = bp; + bp->prev = ap->freelist; + } + bp->last = bp->cell + acells; + /* initial free list */ + fp = bp->freelist = bp->cell + NOBJECT_FIELDS; + (fp-1)->size = acells - NOBJECT_FIELDS; + (fp-2)->block = bp; + fp->next = bp->last; + fpp = NULL; + } + Found: - ap->freelist = bp; - dp = fp; /* allocated object */ - split = (dp-1)->size - cells; + return asplit(ap, bp, fp, fpp, cells); +} + +/* Do the work of splitting an object into allocated and (possibly) unallocated + * objects. Returns the `allocated' object. + */ +static void * +asplit(ap, bp, fp, fpp, cells) + Area *ap; + Block *bp; + Cell *fp; + Cell *fpp; + int cells; +{ + Cell *dp = fp; /* allocated object */ + int split = (fp-1)->size - cells; + + ACHECK(ap); if (split < 0) aerror(ap, "allocated object too small"); - if (--split <= 0) { /* allocate all */ + if (split <= NOBJECT_FIELDS) { /* allocate all */ fp = fp->next; } else { /* allocate head, free tail */ + Cell *next = fp->next; /* needed, as cells may be 0 */ + ap->freelist = bp; /* next time, start looking for space here */ (fp-1)->size = cells; - fp += cells + 1; - (fp-1)->size = split; - fp->next = dp->next; + fp += cells + NOBJECT_FIELDS; + (fp-1)->size = split - NOBJECT_FIELDS; + (fp-2)->block = bp; + fp->next = next; } if (fpp == NULL) bp->freelist = fp; else fpp->next = fp; + ACHECK(ap); return (void*) dp; } @@ -140,35 +268,133 @@ aresize(ptr, size, ap) Area *ap; { int cells; - register Cell *dp = (Cell*) ptr; + Cell *dp = (Cell*) ptr; + int oldcells = dp ? (dp-1)->size : 0; - if (size <= 0) { + ACHECK(ap); + if (size <= 0) aerror(ap, "allocate bad size"); - return NULL; - } + /* New size (in cells) */ cells = (unsigned)(size - 1) / sizeof(Cell) + 1; - if (dp == NULL || (dp-1)->size < cells) { /* enlarge object */ - /* XXX check for available adjacent free block */ - ptr = alloc(size, ap); - if (dp != NULL) { - memcpy(ptr, dp, (dp-1)->size * sizeof(Cell)); - afree((void *) dp, ap); + /* Is this a large object? If so, let malloc deal with it + * directly (unless we are crossing the ICELLS border, in + * which case the alloc/free below handles it - this should + * cut down on fragmentation, and will also keep the code + * working (as it assumes size < ICELLS means it is not + * a `large object'). + */ + if (oldcells > ICELLS && cells > ICELLS) { + Block *bp = (dp-2)->block; + Block *nbp; + /* Saved in case realloc fails.. */ + Block *next = bp->next, *prev = bp->prev; + + if (bp->freelist != bp->last) + aerror(ap, "allocation resizing free pointer"); + nbp = realloc((void *) bp, + offsetof(Block, cell[cells + NOBJECT_FIELDS])); + if (!nbp) { + /* Have to clean up... */ + /* NOTE: If this code changes, similar changes may be + * needed in ablockfree(). + */ + if (next == bp) /* only block */ + ap->freelist = &aempty; + else { + next->prev = prev; + prev->next = next; + if (ap->freelist == bp) + ap->freelist = next; + } + aerror(ap, "cannot re-allocate"); + } + /* If location changed, keep pointers straight... */ + if (nbp != bp) { + if (next == bp) /* only one block */ + nbp->next = nbp->prev = nbp; + else { + next->prev = nbp; + prev->next = nbp; + } + if (ap->freelist == bp) + ap->freelist = nbp; + dp = nbp->cell + NOBJECT_FIELDS; + (dp-2)->block = nbp; + } + (dp-1)->size = cells; + nbp->last = nbp->cell + cells + NOBJECT_FIELDS; + nbp->freelist = nbp->last; + + ACHECK(ap); + return (void*) dp; + } + + /* Check if we can just grow this cell + * (need to check that cells < ICELLS so we don't make an + * object a `large' - that would mess everything up). + */ + if (dp && cells > oldcells && cells <= ICELLS) { + Cell *fp, *fpp; + Block *bp = (dp-2)->block; + int need = cells - oldcells - NOBJECT_FIELDS; + + /* XXX if we had a flag in an object indicating + * if the object was free/allocated, we could + * avoid this loop (perhaps) + */ + for (fpp = NULL, fp = bp->freelist; + fp != bp->last + && dp + oldcells + NOBJECT_FIELDS <= fp + ; fpp = fp, fp = fp->next) + { + if (dp + oldcells + NOBJECT_FIELDS == fp + && (fp-1)->size >= need) + { + Cell *np = asplit(ap, bp, fp, fpp, need); + /* May get more than we need here */ + (dp-1)->size += (np-1)->size + NOBJECT_FIELDS; + ACHECK(ap); + return ptr; + } } - } else { /* shrink object */ + } + + /* Check if we can just shrink this cell + * (if oldcells > ICELLS, this is a large object and we leave + * it to malloc...) + * Note: this also handles cells == oldcells (a no-op). + */ + if (dp && cells <= oldcells && oldcells <= ICELLS) { int split; - split = (dp-1)->size - cells; - if (--split <= 0) /* cannot split */ + split = oldcells - cells; + if (split <= NOBJECT_FIELDS) /* cannot split */ ; else { /* shrink head, free tail */ + Block *bp = (dp-2)->block; + (dp-1)->size = cells; - dp += cells + 1; - (dp-1)->size = split; + dp += cells + NOBJECT_FIELDS; + (dp-1)->size = split - NOBJECT_FIELDS; + (dp-2)->block = bp; afree((void*)dp, ap); } + /* ACHECK() done in afree() */ + return ptr; } - return (void*) ptr; + + /* Have to do it the hard way... */ + ptr = alloc(size, ap); + if (dp != NULL) { + size_t s = (dp-1)->size * sizeof(Cell); + if (s > size) + s = size; + memcpy(ptr, dp, size); + afree((void *) dp, ap); + } + /* ACHECK() done in alloc()/afree() */ + return ptr; } void @@ -180,28 +406,33 @@ afree(ptr, ap) register Cell *fp, *fpp; register Cell *dp = (Cell*)ptr; - /* find Block containing Cell */ - for (bp = ap->freelist; ; bp = bp->next) { - if (bp->cell <= dp && dp < bp->last) - break; - if (bp->next == ap->freelist) { - aerror(ap, "freeing with invalid area"); - return; - } + ACHECK(ap); + if (ptr == 0) + aerror(ap, "freeing null pointer"); + bp = (dp-2)->block; + + /* If this is a large object, just free it up... */ + /* Release object... */ + if ((dp-1)->size > ICELLS) { + ablockfree(bp, ap); + ACHECK(ap); + return; } + if (dp < &bp->cell[NOBJECT_FIELDS] || dp >= bp->last) + aerror(ap, "freeing memory outside of block (corrupted?)"); + /* find position in free list */ - for (fpp = NULL, fp = bp->freelist; fp < dp; fpp = fp, fp = fpp->next) + /* XXX if we had prev/next pointers for objects, this loop could go */ + for (fpp = NULL, fp = bp->freelist; fp < dp; fpp = fp, fp = fp->next) ; - if (fp == dp) { + if (fp == dp) aerror(ap, "freeing free object"); - return; - } /* join object with next */ - if (dp + (dp-1)->size == fp-1) { /* adjacent */ - (dp-1)->size += (fp-1)->size + 1; + if (dp + (dp-1)->size == fp-NOBJECT_FIELDS) { /* adjacent */ + (dp-1)->size += (fp-1)->size + NOBJECT_FIELDS; dp->next = fp->next; } else /* non-adjacent */ dp->next = fp; @@ -209,14 +440,141 @@ afree(ptr, ap) /* join previous with object */ if (fpp == NULL) bp->freelist = dp; - else if (fpp + (fpp-1)->size == dp-1) { /* adjacent */ - (fpp-1)->size += (dp-1)->size + 1; + else if (fpp + (fpp-1)->size == dp-NOBJECT_FIELDS) { /* adjacent */ + (fpp-1)->size += (dp-1)->size + NOBJECT_FIELDS; fpp->next = dp->next; } else /* non-adjacent */ fpp->next = dp; + + /* If whole block is free (and we have some other blocks + * around), release this block back to the system... + */ + if (bp->next != bp && bp->freelist == bp->cell + NOBJECT_FIELDS + && bp->freelist + (bp->freelist-1)->size == bp->last + /* XXX and the other block has some free memory? */ + ) + ablockfree(bp, ap); + ACHECK(ap); +} + +static void +ablockfree(bp, ap) + Block *bp; + Area *ap; +{ + /* NOTE: If this code changes, similar changes may be + * needed in alloc() (where realloc fails). + */ + + if (bp->next == bp) /* only block */ + ap->freelist = &aempty; + else { + bp->next->prev = bp->prev; + bp->prev->next = bp->next; + if (ap->freelist == bp) + ap->freelist = bp->next; + } + free((void*) bp); +} + +# if DEBUG_ALLOC +void +acheck(ap) + Area *ap; +{ + Block *bp, *bpp; + Cell *dp, *dptmp, *fp; + int ok = 1; + int isfree; + static int disabled; + + if (disabled) + return; + + if (!ap) { + disabled = 1; + aerror(ap, "acheck: null area pointer"); + } + + bp = ap->freelist; + if (!bp) { + disabled = 1; + aerror(ap, "acheck: null area freelist"); + } + + /* Nothing to check... */ + if (bp == &aempty) + return; + + bpp = ap->freelist->prev; + while (1) { + if (bp->prev != bpp) { + shellf("acheck: bp->prev != previous\n"); + ok = 0; + } + fp = bp->freelist; + for (dp = &bp->cell[NOBJECT_FIELDS]; dp != bp->last; ) { + if ((dp-2)->block != bp) { + shellf("acheck: fragment's block is wrong\n"); + ok = 0; + } + isfree = dp == fp; + if ((dp-1)->size == 0 && isfree) { + shellf("acheck: 0 size frag\n"); + ok = 0; + } + if ((dp-1)->size > ICELLS + && !isfree + && (dp != &bp->cell[NOBJECT_FIELDS] + || dp + (dp-1)->size != bp->last)) + { + shellf("acheck: big cell doesn't make up whole block\n"); + ok = 0; + } + if (isfree) { + if (dp->next <= dp) { + shellf("acheck: free fragment's next <= self\n"); + ok = 0; + } + if (dp->next > bp->last) { + shellf("acheck: free fragment's next > last\n"); + ok = 0; + } + fp = dp->next; + } + dptmp = dp + (dp-1)->size; + if (dptmp > bp->last) { + shellf("acheck: next frag out of range\n"); + ok = 0; + break; + } else if (dptmp != bp->last) { + dptmp += NOBJECT_FIELDS; + if (dptmp > bp->last) { + shellf("acheck: next frag just out of range\n"); + ok = 0; + break; + } + } + if (isfree && dptmp == fp && dptmp != bp->last) { + shellf("acheck: adjacent free frags\n"); + ok = 0; + } else if (dptmp > fp) { + shellf("acheck: free frag list messed up\n"); + ok = 0; + } + dp = dptmp; + } + bpp = bp; + bp = bp->next; + if (bp == ap->freelist) + break; + } + if (!ok) { + disabled = 1; + aerror(ap, "acheck failed"); + } } -#if DEBUG_ALLOC void aprint(ap, ptr, size) register Area *ap; @@ -233,9 +591,11 @@ aprint(ap, ptr, size) shellf("aprint: area is empty\n"); else { int i; - Cell *fp; + Cell *dp, *fp; + Block *bpp; - for (i = 0; !i || bp != ap->freelist; bp = bp->next, i++) { + bpp = ap->freelist->prev; + for (i = 0; ; i++) { if (ptr) { void *eptr = (void *) (((char *) ptr) + size); /* print block only if it overlaps ptr/size */ @@ -247,42 +607,172 @@ aprint(ap, ptr, size) shellf("aprint: overlap of 0x%p .. 0x%p\n", ptr, eptr); } - shellf("aprint: block %2d: 0x%p .. 0x%p (%d)\n", i, + if (bp->prev != bpp || bp->next->prev != bp) + shellf( + "aprint: BAD prev pointer: bp %p, bp->prev %p, bp->next %p, bpp=%p\n", + bp, bp->prev, bp->next, bpp); + shellf("aprint: block %2d (p=%p,%p,n=%p): 0x%p .. 0x%p (%ld)\n", i, + bp->prev, bp, bp->next, bp->cell, bp->last, - (char *) bp->last - (char *) bp->cell); - for (fp = bp->freelist; fp != bp->last; fp = fp->next) + (long) ((char *) bp->last - (char *) bp->cell)); + fp = bp->freelist; + if (bp->last <= bp->cell + NOBJECT_FIELDS) shellf( - "aprint: 0x%p .. 0x%p (%d) free\n", - (fp-1), (fp-1) + (fp-1)->size, - (fp-1)->size * sizeof(Cell)); + "aprint: BAD bp->last too small: %p <= %p\n", + bp->last, bp->cell + NOBJECT_FIELDS); + if (bp->freelist < bp->cell + NOBJECT_FIELDS + || bp->freelist > bp->last) + shellf( + "aprint: BAD bp->freelist %p out of range: %p .. %p\n", + bp->freelist, + bp->cell + NOBJECT_FIELDS, bp->last); + for (dp = bp->cell; dp != bp->last ; ) { + dp += NOBJECT_FIELDS; + shellf( + "aprint: 0x%p .. 0x%p (%ld) %s\n", + (dp-NOBJECT_FIELDS), + (dp-NOBJECT_FIELDS) + (dp-1)->size + + NOBJECT_FIELDS, + (long) ((dp-1)->size + NOBJECT_FIELDS) + * sizeof(Cell), + dp == fp ? "free" : "allocated"); + if ((dp-2)->block != bp) + shellf( + "aprint: BAD dp->block %p != bp %p\n", + (dp-2)->block, bp); + if (dp > bp->last) + shellf( + "aprint: BAD dp gone past block: %p > %p\n", + dp, bp->last); + if (dp > fp) + shellf( + "aprint: BAD dp gone past free: %p > %p\n", + dp, fp); + if (dp == fp) { + fp = fp->next; + if (fp < dp || fp > bp->last) + shellf( + "aprint: BAD free object %p out of range: %p .. %p\n", + fp, + dp, bp->last); + } + dp += (dp-1)->size; + } + bpp = bp; + bp = bp->next; + if (bp == ap->freelist) + break; } } } -#endif /* DEBUG_ALLOC */ +# endif /* DEBUG_ALLOC */ - -#ifdef TEST_ALLOC +# ifdef TEST_ALLOC Area a; +FILE *myout; -main(int argc, char **argv) { - int i; - char *p [9]; - +int +main(int argc, char **argv) +{ + char buf[1024]; + struct info { + int size; + void *value; + }; + struct info info[1024 * 2]; + int size, ident; + int lineno = 0; + + myout = stdout; ainit(&a); - for (i = 0; i < 9; i++) { - p[i] = alloc(124, &a); - printf("alloc: %x\n", p[i]); + while (fgets(buf, sizeof(buf), stdin)) { + lineno++; + if (buf[0] == '\n' || buf[0] == '#') + continue; + if (sscanf(buf, " alloc %d = i%d", &size, &ident) == 2) { + if (ident < 0 || ident > NELEM(info)) { + fprintf(stderr, "bad ident (%d) on line %d\n", + ident, lineno); + exit(1); + } + info[ident].value = alloc(info[ident].size = size, &a); + printf("%p = alloc(%d) [%d,i%d]\n", + info[ident].value, info[ident].size, + lineno, ident); + memset(info[ident].value, 1, size); + continue; + } + if (sscanf(buf, " afree i%d", &ident) == 1) { + if (ident < 0 || ident > NELEM(info)) { + fprintf(stderr, "bad ident (%d) on line %d\n", + ident, lineno); + exit(1); + } + afree(info[ident].value, &a); + printf("afree(%p) [%d,i%d]\n", info[ident].value, + lineno, ident); + continue; + } + if (sscanf(buf, " aresize i%d , %d", &ident, &size) == 2) { + void *value; + if (ident < 0 || ident > NELEM(info)) { + fprintf(stderr, "bad ident (%d) on line %d\n", + ident, lineno); + exit(1); + } + value = info[ident].value; + info[ident].value = aresize(value, + info[ident].size = size, + &a); + printf("%p = aresize(%p, %d) [%d,i%d]\n", + info[ident].value, value, info[ident].size, + lineno, ident); + memset(info[ident].value, 1, size); + continue; + } + if (sscanf(buf, " aprint i%d , %d", &ident, &size) == 2) { + if (ident < 0 || ident > NELEM(info)) { + fprintf(stderr, "bad ident (%d) on line %d\n", + ident, lineno); + exit(1); + } + printf("aprint(%p, %d) [%d,i%d]\n", + info[ident].value, size, lineno, ident); + aprint(&a, info[ident].value, size); + continue; + } + if (sscanf(buf, " aprint %d", &ident) == 1) { + if (ident < 0 || ident > NELEM(info)) { + fprintf(stderr, "bad ident (%d) on line %d\n", + ident, lineno); + exit(1); + } + printf("aprint(0, 0) [%d]\n", lineno); + aprint(&a, 0, 0); + continue; + } + if (sscanf(buf, " afreeall %d", &ident) == 1) { + printf("afreeall() [%d]\n", lineno); + afreeall(&a); + memset(info, 0, sizeof(info)); + continue; + } + fprintf(stderr, "unrecognized line (line %d)\n", + lineno); + exit(1); } - for (i = 1; i < argc; i++) - afree(p[atoi(argv[i])], &a); - afreeall(&a); return 0; } -void aerror(Area *ap, const char *msg) { +void +aerror(Area *ap, const char *msg) +{ + printf("aerror: %s\n", msg); + fflush(stdout); abort(); } -#endif +# endif /* TEST_ALLOC */ +#endif /* MEM_DEBUG */ diff --git a/bin/ksh/c_ksh.c b/bin/ksh/c_ksh.c index 1435aa9c57d..d43faf3d8ce 100644 --- a/bin/ksh/c_ksh.c +++ b/bin/ksh/c_ksh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: c_ksh.c,v 1.7 1997/06/19 13:58:37 kstailey Exp $ */ +/* $OpenBSD: c_ksh.c,v 1.8 1998/06/25 19:01:44 millert Exp $ */ /* * built-in Korn commands: c_* @@ -571,7 +571,7 @@ c_typeset(wp) struct tbl *vp, **p; Tflag fset = 0, fclr = 0; int thing = 0, func = 0, local = 0; - const char *options = "L#R#UZ#fi#lrtux"; /* see comment below */ + const char *options = "L#R#UZ#fi#lprtux"; /* see comment below */ char *fieldstr, *basestr; int field, base; int optc; @@ -609,11 +609,11 @@ c_typeset(wp) flag = 0; switch (optc) { case 'L': - flag |= LJUST; + flag = LJUST; fieldstr = builtin_opt.optarg; break; case 'R': - flag |= RJUST; + flag = RJUST; fieldstr = builtin_opt.optarg; break; case 'U': @@ -621,36 +621,39 @@ c_typeset(wp) * upper/lower case. If this option is changed, * need to change the -U below as well */ - flag |= INT_U; + flag = INT_U; break; case 'Z': - flag |= ZEROFIL; + flag = ZEROFIL; fieldstr = builtin_opt.optarg; break; case 'f': func = 1; break; case 'i': - flag |= INTEGER; + flag = INTEGER; basestr = builtin_opt.optarg; break; case 'l': - flag |= LCASEV; + flag = LCASEV; break; - case 'p': /* posix export/readonly -p flag */ + case 'p': /* posix export/readonly -p flag. + * typset -p is the same as typeset (in pdksh); + * here for compatability with ksh93. + */ pflag = 1; break; case 'r': - flag |= RDONLY; + flag = RDONLY; break; case 't': - flag |= TRACE; + flag = TRACE; break; case 'u': - flag |= UCASEV_AL; /* upper case / autoload */ + flag = UCASEV_AL; /* upper case / autoload */ break; case 'x': - flag |= EXPORT; + flag = EXPORT; break; case '?': return 1; @@ -687,8 +690,19 @@ c_typeset(wp) return 1; } if (wp[builtin_opt.optind]) { - /* Take care of exclusions */ - /* setting these attributes clears the others, unless they + /* 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 @@ -696,26 +710,6 @@ c_typeset(wp) fclr |= ~fset & (LJUST|RJUST|ZEROFIL|UCASEV_AL|LCASEV|INTEGER |INT_U|INT_L); - fclr &= ~fset; /* set wins */ - if ((fset & (ZEROFIL|LJUST)) == ZEROFIL) { - fset |= RJUST; - fclr &= ~RJUST; - } - if (fset & LCASEV) /* LCASEV has priority */ - fclr |= UCASEV_AL; - else if (fset & UCASEV_AL) - fclr |= LCASEV; - if (fset & LJUST) /* LJUST has priority */ - fclr |= RJUST; - else if (fset & RJUST) - fclr |= LJUST; - if ((fset | fclr) & INTEGER) { - if (!(fset | fclr) & INT_U) - fclr |= INT_U; - if (!(fset | fclr) & INT_L) - fclr |= INT_L; - } - fset &= ~fclr; /* in case of something like -LR */ } /* set variables and attributes */ @@ -740,7 +734,10 @@ c_typeset(wp) f->flag &= ~fclr; } else fptreef(shl_stdout, 0, - "function %s %T\n", + 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]); @@ -758,7 +755,9 @@ c_typeset(wp) if (flag && (vp->flag & flag) == 0) continue; if (thing == '-') - fptreef(shl_stdout, 0, "function %s %T\n", + 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); @@ -804,7 +803,7 @@ c_typeset(wp) if ((vp->flag&INT_U)) shprintf("-U "); if (vp->flag&ARRAY) - shprintf("%s[%d]\n", vp->name,vp->index); + shprintf("%s[%d]\n", vp->name, vp->index); else shprintf("%s\n", vp->name); } else { @@ -839,15 +838,21 @@ c_alias(wp) char **wp; { struct table *t = &aliases; - int rv = 0, rflag = 0, tflag, Uflag = 0; + int rv = 0, rflag = 0, tflag, Uflag = 0, pflag = 0; + int prefix = 0; Tflag xflag = 0; int optc; - while ((optc = ksh_getopt(wp, &builtin_opt, "drtUx")) != EOF) + 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; @@ -865,8 +870,16 @@ c_alias(wp) 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.. */ @@ -884,13 +897,19 @@ c_alias(wp) return c_unalias((char **) args); } + if (*wp == NULL) { struct tbl *ap, **p; for (p = tsort(t); (ap = *p++) != NULL; ) if ((ap->flag & (ISSET|xflag)) == (ISSET|xflag)) { - shprintf("%s=", ap->name); - print_value_quoted(ap->val.s); + 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); } } @@ -908,8 +927,13 @@ c_alias(wp) if (val == NULL && !tflag && !xflag) { ap = tsearch(t, alias, h); if (ap != NULL && (ap->flag&ISSET)) { - shprintf("%s=", ap->name); - print_value_quoted(ap->val.s); + 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); @@ -934,7 +958,11 @@ c_alias(wp) } else ap->flag &= ~ISSET; } - ap->flag |= DEFINED|xflag; + ap->flag |= DEFINED; + if (prefix == '+') + ap->flag &= ~xflag; + else + ap->flag |= xflag; if (val) afree(alias, ATEMP); } @@ -1220,17 +1248,14 @@ c_kill(wp) return rv; } -static Getopt user_opt; /* parsing state for getopts builtin command */ -static int getopts_noset; /* stop OPTIND assign from resetting state */ - void getopts_reset(val) int val; { - if (!getopts_noset && val >= 1) { + if (val >= 1) { ksh_getopt_reset(&user_opt, GF_NONAME | (Flag(FPOSIX) ? 0 : GF_PLUSOPT)); - user_opt.optind = val; + user_opt.optind = user_opt.uoptind = val; } } @@ -1306,9 +1331,7 @@ c_getopts(wp) * on this staying). */ if (optc != '?') { - getopts_noset = 1; - setint(global("OPTIND"), (long) user_opt.optind); - getopts_noset = 0; + user_opt.uoptind = user_opt.optind; } if (user_opt.optarg == (char *) 0) diff --git a/bin/ksh/c_sh.c b/bin/ksh/c_sh.c index 0691aa51af5..88663520550 100644 --- a/bin/ksh/c_sh.c +++ b/bin/ksh/c_sh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: c_sh.c,v 1.6 1997/08/05 21:49:54 grr Exp $ */ +/* $OpenBSD: c_sh.c,v 1.7 1998/06/25 19:01:46 millert Exp $ */ /* * built-in Bourne commands @@ -11,6 +11,7 @@ static char *clocktos ARGS((clock_t t)); + /* :, false and true */ int c_label(wp) @@ -179,15 +180,16 @@ c_dot(wp) 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, (int *) 0); + file = search(cp, path, R_OK, &err); if (file == NULL) { - bi_errorf("%s: not found", cp); + bi_errorf("%s: %s", cp, err ? strerror(err) : "not found"); return 1; } @@ -353,7 +355,7 @@ c_read(wp) expanding = 0; if (c == '\n') { c = 0; - if (Flag(FTALKING) && isatty(fd)) { + if (Flag(FTALKING_I) && isatty(fd)) { /* set prompt in case this is * called from .profile or $ENV */ @@ -656,10 +658,10 @@ c_times(wp) struct tms all; (void) ksh_times(&all); - shprintf("Shell: %8s user ", clocktos(all.tms_utime)); - shprintf("%8s system\n", clocktos(all.tms_stime)); - shprintf("Kids: %8s user ", clocktos(all.tms_cutime)); - shprintf("%8s system\n", clocktos(all.tms_cstime)); + shprintf("Shell: %8ss user ", clocktos(all.tms_utime)); + shprintf("%8ss system\n", clocktos(all.tms_stime)); + shprintf("Kids: %8ss user ", clocktos(all.tms_cutime)); + shprintf("%8ss system\n", clocktos(all.tms_cstime)); return 0; } @@ -672,22 +674,49 @@ timex(t, f) struct op *t; int f; { +#define TF_NONE 0 +#define TF_NOREAL BIT(1) /* don't report real time */ +#define TF_POSIX BIT(2) /* report in posix format */ int rv; - struct tms t0, t1; + struct tms t0, t1, tms; clock_t t0t, t1t; + clock_t real; + int tf = TF_NONE; extern clock_t j_usrtime, j_systime; /* computed by j_wait */ - j_usrtime = j_systime = 0; t0t = ksh_times(&t0); - rv = execute(t->left, f); - t1t = ksh_times(&t1); + if (t->left) { + /* + * Two ways of getting cpu usage of a command: just use t0 + * and t1 (which will get cpu usage from other jobs that + * finish while we are executing t->left), or get the + * cpu usage of t->left. at&t ksh does the former, while + * pdksh tries to do the later (the j_usrtime hack doesn't + * really work as it only counts the last job). + */ + j_usrtime = j_systime = 0; + rv = execute(t->left, f); + t1t = ksh_times(&t1); + real = t1t - t0t; + tms.tms_utime = t1.tms_utime - t0.tms_utime + j_usrtime; + tms.tms_stime = t1.tms_stime - t0.tms_stime + j_systime; + } else { /* ksh93 - report shell times (shell+kids) */ + tf |= TF_NOREAL; + real = 0; + tms.tms_utime = t0.tms_utime + t0.tms_cutime; + tms.tms_stime = t0.tms_stime + t0.tms_cstime; + rv = 0; + } - shf_fprintf(shl_out, "%8s real ", clocktos(t1t - t0t)); - shf_fprintf(shl_out, "%8s user ", - clocktos(t1.tms_utime - t0.tms_utime + j_usrtime)); - shf_fprintf(shl_out, "%8s system ", - clocktos(t1.tms_stime - t0.tms_stime + j_systime)); - shf_fprintf(shl_out, newline); + if (!(tf & TF_NOREAL)) + shf_fprintf(shl_out, + tf & TF_POSIX ? "real %8s\n" : "%8ss real ", + clocktos(real)); + shf_fprintf(shl_out, tf & TF_POSIX ? "user %8s\n" : "%8ss user ", + clocktos(tms.tms_utime)); + shf_fprintf(shl_out, tf & TF_POSIX ? "user %8s\n" : "%8ss system\n", + clocktos(tms.tms_stime)); + shf_flush(shl_out); return rv; } @@ -696,16 +725,18 @@ static char * clocktos(t) clock_t t; { - static char temp[20]; + static char temp[22]; /* enough for 64 bit clock_t */ register int i; register char *cp = temp + sizeof(temp); + /* note: posix says must use max precision, ie, if clk_tck is + * 1000, must print 3 places after decimal (if non-zero, else 1). + */ if (CLK_TCK != 100) /* convert to 1/100'ths */ t = (t < 1000000000/CLK_TCK) ? (t * 100) / CLK_TCK : (t / CLK_TCK) * 100; *--cp = '\0'; - *--cp = 's'; for (i = -2; i <= 0 || t > 0; i++) { if (i == 0) *--cp = '.'; @@ -727,9 +758,16 @@ c_exec(wp) for (i = 0; i < NUFILE; i++) { if (e->savefd[i] > 0) close(e->savefd[i]); - /* keep anything > 2 private */ + /* + * For ksh keep anything > 2 private, + * for sh, let them be (POSIX says what + * happens is unspecified and the bourne shell + * keeps them open). + */ +#ifdef KSH if (!Flag(FSH) && i > 2 && e->savefd[i]) fd_clexec(i); +#endif /* KSH */ } e->savefd = NULL; } diff --git a/bin/ksh/c_test.c b/bin/ksh/c_test.c index 16703ff4960..49654f344f9 100644 --- a/bin/ksh/c_test.c +++ b/bin/ksh/c_test.c @@ -1,4 +1,4 @@ -/* $OpenBSD: c_test.c,v 1.4 1997/06/19 13:58:38 kstailey Exp $ */ +/* $OpenBSD: c_test.c,v 1.5 1998/06/25 19:01:47 millert Exp $ */ /* * test(1); version 7-like -- author Erik Baalbergen @@ -157,6 +157,12 @@ c_test(wp) } if (argc == 1) { opnd1 = (*te.getopnd)(&te, TO_NONOP, 1); + /* Historically, -t by itself test if fd 1 + * is a file descriptor, but POSIX says its + * a string test... + */ + if (!Flag(FPOSIX) && strcmp(opnd1, "-t") == 0) + break; res = (*te.eval)(&te, TO_STNZE, opnd1, (char *) 0, 1); if (invert & 1) @@ -326,11 +332,13 @@ test_eval(te, op, opnd1, opnd2, do_eval) if (opnd1 && !bi_getn(opnd1, &res)) { te->flags |= TEF_ERROR; res = 0; - } else + } 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 == geteuid(); + return test_stat(opnd1, &b1) == 0 && b1.st_uid == ksheuid; case TO_FILGID: /* -G */ return test_stat(opnd1, &b1) == 0 && b1.st_gid == getegid(); /* @@ -410,15 +418,20 @@ test_stat(path, statb) return stat(path, statb); } -/* Another nasty kludge to handle Korn's bizarre /dev/fd hack */ +/* Routine to handle Korn's /dev/fd hack, and to deal with X_OK on + * non-directories when running as root. + */ static int test_eaccess(path, mode) const char *path; int mode; { + int res; + #if !defined(HAVE_DEV_FD) int fd; + /* Note: doesn't handle //dev/fd, etc.. (this is ok) */ if (strncmp(path, "/dev/fd/", 8) == 0 && getn(path + 8, &fd)) { int flags; @@ -431,7 +444,28 @@ test_eaccess(path, mode) } #endif /* !HAVE_DEV_FD */ - return eaccess(path, mode); + /* On most (all?) unixes, access() says everything is executable for + * root - avoid this on files by using stat(). + */ + if ((mode & X_OK) && ksheuid == 0) { + struct stat statb; + + if (stat(path, &statb) < 0) + res = -1; + else if (S_ISDIR(statb.st_mode)) + res = 0; + else + res = (statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) + ? 0 : -1; + /* Need to check other permissions? If so, use access() as + * this will deal with root on NFS. + */ + if (res == 0 && (mode & (R_OK|W_OK))) + res = eaccess(path, mode); + } else + res = eaccess(path, mode); + + return res; } int diff --git a/bin/ksh/config.h b/bin/ksh/config.h index c949a254ee2..4c08c1194a8 100644 --- a/bin/ksh/config.h +++ b/bin/ksh/config.h @@ -1,4 +1,4 @@ -/* $OpenBSD: config.h,v 1.2 1996/08/25 11:56:34 downsj Exp $ */ +/* $OpenBSD: config.h,v 1.3 1998/06/25 19:01:49 millert Exp $ */ /* config.h. Generated automatically by configure. */ /* config.h.in. Generated automatically from configure.in by autoheader. */ @@ -250,6 +250,9 @@ /* Define if you have the getgroups function. */ /* #undef HAVE_GETGROUPS */ +/* Define if you have the getpagesize function. */ +#define HAVE_GETPAGESIZE 1 + /* Define if you have the getrusage function. */ /* #undef HAVE_GETRUSAGE */ @@ -286,6 +289,9 @@ /* 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 diff --git a/bin/ksh/edit.c b/bin/ksh/edit.c index 2d4b96b4d0f..c0d3af2a38e 100644 --- a/bin/ksh/edit.c +++ b/bin/ksh/edit.c @@ -1,4 +1,4 @@ -/* $OpenBSD: edit.c,v 1.6 1997/06/19 13:58:39 kstailey Exp $ */ +/* $OpenBSD: edit.c,v 1.7 1998/06/25 19:01:50 millert Exp $ */ /* * Command line editing - common code @@ -725,15 +725,7 @@ x_locate_word(buf, buflen, pos, startp, is_commandp) *is_commandp = 0; return 0; } - - if (pos == buflen) { - if (pos == 0) { /* empty buffer? */ - *startp = pos; - *is_commandp = 1; - return 0; - } - pos--; - } + /* The case where pos == buflen happens to take care of itself... */ start = pos; /* Keep going backwards to start of word (has effect of allowing diff --git a/bin/ksh/emacs.c b/bin/ksh/emacs.c index db515363a1d..d0d756651e2 100644 --- a/bin/ksh/emacs.c +++ b/bin/ksh/emacs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: emacs.c,v 1.4 1997/06/19 13:58:39 kstailey Exp $ */ +/* $OpenBSD: emacs.c,v 1.5 1998/06/25 19:01:51 millert Exp $ */ /* * Emacs-like command line editing and history @@ -1244,12 +1244,12 @@ x_meta_yank(c) len = strlen(killstack[killtp]); x_goto(xcp - len); x_delete(len, FALSE); - do { + do { if (killtp == 0) killtp = KILLSIZE - 1; else killtp--; - } while (killstack[killtp] == 0); + } while (killstack[killtp] == 0); x_ins(killstack[killtp]); return KSTD; } @@ -2012,15 +2012,8 @@ x_prev_histword(c) { register char *rcp; char *cp; - char **hp; - hp = x_histp-1; - if (hp < history || hp > histptr) - { - x_e_putc(BEL); - return KSTD; - } - cp = *hp; + cp = *histptr; if (x_arg_defaulted) { rcp = &cp[strlen(cp) - 1]; /* diff --git a/bin/ksh/eval.c b/bin/ksh/eval.c index 292294d08ee..d4ace8e4a9b 100644 --- a/bin/ksh/eval.c +++ b/bin/ksh/eval.c @@ -1,4 +1,4 @@ -/* $OpenBSD: eval.c,v 1.4 1997/06/19 13:58:40 kstailey Exp $ */ +/* $OpenBSD: eval.c,v 1.5 1998/06/25 19:01:53 millert Exp $ */ /* * Expansion - quoting, separation, substitution, globbing @@ -40,7 +40,7 @@ typedef struct Expand { #define IFS_WS 1 /* have seen IFS white-space */ #define IFS_NWS 2 /* have seen IFS non-white-space */ -static int varsub ARGS((Expand *xp, char *sp, char *word, int *stypep)); +static int varsub ARGS((Expand *xp, char *sp, char *word, int *stypep, int *slenp)); static int comsub ARGS((Expand *xp, char *cp)); static char *trimsub ARGS((char *str, char *pat, int how)); static void glob ARGS((char *cp, XPtrV *wp, int markdirs)); @@ -275,21 +275,23 @@ expand(cp, wp, f) continue; case OSUBST: /* ${{#}var{:}[=+-?#%]word} */ /* format is: - * OSUBST plain-variable-part \0 - * compiled-word-part CSUBST + * OSUBST [{x] plain-variable-part \0 + * compiled-word-part CSUBST [}x] * This is were all syntax checking gets done... */ { - char *varname = sp; + char *varname = ++sp; /* skip the { or x (}) */ int stype; + int slen; sp = strchr(sp, '\0') + 1; /* skip variable */ - type = varsub(&x, varname, sp, &stype); + type = varsub(&x, varname, sp, &stype, &slen); if (type < 0) { char endc; char *str, *end; end = (char *) wdscan(sp, CSUBST); + /* ({) the } or x is already skipped */ endc = *end; *end = EOS; str = snptreef((char *) 0, 64, "%S", @@ -317,12 +319,8 @@ expand(cp, wp, f) st->var = x.var; st->quote = quote; /* skip qualifier(s) */ - if (stype) { - sp += 2; - /* :[-+=?] or double [#%] */ - if (stype & 0x80) - sp += 2; - } + if (stype) + sp += slen; switch (stype & 0x7f) { case '#': case '%': @@ -330,6 +328,12 @@ expand(cp, wp, f) f = DOPAT | (f&DONTRUNCOMMAND) | DOTEMP_; quote = 0; + /* Prepend open pattern (so | + * in a trim will work as + * expected) + */ + *dp++ = MAGIC; + *dp++ = '@' + 0x80; break; case '=': /* Enabling tilde expansion @@ -345,13 +349,9 @@ expand(cp, wp, f) * sense though, since ~ is * a arithmetic operator. */ -#if !defined(__hppa) || __GNUC__ != 2 /* gcc 2.3.3 on hp-pa dies on this - ifdef goes away as soon as I get a new version of gcc.. */ if (!(x.var->flag & INTEGER)) f |= DOASNTILDE|DOTILDE; f |= DOTEMP_; -#else - f |= DOTEMP_|DOASNTILDE|DOTILDE; -#endif /* These will be done after the * value has been assigned. */ @@ -373,6 +373,7 @@ expand(cp, wp, f) 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; @@ -382,6 +383,8 @@ expand(cp, wp, f) 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 @@ -681,15 +684,17 @@ expand(cp, wp, f) * Prepare to generate the string returned by ${} substitution. */ static int -varsub(xp, sp, word, stypep) +varsub(xp, sp, word, stypep, slenp) Expand *xp; char *sp; char *word; - int *stypep; + int *stypep; /* becomes qualifier type */ + int *slenp; /* " " len (=, :=, etc.) valid iff *stypep != 0 */ { int c; int state; /* next state: XBASE, XARG, XSUB, XNULLSUB */ int stype; /* substitution type */ + int slen; char *p; struct tbl *vp; @@ -731,29 +736,34 @@ varsub(xp, sp, word, stypep) /* Check for qualifiers in word part */ stype = 0; - c = *word == CHAR ? word[1] : 0; + c = word[slen = 0] == CHAR ? word[1] : 0; if (c == ':') { + slen += 2; stype = 0x80; - c = word[2] == CHAR ? word[3] : 0; + c = word[slen + 0] == CHAR ? word[slen + 1] : 0; } - if (ctype(c, C_SUBOP1)) + if (ctype(c, C_SUBOP1)) { + slen += 2; stype |= c; - else if (stype) /* :, :# or :% is not ok */ - return -1; - else if (ctype(c, C_SUBOP2)) { + } else if (ctype(c, C_SUBOP2)) { /* Note: ksh88 allows :%, :%%, etc */ + slen += 2; stype = c; - if (word[2] == CHAR && c == word[3]) + 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 */ + case '%': /* can't trim a vector (yet) */ case '#': return -1; } @@ -772,7 +782,7 @@ varsub(xp, sp, word, stypep) switch (stype & 0x7f) { case '=': /* can't assign to a vector */ - case '%': /* can't trim a vector */ + case '%': /* can't trim a vector (yet) */ case '#': return -1; } @@ -1168,11 +1178,12 @@ debunk(dp, sp) memcpy(dp, sp, s - sp); for (d = dp + (s - sp); *s; s++) if (!ISMAGIC(*s) || !(*++s & 0x80) - || !strchr("*+?@!", *s & 0x7f)) + || !strchr("*+?@! ", *s & 0x7f)) *d++ = *s; else { /* extended pattern operators: *+?@! */ - *d++ = *s & 0x7f; + if ((*s & 0x7f) != ' ') + *d++ = *s & 0x7f; *d++ = '('; } *d = '\0'; diff --git a/bin/ksh/exec.c b/bin/ksh/exec.c index 7c98d956239..4e446d0beda 100644 --- a/bin/ksh/exec.c +++ b/bin/ksh/exec.c @@ -1,4 +1,4 @@ -/* $OpenBSD: exec.c,v 1.10 1997/09/12 04:38:05 millert Exp $ */ +/* $OpenBSD: exec.c,v 1.11 1998/06/25 19:01:54 millert Exp $ */ /* * execute command tree @@ -462,16 +462,18 @@ comexec(t, tp, ap, flags) (void)&rv; #endif +#ifdef KSH /* snag the last argument for $_ XXX not the same as at&t ksh, * which only seems to set $_ after a newline (but not in * functions/dot scripts, but in interactive and scipt) - * perhaps save last arg here and set it in shell()?. */ - if (!Flag(FSH) && *(lastp = ap)) { + if (!Flag(FSH) && Flag(FTALKING) && *(lastp = ap)) { while (*++lastp) ; setstr(typeset("_", LOCAL, 0, 0, 0), *--lastp); } +#endif /* KSH */ /* Deal with the shell builtins builtin, exec and command since * they can be followed by other commands. This must be done before @@ -631,11 +633,20 @@ comexec(t, tp, ap, flags) 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; - getopts_reset(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; @@ -702,10 +713,12 @@ comexec(t, tp, ap, flags) break; } +#ifdef KSH if (!Flag(FSH)) { /* set $_ to program's full path */ setstr(typeset("_", LOCAL|EXPORT, 0, 0, 0), tp->val.s); } +#endif /* KSH */ if (flags&XEXEC) { j_exit(); @@ -1090,14 +1103,15 @@ search_access(path, mode, errnop) 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)))) + else if (!S_ISREG(statb.st_mode) + /* This 'cause access() says root can execute everything */ + || (mode == X_OK + && !(statb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)))) { ret = -1; err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES; } - if (err && errnop) + if (err && errnop && !*errnop) *errnop = err; return ret; #else /* !OS2 */ @@ -1162,7 +1176,7 @@ search_access1(path, mode, errnop) ret = -1; err = S_ISDIR(statb.st_mode) ? EISDIR : EACCES; } - if (err && errnop) + if (err && errnop && !*errnop) *errnop = err; return ret; } @@ -1277,7 +1291,7 @@ iosetup(iop, tp) struct stat statb; if (iotype != IOHERE) - cp = evalonestr(cp, DOTILDE|(Flag(FTALKING) ? DOGLOB : 0)); + cp = evalonestr(cp, DOTILDE|(Flag(FTALKING_I) ? DOGLOB : 0)); /* Used for tracing and error messages to print expanded cp */ iotmp = *iop; diff --git a/bin/ksh/expand.h b/bin/ksh/expand.h index d0ab5937c1d..9bd9df4f61e 100644 --- a/bin/ksh/expand.h +++ b/bin/ksh/expand.h @@ -1,9 +1,10 @@ -/* $OpenBSD: expand.h,v 1.1 1996/08/14 06:19:11 downsj Exp $ */ +/* $OpenBSD: expand.h,v 1.2 1998/06/25 19:01:56 millert Exp $ */ /* * Expanding strings */ +#define X_EXTRA 8 /* this many extra bytes in X string */ #if 0 /* Usage */ XString xs; @@ -17,9 +18,9 @@ return Xclose(xs, xp); /* resize string */ /* * NOTE: - * The Xcheck and Xinit macros have a magic + 8 in the lengths. This is - * so that you can put up to 4 characters in a XString before calling - * Xcheck. (See yylex in lex.c) + * 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 */ @@ -35,7 +36,7 @@ typedef char * XStringP; #define Xinit(xs, xp, length, area) do { \ (xs).len = length; \ (xs).areap = (area); \ - (xs).beg = alloc((xs).len + 8, (xs).areap); \ + (xs).beg = alloc((xs).len + X_EXTRA, (xs).areap); \ (xs).end = (xs).beg + (xs).len; \ xp = (xs).beg; \ } while (0) diff --git a/bin/ksh/history.c b/bin/ksh/history.c index f9c4b28e2e7..acdc2c4ec4d 100644 --- a/bin/ksh/history.c +++ b/bin/ksh/history.c @@ -1,4 +1,4 @@ -/* $OpenBSD: history.c,v 1.7 1997/06/19 13:58:42 kstailey Exp $ */ +/* $OpenBSD: history.c,v 1.8 1998/06/25 19:01:58 millert Exp $ */ /* * command history @@ -27,7 +27,7 @@ # ifdef OS2 # define HISTFILE "history.ksh" # else /* OS2 */ -# define HISTFILE ".pdksh_hist" +# define HISTFILE ".pdksh_history" # endif /* OS2 */ # endif @@ -486,7 +486,7 @@ histnum(n) current = histptr; curpos = last; return last; - } else { + } else { current = &history[n]; curpos = n; return n; diff --git a/bin/ksh/io.c b/bin/ksh/io.c index 4a2f1cc88e2..e52ade63626 100644 --- a/bin/ksh/io.c +++ b/bin/ksh/io.c @@ -1,4 +1,4 @@ -/* $OpenBSD: io.c,v 1.4 1997/06/19 13:58:42 kstailey Exp $ */ +/* $OpenBSD: io.c,v 1.5 1998/06/25 19:02:00 millert Exp $ */ /* * shell buffered IO and formatted output @@ -8,6 +8,8 @@ #include "sh.h" #include "ksh_stat.h" +static int initio_done; + /* * formatted output functions */ @@ -127,7 +129,10 @@ void error_prefix(fileline) int fileline; { - shf_fprintf(shl_out, "%s: ", kshname + (*kshname == '-')); + /* 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); @@ -147,6 +152,8 @@ shellf(fmt, va_alist) { 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); @@ -191,6 +198,7 @@ initio() shf_fdopen(1, SHF_WR, shl_stdout); /* force buffer allocation */ shf_fdopen(2, SHF_WR, shl_out); shf_fdopen(2, SHF_WR, shl_spare); /* force buffer allocation */ + initio_done = 1; } /* A dup2() with error checking */ diff --git a/bin/ksh/jobs.c b/bin/ksh/jobs.c index e79f2ac731c..c8df8939146 100644 --- a/bin/ksh/jobs.c +++ b/bin/ksh/jobs.c @@ -1,4 +1,4 @@ -/* $OpenBSD: jobs.c,v 1.6 1997/06/19 13:58:43 kstailey Exp $ */ +/* $OpenBSD: jobs.c,v 1.7 1998/06/25 19:02:02 millert Exp $ */ /* * Process and job control @@ -439,12 +439,13 @@ exchild(t, flags, close_fd) Job *j; int rv = 0; int forksleep; - int orig_flags = flags; int ischild; - flags &= ~(XFORK|XPCLOSE|XCCLOSE|XCOPROC); if (flags & XEXEC) - return execute(t, flags); + /* 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 */ @@ -459,6 +460,8 @@ exchild(t, flags, close_fd) /* 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; @@ -583,13 +586,13 @@ exchild(t, flags, close_fd) #endif /* JOBS */ /* used to close pipe input fd */ - if (close_fd >= 0 && (((orig_flags & XPCLOSE) && !ischild) - || ((orig_flags & XCCLOSE) && ischild))) + if (close_fd >= 0 && (((flags & XPCLOSE) && !ischild) + || ((flags & XCCLOSE) && ischild))) close(close_fd); if (ischild) { /* child */ #ifdef KSH /* Do this before restoring signal */ - if (orig_flags & XCOPROC) + if (flags & XCOPROC) coproc_cleanup(FALSE); #endif /* KSH */ #ifdef JOB_SIGS @@ -616,7 +619,7 @@ exchild(t, flags, close_fd) SS_RESTORE_IGN|SS_FORCE); setsig(&sigtraps[SIGQUIT], SIG_IGN, SS_RESTORE_IGN|SS_FORCE); - if (!(orig_flags & (XPIPEI | XCOPROC))) { + if (!(flags & (XPIPEI | XCOPROC))) { int fd = open("/dev/null", 0); (void) ksh_dup2(fd, 0, TRUE); close(fd); @@ -631,7 +634,7 @@ exchild(t, flags, close_fd) Flag(FTALKING) = 0; tty_close(); cleartraps(); - execute(t, flags|XEXEC); /* no return */ + execute(t, (flags & XERROK) | XEXEC); /* no return */ internal_errorf(0, "exchild: execute() returned"); unwind(LLEAVE); /* NOTREACHED */ @@ -647,7 +650,7 @@ exchild(t, flags, close_fd) #endif /* TTY_PGRP */ j_startjob(j); #ifdef KSH - if (orig_flags & XCOPROC) { + 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 */ diff --git a/bin/ksh/ksh.1 b/bin/ksh/ksh.1 index cb05da41bcb..1c31e9a512f 100644 --- a/bin/ksh/ksh.1 +++ b/bin/ksh/ksh.1 @@ -1,5 +1,5 @@ '\" t -.\" $OpenBSD: ksh.1,v 1.6 1998/03/27 20:20:41 deraadt Exp $ +.\" $OpenBSD: ksh.1,v 1.7 1998/06/25 19:02:03 millert Exp $ .\"{{{}}} .\"{{{ Notes about man page .\" - use the pseudo-macros .sh( and .sh) to begin and end sh-specific @@ -716,9 +716,11 @@ splitting arguments with spaces). The following parameters are set and/or used by the shell: .\"{{{ _ .IP "\fB_\fP \fI(underscore)\fP" -In interactive use, this parameter is set to the last word of the -previous command. When a command is executed, this parameter is set to -the full path of the command and is placed in the command's environment. +When an external command is executed by the shell, this parameter is +set in the environment of the new process to the path of the executed +command. +In interactive use, this parameter is also set in the parent shell to +the last word of the previous command. When \fBMAILPATH\fP messages are evaluated, this parameter contains the name of the file that changed (see \fBMAILPATH\fP parameter below). .\"}}} @@ -1360,6 +1362,11 @@ the \fB$0\fP parameter is set to the name of the function parameter assignments preceeding function calls are not kept in the shell environment (executing Bourne-style functions will keep assignments). +.IP \ \ \(bu +\fBOPTIND\fP is saved/reset and restored on entry and exit from the function +so \fBgetopts\fP can be used properly both inside and outside the function +(Bourne-style functions leave \fBOPTIND\fP untouched, so using \fBgetopts\fP +inside a function interferes with using \fBgetopts\fP outside the function). .nr PD \n(P2 In the future, the following differences will also be added: .nr P2 \n(PD @@ -1450,6 +1457,11 @@ a i in 1 2; do echo i=$i j=$j; done .ft P .RE uses parameter \fBi\fP in posix mode, \fBj\fP in non-posix mode. +.IP \ \ \(bu +test: in posix mode, the expression "\fB-t\fP" (preceded by +some number of "\fB!\fP" arguments) is always true as it is a non-zero length +string; in non-posix mode, it tests if file descriptor 1 is a tty (\fIi.e.\fP, +the \fIfd\fP argument to the \fB-t\fP test may be left out and defaults to 1). .nr PD \n(P2 .\"}}} .\"{{{ Command Execution (built-in commands) @@ -1525,11 +1537,23 @@ environment the command is used in. The null command. Exit status is set to zero. .\"}}} -.\"{{{ alias [ -d | -t [ -r ] ] [-x] [name1[=value1] ...] -.IP "\fBalias\fP [ \fB\-d\fP | \fB\-t\fP [\fB\-r\fP] ] [\fB\-x\fP] [\fIname1\fP[\fB=\fP\fIvalue1\fP] ...]" -Without arguments, \fBalias\fP lists all aliases and their values. For -any name without a value, its value is listed. Any name with a value -defines an alias (see Aliases above). +.\"{{{ alias [ -d | +-t [ -r ] ] [+-px] [+-] [name1[=value1] ...] +.IP "\fBalias\fP [ \fB\-d\fP | \fB\(+-t\fP [\fB\-r\fP] ] [\fB\(+-px\fP] [\fB\(+-\fP] [\fIname1\fP[\fB=\fP\fIvalue1\fP] ...]" +Without arguments, \fBalias\fP lists all aliases. +For any name without a value, the existing alias is listed. +Any name with a value defines an alias (see Aliases above). +.sp +When listing aliases, one of two formats is used: +normally, aliases are listed as \fIname\fP\fB=\fP\fIvalue\fP, where +\fIvalue\fP is quoted; if options were preceded with \fB+\fP +or a lone \fB+\fP is given on the command line, only \fIname\fP +is printed. +In addition, if the \fB\-p\fP option is used, each alias +is prefixed with the string "\fBalias\fP\ ". +.sp +The \fB\-x\fP option sets (\fB+x\fP clears) the export attribute of an alias, +or, if no names are given, lists the aliases with the export attribute +(exporting an alias has no affect). .sp The \fB\-x\fP option sets the export attribute of an alias, or, if no names are given, lists the aliases with the export attribute @@ -1647,8 +1671,11 @@ The command is executed without forking, replacing the shell process. If no arguments are given, any IO redirection is permanent and the shell is not replaced. Any file descriptors greater than 2 which are opened or \fIdup\fP(2)-ed -in this way are not made available to other executed commands (\fIi.e.\fP, +in this way are not +made available to other executed commands (\fIi.e.\fP, commands that are not built-in to the shell). +Note that the Bourne shell differs here: it does pass these +file descriptors on. .\"}}} .\"{{{ exit [status] .IP "\fBexit\fP [\fIstatus\fP]" @@ -1707,9 +1734,7 @@ options. \fIoptstring\fP contains the option letters that \fBgetopts\fP is to recognize. If a letter is followed by a colon, the option is expected to have an argument. -Arguments containing options must all start with either a \fB\-\fP or -a \fB+\fP, options that do not take arguments may be grouped in a single -argument. +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 @@ -2140,44 +2165,46 @@ T} \fIfile\fP \-ef \fIfile\fP T{ first \fIfile\fP is the same file as second \fIfile\fP T} -\-t [\fIfd\fP] T{ +\-t\ [\fIfd\fP] T{ file descriptor is a tty device. -Default value of \fIfd\fP is 1. +If the posix option (\fBset \-o posix\fP, see POSIX Mode above) is not +set, \fIfd\fP may be left out, in which case it is taken to be 1 +(the behaviour differs due to the special POSIX rules described below). T} \fIstring\fP T{ \fIstring\fP is not empty T} -\-z \fIstring\fP T{ +\-z\ \fIstring\fP T{ \fIstring\fP is empty T} -\-n \fIstring\fP T{ +\-n\ \fIstring\fP T{ \fIstring\fP is not empty T} -\fIstring\fP = \fIstring\fP T{ +\fIstring\fP\ =\ \fIstring\fP T{ strings are equal T} -\fIstring\fP == \fIstring\fP T{ +\fIstring\fP\ ==\ \fIstring\fP T{ strings are equal T} -\fIstring\fP != \fIstring\fP T{ +\fIstring\fP\ !=\ \fIstring\fP T{ strings are not equal T} -\fInumber\fP \-eq \fInumber\fP T{ +\fInumber\fP\ \-eq\ \fInumber\fP T{ numbers compare equal T} -\fInumber\fP \-ne \fInumber\fP T{ +\fInumber\fP\ \-ne\ \fInumber\fP T{ numbers compare not equal T} -\fInumber\fP \-ge \fInumber\fP T{ +\fInumber\fP\ \-ge\ \fInumber\fP T{ numbers compare greater than or equal T} -\fInumber\fP \-gt \fInumber\fP T{ +\fInumber\fP\ \-gt\ \fInumber\fP T{ numbers compare greater than T} -\fInumber\fP \-le \fInumber\fP T{ +\fInumber\fP\ \-le\ \fInumber\fP T{ numbers compare less than or equal T} -\fInumber\fP \-lt \fInumber\fP T{ +\fInumber\fP\ \-lt\ \fInumber\fP T{ numbers compare less than T} .TE @@ -2253,8 +2280,8 @@ The original Korn shell's \fBDEBUG\fP trap and the handling of \fBERR\fP and .IP \fBtrue\fP A command that exits with a zero value. .\"}}} -.\"{{{ typeset [[+-Ulrtux] [-L[n]] [-R[n]] [-Z[n]] [-i[n]] | -f [-tux]] [name[=value] ...] -.IP "\fBtypeset\fP [[\(+-Ulrtux] [\fB\-L\fP[\fIn\fP]] [\fB\-R\fP[\fIn\fP]] [\fB\-Z\fP[\fIn\fP]] [\fB\-i\fP[\fIn\fP]] | \fB\-f\fP [\fB\-tux\fP]] [\fIname\fP[\fB=\fP\fIvalue\fP] ...]" +.\"{{{ typeset [[+-Ulprtux] [-L[n]] [-R[n]] [-Z[n]] [-i[n]] | -f [-tux]] [name[=value] ...] +.IP "\fBtypeset\fP [[\(+-Ulprtux] [\fB\-L\fP[\fIn\fP]] [\fB\-R\fP[\fIn\fP]] [\fB\-Z\fP[\fIn\fP]] [\fB\-i\fP[\fIn\fP]] | \fB\-f\fP [\fB\-tux\fP]] [\fIname\fP[\fB=\fP\fIvalue\fP] ...]" Display or set parameter attributes. With no \fIname\fP arguments, parameter attributes are displayed: if no options arg used, the current attributes of all parameters are printed as typeset @@ -2318,6 +2345,11 @@ lower case. (In the original Korn shell, this parameter meant `long integer' when used with the \fB\-i\fP option). T} +\-p T{ +Print complete 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 compatability). +T} \-r T{ Readonly attribute: parameters with the this attribute may not be assigned to or unset. @@ -3266,7 +3298,7 @@ ftp.cs.mun.ca:pub/pdksh/. This shell is based on the public domain 7th edition Bourne shell clone by Charles Forsyth and parts of the BRL shell by Doug A.\& Gwyn, Doug Kingston, Ron Natalie, Arnold Robbins, Lou Salkind and others. The first release -was created by Eric Gisin, and it was subsequently maintained by +of pdksh was created by Eric Gisin, and it was subsequently maintained by John R.\& MacMillan (chance!john@sq.sq.com), and Simon J.\& Gerraty (sjg@zen.void.oz.au). The current maintainer is Michael Rendell (michael@cs.mun.ca). diff --git a/bin/ksh/ksh.1tbl b/bin/ksh/ksh.1tbl index aa5ec922b0c..532b0dff8a0 100644 --- a/bin/ksh/ksh.1tbl +++ b/bin/ksh/ksh.1tbl @@ -1,5 +1,5 @@ '\" t -.\" $OpenBSD: ksh.1tbl,v 1.6 1998/03/27 20:20:41 deraadt Exp $ +.\" $OpenBSD: ksh.1tbl,v 1.7 1998/06/25 19:02:03 millert Exp $ .\"{{{}}} .\"{{{ Notes about man page .\" - use the pseudo-macros .sh( and .sh) to begin and end sh-specific @@ -716,9 +716,11 @@ splitting arguments with spaces). The following parameters are set and/or used by the shell: .\"{{{ _ .IP "\fB_\fP \fI(underscore)\fP" -In interactive use, this parameter is set to the last word of the -previous command. When a command is executed, this parameter is set to -the full path of the command and is placed in the command's environment. +When an external command is executed by the shell, this parameter is +set in the environment of the new process to the path of the executed +command. +In interactive use, this parameter is also set in the parent shell to +the last word of the previous command. When \fBMAILPATH\fP messages are evaluated, this parameter contains the name of the file that changed (see \fBMAILPATH\fP parameter below). .\"}}} @@ -1360,6 +1362,11 @@ the \fB$0\fP parameter is set to the name of the function parameter assignments preceeding function calls are not kept in the shell environment (executing Bourne-style functions will keep assignments). +.IP \ \ \(bu +\fBOPTIND\fP is saved/reset and restored on entry and exit from the function +so \fBgetopts\fP can be used properly both inside and outside the function +(Bourne-style functions leave \fBOPTIND\fP untouched, so using \fBgetopts\fP +inside a function interferes with using \fBgetopts\fP outside the function). .nr PD \n(P2 In the future, the following differences will also be added: .nr P2 \n(PD @@ -1450,6 +1457,11 @@ a i in 1 2; do echo i=$i j=$j; done .ft P .RE uses parameter \fBi\fP in posix mode, \fBj\fP in non-posix mode. +.IP \ \ \(bu +test: in posix mode, the expression "\fB-t\fP" (preceded by +some number of "\fB!\fP" arguments) is always true as it is a non-zero length +string; in non-posix mode, it tests if file descriptor 1 is a tty (\fIi.e.\fP, +the \fIfd\fP argument to the \fB-t\fP test may be left out and defaults to 1). .nr PD \n(P2 .\"}}} .\"{{{ Command Execution (built-in commands) @@ -1525,11 +1537,23 @@ environment the command is used in. The null command. Exit status is set to zero. .\"}}} -.\"{{{ alias [ -d | -t [ -r ] ] [-x] [name1[=value1] ...] -.IP "\fBalias\fP [ \fB\-d\fP | \fB\-t\fP [\fB\-r\fP] ] [\fB\-x\fP] [\fIname1\fP[\fB=\fP\fIvalue1\fP] ...]" -Without arguments, \fBalias\fP lists all aliases and their values. For -any name without a value, its value is listed. Any name with a value -defines an alias (see Aliases above). +.\"{{{ alias [ -d | +-t [ -r ] ] [+-px] [+-] [name1[=value1] ...] +.IP "\fBalias\fP [ \fB\-d\fP | \fB\(+-t\fP [\fB\-r\fP] ] [\fB\(+-px\fP] [\fB\(+-\fP] [\fIname1\fP[\fB=\fP\fIvalue1\fP] ...]" +Without arguments, \fBalias\fP lists all aliases. +For any name without a value, the existing alias is listed. +Any name with a value defines an alias (see Aliases above). +.sp +When listing aliases, one of two formats is used: +normally, aliases are listed as \fIname\fP\fB=\fP\fIvalue\fP, where +\fIvalue\fP is quoted; if options were preceded with \fB+\fP +or a lone \fB+\fP is given on the command line, only \fIname\fP +is printed. +In addition, if the \fB\-p\fP option is used, each alias +is prefixed with the string "\fBalias\fP\ ". +.sp +The \fB\-x\fP option sets (\fB+x\fP clears) the export attribute of an alias, +or, if no names are given, lists the aliases with the export attribute +(exporting an alias has no affect). .sp The \fB\-x\fP option sets the export attribute of an alias, or, if no names are given, lists the aliases with the export attribute @@ -1647,8 +1671,11 @@ The command is executed without forking, replacing the shell process. If no arguments are given, any IO redirection is permanent and the shell is not replaced. Any file descriptors greater than 2 which are opened or \fIdup\fP(2)-ed -in this way are not made available to other executed commands (\fIi.e.\fP, +in this way are not +made available to other executed commands (\fIi.e.\fP, commands that are not built-in to the shell). +Note that the Bourne shell differs here: it does pass these +file descriptors on. .\"}}} .\"{{{ exit [status] .IP "\fBexit\fP [\fIstatus\fP]" @@ -1707,9 +1734,7 @@ options. \fIoptstring\fP contains the option letters that \fBgetopts\fP is to recognize. If a letter is followed by a colon, the option is expected to have an argument. -Arguments containing options must all start with either a \fB\-\fP or -a \fB+\fP, options that do not take arguments may be grouped in a single -argument. +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 @@ -2140,44 +2165,46 @@ T} \fIfile\fP \-ef \fIfile\fP T{ first \fIfile\fP is the same file as second \fIfile\fP T} -\-t [\fIfd\fP] T{ +\-t\ [\fIfd\fP] T{ file descriptor is a tty device. -Default value of \fIfd\fP is 1. +If the posix option (\fBset \-o posix\fP, see POSIX Mode above) is not +set, \fIfd\fP may be left out, in which case it is taken to be 1 +(the behaviour differs due to the special POSIX rules described below). T} \fIstring\fP T{ \fIstring\fP is not empty T} -\-z \fIstring\fP T{ +\-z\ \fIstring\fP T{ \fIstring\fP is empty T} -\-n \fIstring\fP T{ +\-n\ \fIstring\fP T{ \fIstring\fP is not empty T} -\fIstring\fP = \fIstring\fP T{ +\fIstring\fP\ =\ \fIstring\fP T{ strings are equal T} -\fIstring\fP == \fIstring\fP T{ +\fIstring\fP\ ==\ \fIstring\fP T{ strings are equal T} -\fIstring\fP != \fIstring\fP T{ +\fIstring\fP\ !=\ \fIstring\fP T{ strings are not equal T} -\fInumber\fP \-eq \fInumber\fP T{ +\fInumber\fP\ \-eq\ \fInumber\fP T{ numbers compare equal T} -\fInumber\fP \-ne \fInumber\fP T{ +\fInumber\fP\ \-ne\ \fInumber\fP T{ numbers compare not equal T} -\fInumber\fP \-ge \fInumber\fP T{ +\fInumber\fP\ \-ge\ \fInumber\fP T{ numbers compare greater than or equal T} -\fInumber\fP \-gt \fInumber\fP T{ +\fInumber\fP\ \-gt\ \fInumber\fP T{ numbers compare greater than T} -\fInumber\fP \-le \fInumber\fP T{ +\fInumber\fP\ \-le\ \fInumber\fP T{ numbers compare less than or equal T} -\fInumber\fP \-lt \fInumber\fP T{ +\fInumber\fP\ \-lt\ \fInumber\fP T{ numbers compare less than T} .TE @@ -2253,8 +2280,8 @@ The original Korn shell's \fBDEBUG\fP trap and the handling of \fBERR\fP and .IP \fBtrue\fP A command that exits with a zero value. .\"}}} -.\"{{{ typeset [[+-Ulrtux] [-L[n]] [-R[n]] [-Z[n]] [-i[n]] | -f [-tux]] [name[=value] ...] -.IP "\fBtypeset\fP [[\(+-Ulrtux] [\fB\-L\fP[\fIn\fP]] [\fB\-R\fP[\fIn\fP]] [\fB\-Z\fP[\fIn\fP]] [\fB\-i\fP[\fIn\fP]] | \fB\-f\fP [\fB\-tux\fP]] [\fIname\fP[\fB=\fP\fIvalue\fP] ...]" +.\"{{{ typeset [[+-Ulprtux] [-L[n]] [-R[n]] [-Z[n]] [-i[n]] | -f [-tux]] [name[=value] ...] +.IP "\fBtypeset\fP [[\(+-Ulprtux] [\fB\-L\fP[\fIn\fP]] [\fB\-R\fP[\fIn\fP]] [\fB\-Z\fP[\fIn\fP]] [\fB\-i\fP[\fIn\fP]] | \fB\-f\fP [\fB\-tux\fP]] [\fIname\fP[\fB=\fP\fIvalue\fP] ...]" Display or set parameter attributes. With no \fIname\fP arguments, parameter attributes are displayed: if no options arg used, the current attributes of all parameters are printed as typeset @@ -2318,6 +2345,11 @@ lower case. (In the original Korn shell, this parameter meant `long integer' when used with the \fB\-i\fP option). T} +\-p T{ +Print complete 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 compatability). +T} \-r T{ Readonly attribute: parameters with the this attribute may not be assigned to or unset. @@ -3266,7 +3298,7 @@ ftp.cs.mun.ca:pub/pdksh/. This shell is based on the public domain 7th edition Bourne shell clone by Charles Forsyth and parts of the BRL shell by Doug A.\& Gwyn, Doug Kingston, Ron Natalie, Arnold Robbins, Lou Salkind and others. The first release -was created by Eric Gisin, and it was subsequently maintained by +of pdksh was created by Eric Gisin, and it was subsequently maintained by John R.\& MacMillan (chance!john@sq.sq.com), and Simon J.\& Gerraty (sjg@zen.void.oz.au). The current maintainer is Michael Rendell (michael@cs.mun.ca). diff --git a/bin/ksh/lex.c b/bin/ksh/lex.c index 5db0c81accd..cf92b67454a 100644 --- a/bin/ksh/lex.c +++ b/bin/ksh/lex.c @@ -1,4 +1,4 @@ -/* $OpenBSD: lex.c,v 1.10 1997/09/12 04:39:32 millert Exp $ */ +/* $OpenBSD: lex.c,v 1.11 1998/06/25 19:02:06 millert Exp $ */ /* * lexical analysis and source input @@ -7,14 +7,61 @@ #include "sh.h" #include <ctype.h> + +/* Structure to keep track of the lexing state and the various pieces of info + * needed for each particular state. + */ +typedef struct lex_state Lex_state; +struct lex_state { + int ls_state; + union { + /* $(...) */ + struct scsparen_info { + int nparen; /* count open parenthesis */ + int csstate; /* XXX remove */ +#define ls_scsparen ls_info.u_scsparen + } u_scsparen; + + /* $((...)) */ + struct sasparen_info { + int nparen; /* count open parenthesis */ + int start; /* marks start of $(( in output str */ +#define ls_sasparen ls_info.u_sasparen + } u_sasparen; + + /* ((...)) */ + struct sletparen_info { + int nparen; /* count open parenthesis */ +#define ls_sletparen ls_info.u_sletparen + } u_sletparen; + + /* `...` */ + struct sbquote_info { + int indquotes; /* true if in double quotes: "`...`" */ +#define ls_sbquote ls_info.u_sbquote + } u_sbquote; + + Lex_state *base; /* used to point to next state block */ + } ls_info; +}; + +typedef struct State_info State_info; +struct State_info { + Lex_state *base; + Lex_state *end; +}; + + static void readhere ARGS((struct ioword *iop)); static int getsc__ ARGS((void)); static void getsc_line ARGS((Source *s)); +static int getsc_bn ARGS((void)); static char *get_brace_var ARGS((XString *wsp, char *wp)); static int arraysub ARGS((char **strp)); static const char *ungetsc ARGS((int c)); -static int getsc_bn ARGS((void)); static void gethere ARGS((void)); +static Lex_state *push_state_ ARGS((State_info *si, Lex_state *old_end)); +static Lex_state *pop_state_ ARGS((State_info *si, Lex_state *old_end)); static int backslash_skip; static int ignore_backslash_newline; @@ -25,6 +72,21 @@ static int ignore_backslash_newline; /* 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 @@ -38,36 +100,38 @@ int yylex(cf) int cf; { + Lex_state states[STATE_BSIZE], *statep; + State_info state_info; register int c, state; - char states [64], *statep = states; /* XXX overflow check */ XString ws; /* expandable output word */ register char *wp; /* output word pointer */ - register char *sp, *dp; - char UNINITIALIZED(*ddparen_start); - int istate; - int UNINITIALIZED(c2); - int UNINITIALIZED(nparen), UNINITIALIZED(csstate); - int UNINITIALIZED(ndparen); - int UNINITIALIZED(indquotes); + char *sp, *dp; + int c2; Again: + states[0].ls_state = -1; + states[0].ls_info.base = (Lex_state *) 0; + statep = &states[1]; + state_info.base = states; + state_info.end = &states[STATE_BSIZE]; + Xinit(ws, wp, 64, ATEMP); backslash_skip = 0; ignore_backslash_newline = 0; if (cf&ONEWORD) - istate = SWORD; + state = SWORD; #ifdef KSH else if (cf&LETEXPR) { *wp++ = OQUOTE; /* enclose arguments in (double) quotes */ - istate = SDPAREN; - ndparen = 0; + state = SLETPAREN; + statep->ls_sletparen.nparen = 0; } #endif /* KSH */ else { /* normal lexing */ - istate = (cf & HEREDELIM) ? SHEREDELIM : SBASE; + state = (cf & HEREDELIM) ? SHEREDELIM : SBASE; while ((c = getsc()) == ' ' || c == '\t') ; if (c == '#') { @@ -87,10 +151,13 @@ yylex(cf) cf |= ALIAS; } + /* Initial state: one of SBASE SHEREDELIM SWORD SASPAREN */ + statep->ls_state = state; + /* collect non-special or quoted characters to form word */ - for (*statep = state = istate; - !((c = getsc()) == 0 || ((state == SBASE || state == SHEREDELIM) - && ctype(c, C_LEX1))); ) + while (!((c = getsc()) == 0 + || ((state == SBASE || state == SHEREDELIM) + && ctype(c, C_LEX1)))) { Xcheck(ws, wp); switch (state) { @@ -136,7 +203,7 @@ yylex(cf) if (c2 == '(' /*)*/ ) { *wp++ = OPAT; *wp++ = c; - *++statep = state = SPATTERN; + PUSH_STATE(SPATTERN); break; } ungetsc(c2); @@ -157,13 +224,13 @@ yylex(cf) *wp++ = QCHAR, *wp++ = c; break; case '\'': - *++statep = state = SSQUOTE; *wp++ = OQUOTE; ignore_backslash_newline++; + PUSH_STATE(SSQUOTE); break; case '"': - *++statep = state = SDQUOTE; *wp++ = OQUOTE; + PUSH_STATE(SDQUOTE); break; default: goto Subst; @@ -193,39 +260,41 @@ yylex(cf) if (c == '(') /*)*/ { c = getsc(); if (c == '(') /*)*/ { - *++statep = state = SDDPAREN; - nparen = 2; - ddparen_start = wp; + PUSH_STATE(SASPAREN); + statep->ls_sasparen.nparen = 2; + statep->ls_sasparen.start = + Xsavepos(ws, wp); *wp++ = EXPRSUB; } else { ungetsc(c); - *++statep = state = SPAREN; - nparen = 1; - csstate = 0; + 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, - * wrap @(...) around the pattern - * (allows easy handling of ${a#b|c}) + * treat (,|,) specially in STBRACE. */ - c = getsc(); if (c == '#' || c == '%') { - *wp++ = CHAR, *wp++ = c; - if ((c2 = getsc()) == c) - *wp++ = CHAR, *wp++ = c; - else - ungetsc(c2); - *wp++ = OPAT, *wp++ = '@'; - *++statep = state = STBRACE; + ungetsc(c); + PUSH_STATE(STBRACE); } else { ungetsc(c); - *++statep = state = SBRACE; + PUSH_STATE(SBRACE); } } else if (ctype(c, C_ALPHA)) { *wp++ = OSUBST; + *wp++ = 'X'; do { Xcheck(ws, wp); *wp++ = c; @@ -233,30 +302,62 @@ yylex(cf) } 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 '`': - *++statep = state = SBQUOTE; + 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 + * $ ` \."). */ - indquotes = 0; - if (!Flag(FPOSIX)) - for (sp = statep; sp > states; --sp) - if (*sp == SDQUOTE) - indquotes = 1; + 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; @@ -265,7 +366,7 @@ yylex(cf) case SSQUOTE: if (c == '\'') { - state = *--statep; + POP_STATE(); *wp++ = CQUOTE; ignore_backslash_newline--; } else @@ -274,35 +375,35 @@ yylex(cf) case SDQUOTE: if (c == '"') { - state = *--statep; + POP_STATE(); *wp++ = CQUOTE; } else goto Subst; break; - case SPAREN: /* $( .. ) */ + 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 (csstate) { + switch (statep->ls_scsparen.csstate) { case 0: /* normal */ switch (c) { case '(': - nparen++; + statep->ls_scsparen.nparen++; break; case ')': - nparen--; + statep->ls_scsparen.nparen--; break; case '\\': - csstate = 1; + statep->ls_scsparen.csstate = 1; break; case '"': - csstate = 2; + statep->ls_scsparen.csstate = 2; break; case '\'': - csstate = 4; + statep->ls_scsparen.csstate = 4; ignore_backslash_newline++; break; } @@ -310,58 +411,63 @@ yylex(cf) case 1: /* backslash in normal mode */ case 3: /* backslash in double quotes */ - --csstate; + --statep->ls_scsparen.csstate; break; case 2: /* double quotes */ if (c == '"') - csstate = 0; + statep->ls_scsparen.csstate = 0; else if (c == '\\') - csstate = 3; + statep->ls_scsparen.csstate = 3; break; case 4: /* single quotes */ if (c == '\'') { - csstate = 0; + statep->ls_scsparen.csstate = 0; ignore_backslash_newline--; } break; } - if (nparen == 0) { - state = *--statep; + if (statep->ls_scsparen.nparen == 0) { + POP_STATE(); *wp++ = 0; /* end of COMSUB */ } else *wp++ = c; break; - case SDDPAREN: /* $(( .. )) */ + case SASPAREN: /* $(( .. )) */ /* todo: deal with $((...); (...)) properly */ /* XXX should nest using existing state machine * (embed "..", $(...), etc.) */ if (c == '(') - nparen++; + statep->ls_sasparen.nparen++; else if (c == ')') { - nparen--; - if (nparen == 1) { + statep->ls_sasparen.nparen--; + if (statep->ls_sasparen.nparen == 1) { /*(*/ if ((c2 = getsc()) == ')') { - state = *--statep; + POP_STATE(); *wp++ = 0; /* end of EXPRSUB */ break; } else { + char *s; + ungetsc(c2); /* mismatched parenthesis - * assume we were really * parsing a $(..) expression */ - memmove(ddparen_start + 1, - ddparen_start, - wp - ddparen_start); - *ddparen_start++ = COMSUB; - *ddparen_start = '('; /*)*/ + s = Xrestpos(ws, wp, + statep->ls_sasparen.start); + memmove(s + 1, s, wp - s); + *s++ = COMSUB; + *s = '('; /*)*/ wp++; - csstate = 0; - *statep = state = SPAREN; + statep->ls_scsparen.nparen = 1; + statep->ls_scsparen.csstate = 0; + state = statep->ls_state + = SCSPAREN; + } } } @@ -371,23 +477,26 @@ yylex(cf) case SBRACE: /*{*/ if (c == '}') { - state = *--statep; + POP_STATE(); *wp++ = CSUBST; + *wp++ = /*{*/ '}'; } else goto Sbase1; break; case STBRACE: - /* same as SBRACE, except | is saved as SPAT and - * CPAT is added at the end. - */ + /* Same as SBRACE, except (,|,) treated specially */ /*{*/ if (c == '}') { - state = *--statep; - *wp++ = CPAT; + 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; @@ -395,7 +504,7 @@ yylex(cf) case SBQUOTE: if (c == '`') { *wp++ = 0; - state = *--statep; + POP_STATE(); } else if (c == '\\') { switch (c = getsc()) { case '\\': @@ -403,7 +512,7 @@ yylex(cf) *wp++ = c; break; case '"': - if (indquotes) { + if (statep->ls_sbquote.indquotes) { *wp++ = c; break; } @@ -423,11 +532,11 @@ yylex(cf) goto Subst; #ifdef KSH - case SDPAREN: /* LETEXPR: (( ... )) */ + case SLETPAREN: /* LETEXPR: (( ... )) */ /*(*/ if (c == ')') { - if (ndparen > 0) - --ndparen; + if (statep->ls_sletparen.nparen > 0) + --statep->ls_sletparen.nparen; /*(*/ else if ((c2 = getsc()) == ')') { c = 0; @@ -440,7 +549,7 @@ yylex(cf) * are lost, but at&t ksh doesn't count them * either */ - ++ndparen; + ++statep->ls_sletparen.nparen; goto Sbase2; #endif /* KSH */ @@ -460,11 +569,11 @@ yylex(cf) *wp++ = c; } } else if (c == '\'') { - *++statep = state = SSQUOTE; + PUSH_STATE(SSQUOTE); *wp++ = OQUOTE; ignore_backslash_newline++; } else if (c == '"') { - state = SHEREDQUOTE; + state = statep->ls_state = SHEREDQUOTE; *wp++ = OQUOTE; } else { *wp++ = CHAR; @@ -475,7 +584,7 @@ yylex(cf) case SHEREDQUOTE: /* " in <<,<<- delimiter */ if (c == '"') { *wp++ = CQUOTE; - state = SHEREDELIM; + state = statep->ls_state = SHEREDELIM; } else { if (c == '\\') { switch (c = getsc()) { @@ -498,33 +607,69 @@ yylex(cf) case SPATTERN: /* in *(...|...) pattern (*+?@!) */ if ( /*(*/ c == ')') { *wp++ = CPAT; - state = *--statep; - } else if (c == '|') + POP_STATE(); + } else if (c == '|') { *wp++ = SPAT; - else + } else if (c == '(') { + *wp++ = OPAT; + *wp++ = ' '; /* simile for @ */ + PUSH_STATE(SPATTERN); + } else goto Sbase1; break; } } Done: Xcheck(ws, wp); - if (state != istate) + 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; - if ((c == '<' || c == '>') && state == SBASE) { - char *cp = Xstring(ws, wp); - if (Xlength(ws, wp) == 2 && cp[0] == CHAR && digit(cp[1])) { - wp = cp; /* throw away word */ - c2/*unit*/ = cp[1] - '0'; - } else - c2/*unit*/ = c == '>'; /* 0 for <, 1 for > */ + dp = Xstring(ws, wp); + if ((c == '<' || c == '>') && state == SBASE + && ((c2 = Xlength(ws, wp)) == 0 + || (c2 == 2 && dp[0] == CHAR && digit(dp[1])))) + { + struct ioword *iop = + (struct ioword *) alloc(sizeof(*iop), ATEMP); + + if (c2 == 2) + iop->unit = dp[1] - '0'; + else + iop->unit = c == '>'; /* 0 for <, 1 for > */ + + c2 = getsc(); + /* <<, >>, <> are ok, >< is not */ + if (c == c2 || (c == '<' && c2 == '>')) { + iop->flag = c == c2 ? + (c == '>' ? IOCAT : IOHERE) : IORDWR; + if (iop->flag == IOHERE) + if ((c2 = getsc()) == '-') + iop->flag |= IOSKIP; + else + ungetsc(c2); + } else if (c2 == '&') + iop->flag = IODUP | (c == '<' ? IORDUP : 0); + else { + iop->flag = c == '>' ? IOWRITE : IOREAD; + if (c == '>' && c2 == '|') + iop->flag |= IOCLOB; + else + ungetsc(c2); + } + + iop->name = (char *) 0; + iop->delim = (char *) 0; + Xfree(ws, wp); /* free word */ + yylval.iop = iop; + return REDIR; } - if (wp == Xstring(ws, wp) && state == SBASE) { + if (wp == dp && state == SBASE) { Xfree(ws, wp); /* free word */ /* no word, process LEX1 character */ switch (c) { @@ -547,38 +692,6 @@ Done: ungetsc(c2); return c; - case '>': - case '<': { - register struct ioword *iop; - - iop = (struct ioword *) alloc(sizeof(*iop), ATEMP); - iop->unit = c2/*unit*/; - - c2 = getsc(); - /* <<, >>, <> are ok, >< is not */ - if (c == c2 || (c == '<' && c2 == '>')) { - iop->flag = c == c2 ? - (c == '>' ? IOCAT : IOHERE) : IORDWR; - if (iop->flag == IOHERE) - if ((c2 = getsc()) == '-') - iop->flag |= IOSKIP; - else - ungetsc(c2); - } else if (c2 == '&') - iop->flag = IODUP | (c == '<' ? IORDUP : 0); - else { - iop->flag = c == '>' ? IOWRITE : IOREAD; - if (c == '>' && c2 == '|') - iop->flag |= IOCLOB; - else - ungetsc(c2); - } - - iop->name = (char *) 0; - iop->delim = (char *) 0; - yylval.iop = iop; - return REDIR; - } case '\n': gethere(); if (cf & CONTIN) @@ -589,6 +702,7 @@ Done: #ifdef KSH if (!Flag(FSH)) { if ((c2 = getsc()) == '(') /*)*/ + /* XXX need to handle ((...); (...)) */ c = MDPAREN; else ungetsc(c2); @@ -605,7 +719,7 @@ Done: yylval.cp = Xclose(ws, wp); if (state == SWORD #ifdef KSH - || state == SDPAREN + || state == SLETPAREN #endif /* KSH */ ) /* ONEWORD? */ return LWORD; @@ -754,7 +868,6 @@ yyerror(fmt, va_alist) { va_list va; - yynerrs++; /* pop aliases and re-reads */ while (source->type == SALIAS || source->type == SREREAD) source = source->next; @@ -1279,3 +1392,31 @@ getsc_bn ARGS((void)) return c; } } + +static Lex_state * +push_state_(si, old_end) + State_info *si; + Lex_state *old_end; +{ + Lex_state *new = alloc(sizeof(Lex_state) * STATE_BSIZE, ATEMP); + + new[0].ls_info.base = old_end; + si->base = &new[0]; + si->end = &new[STATE_BSIZE]; + return &new[1]; +} + +static Lex_state * +pop_state_(si, old_end) + State_info *si; + Lex_state *old_end; +{ + Lex_state *old_base = si->base; + + si->base = old_end->ls_info.base - STATE_BSIZE; + si->end = old_end->ls_info.base; + + afree(old_base, ATEMP); + + return si->base + STATE_BSIZE - 1;; +} diff --git a/bin/ksh/lex.h b/bin/ksh/lex.h index bca6446d268..427d20cabef 100644 --- a/bin/ksh/lex.h +++ b/bin/ksh/lex.h @@ -1,4 +1,4 @@ -/* $OpenBSD: lex.h,v 1.3 1996/10/13 21:32:20 downsj Exp $ */ +/* $OpenBSD: lex.h,v 1.4 1998/06/25 19:02:07 millert Exp $ */ /* * Source input, lexer and parser @@ -53,14 +53,14 @@ struct source { #define SBASE 0 /* outside any lexical constructs */ #define SWORD 1 /* implicit quoting for substitute() */ #ifdef KSH -#define SDPAREN 2 /* inside (( )), implicit quoting */ +#define SLETPAREN 2 /* inside (( )), implicit quoting */ #endif /* KSH */ #define SSQUOTE 3 /* inside '' */ #define SDQUOTE 4 /* inside "" */ #define SBRACE 5 /* inside ${} */ -#define SPAREN 6 /* inside $() */ +#define SCSPAREN 6 /* inside $() */ #define SBQUOTE 7 /* inside `` */ -#define SDDPAREN 8 /* inside $(( )) */ +#define SASPAREN 8 /* inside $(( )) */ #define SHEREDELIM 9 /* parsing <<,<<- delimiter */ #define SHEREDQUOTE 10 /* parsing " in <<,<<- delimiter */ #define SPATTERN 11 /* parsing *(...|...) pattern (*+?@!) */ @@ -120,7 +120,6 @@ typedef union { EXTERN Source *source; /* yyparse/yylex source */ EXTERN YYSTYPE yylval; /* result from yylex */ -EXTERN int yynerrs; EXTERN struct ioword *heres [HERES], **herep; EXTERN char ident [IDENT+1]; diff --git a/bin/ksh/mail.c b/bin/ksh/mail.c index 79ff7aee0f6..d3136632622 100644 --- a/bin/ksh/mail.c +++ b/bin/ksh/mail.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mail.c,v 1.6 1997/11/16 12:07:28 niklas Exp $ */ +/* $OpenBSD: mail.c,v 1.7 1998/06/25 19:02:09 millert Exp $ */ /* * Mailbox checking code by Robert J. Gibson, adapted for PD ksh by @@ -92,9 +92,12 @@ mbset(p) if (mbox.mb_msg) afree((void *)mbox.mb_msg, APERM); - mbox.mb_path = p; + if (mbox.mb_path) + afree((void *)mbox.mb_path, APERM); + /* Save a copy to protect from export (which munges the string) */ + mbox.mb_path = str_save(p, APERM); mbox.mb_msg = NULL; - if (p && stat(p,&stbuf) == 0 && S_ISREG(stbuf.st_mode)) + if (p && stat(p, &stbuf) == 0 && S_ISREG(stbuf.st_mode)) mbox.mb_mtime = stbuf.st_mtime; else mbox.mb_mtime = 0; diff --git a/bin/ksh/main.c b/bin/ksh/main.c index d161fb9b3d0..92a7e333f3b 100644 --- a/bin/ksh/main.c +++ b/bin/ksh/main.c @@ -1,4 +1,4 @@ -/* $OpenBSD: main.c,v 1.9 1997/09/12 00:32:53 deraadt Exp $ */ +/* $OpenBSD: main.c,v 1.10 1998/06/25 19:02:10 millert Exp $ */ /* * startup, main loop, enviroments and error handling @@ -26,7 +26,7 @@ static int is_restricted ARGS((char *name)); static const char initifs [] = "IFS= \t\n"; /* must be R/W */ -static const char initsubs [] = +static const char initsubs [] = "${PS2=> } ${PS3=#? } ${PS4=+ }"; static const char version_param[] = @@ -91,15 +91,14 @@ main(argc, argv) int argi; Source *s; struct block *l; - int restricted; + int restricted, errexit; char **wp; struct env env; - int euid; #ifdef MEM_DEBUG - chmem_push("+c", 1); - /*chmem_push("+cd", 1);*/ -#endif + chmem_set_defaults("ct", 1); + /* chmem_push("+c", 1); */ +#endif /* MEM_DEBUG */ #ifdef OS2 setmode (0, O_BINARY); @@ -168,7 +167,14 @@ main(argc, argv) } } #endif /* HAVE_CONFSTR && _CS_PATH */ - path = def_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(vp, def_path); + } /* Turn on nohup by default for how - will change to off @@ -248,20 +254,22 @@ main(argc, argv) ; } - euid = geteuid(); - safe_prompt = euid ? "$ " : "# "; + + 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) || (!euid && !strchr(str_val(vp), '#'))) + if (!(vp->flag & ISSET) + || (!ksheuid && !strchr(str_val(vp), '#'))) setstr(vp, safe_prompt); } /* Set this before parsing arguments */ - Flag(FPRIVILEGED) = getuid() != euid || getgid() != getegid(); + Flag(FPRIVILEGED) = getuid() != ksheuid || getgid() != getegid(); /* this to note if monitor is set on command line (see below) */ Flag(FMONITOR) = 127; @@ -302,7 +310,7 @@ main(argc, argv) s->u.shf = shf_fdopen(0, SHF_RD | can_seek(0), (struct shf *) 0); if (isatty(0) && isatty(2)) { - Flag(FTALKING) = 1; + Flag(FTALKING) = Flag(FTALKING_I) = 1; /* The following only if isatty(0) */ s->flags |= SF_TTY; s->u.shf->flags |= SHF_INTERRUPT; @@ -337,6 +345,8 @@ main(argc, argv) /* 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. @@ -408,6 +418,8 @@ main(argc, argv) /* After typeset command... */ Flag(FRESTRICTED) = 1; } + if (errexit) + Flag(FERREXIT) = 1; if (Flag(FTALKING)) { hist_init(s); @@ -450,10 +462,10 @@ include(name, argc, argv, intr_ok) newenv(E_INCL); i = ksh_sigsetjmp(e->jbuf, 0); if (i) { - quitenv(); source = sold; - if (s) + if (s) /* Do this before quitenv(), which frees the memory */ shf_close(s->u.shf); + quitenv(); if (old_argv) { e->loc->argv = old_argv; e->loc->argc = old_argc; @@ -660,6 +672,9 @@ unwind(i) kill(0, sig); } } +#ifdef MEM_DEBUG + chmem_allfree(); +#endif /* MEM_DEBUG */ exit(exstat); /* NOTREACHED */ } @@ -786,6 +801,7 @@ remove_temps(tp) sizeof(struct temp) + strlen(tp->name) + 1, APERM); memset(t, 0, sizeof(struct temp)); + t->name = (char *) &t[1]; strcpy(t->name, tp->name); t->next = delayed_remove; delayed_remove = t; diff --git a/bin/ksh/misc.c b/bin/ksh/misc.c index 5287f4e9dfa..192fba81232 100644 --- a/bin/ksh/misc.c +++ b/bin/ksh/misc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.6 1997/06/19 13:58:45 kstailey Exp $ */ +/* $OpenBSD: misc.c,v 1.7 1998/06/25 19:02:11 millert Exp $ */ /* * Miscellaneous functions @@ -173,7 +173,10 @@ const struct option options[] = { { "vi-esccomplete", 0, OF_ANY }, /* non-standard */ #endif { "xtrace", 'x', OF_ANY }, - { NULL, 0, 0 } + /* Anonymous flags: used internally by shell only + * (not visable to user) + */ + { (char *) 0, 0, OF_INTERNAL }, /* FTALKING_I */ }; /* @@ -185,8 +188,8 @@ option(n) { int i; - for (i = 0; options[i].name; i++) - if (strcmp(options[i].name, n) == 0) + for (i = 0; i < NELEM(options); i++) + if (options[i].name && strcmp(options[i].name, n) == 0) return i; return -1; @@ -232,8 +235,8 @@ printoptions(verbose) /* verbose version */ shprintf("Current option settings\n"); - for (i = n = oi.opt_width = 0; options[i].name; i++) - if (options[i].name[0]) { + 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; @@ -245,8 +248,8 @@ printoptions(verbose) } else { /* short version ala ksh93 */ shprintf("set"); - for (i = 0; options[i].name; i++) - if (Flag(i) && options[i].name[0]) + for (i = 0; i < NELEM(options); i++) + if (Flag(i) && options[i].name) shprintf(" -o %s", options[i].name); shprintf(newline); } @@ -259,7 +262,7 @@ getoptions() char m[FNFLAGS + 1]; register char *cp = m; - for (i = 0; options[i].name; i++) + for (i = 0; i < NELEM(options); i++) if (options[i].c && Flag(i)) *cp++ = options[i].c; *cp = 0; @@ -309,8 +312,8 @@ change_flag(f, what, newval) #ifdef OS2 ; #else /* OS2 */ - seteuid(getuid()); - setuid(getuid()); + seteuid(ksheuid = getuid()); + setuid(ksheuid); setegid(getgid()); setgid(getgid()); #endif /* OS2 */ @@ -320,6 +323,11 @@ change_flag(f, what, newval) #endif /* BRACE_EXPAND */ ; } + /* Changing interactive flag? */ + if (f == FTALKING) { + if ((what == OF_CMDLINE || what == OF_SET) && procpid == kshpid) + Flag(FTALKING_I) = newval; + } } /* parse command line & set command arguments. returns the index of @@ -334,28 +342,28 @@ parse_args(argv, what, setargsp) static char cmd_opts[NELEM(options) + 3]; /* o:\0 */ static char set_opts[NELEM(options) + 5]; /* Ao;s\0 */ char *opts; - char *array; + char *array = (char *) 0; Getopt go; int i, optc, set, sortargs = 0, arrayset = 0; /* First call? Build option strings... */ if (cmd_opts[0] == '\0') { - char *p; + char *p, *q; - /* c is also in options[], but it needs a trailing : */ strcpy(cmd_opts, "o:"); /* see cmd_opts[] declaration */ p = cmd_opts + strlen(cmd_opts); - for (i = 0; options[i].name; i++) - if (options[i].c && (options[i].flags & OF_CMDLINE)) - *p++ = options[i].c; - *p = '\0'; - - strcpy(set_opts, "Ao;s"); /* see set_opts[] declaration */ - p = set_opts + strlen(set_opts); - for (i = 0; options[i].name; i++) - if (options[i].c && (options[i].flags & OF_SET)) - *p++ = options[i].c; + strcpy(set_opts, "A:o;s"); /* see set_opts[] declaration */ + q = set_opts + strlen(set_opts); + for (i = 0; i < NELEM(options); i++) { + if (options[i].c) { + if (options[i].flags & OF_CMDLINE) + *p++ = options[i].c; + if (options[i].flags & OF_SET) + *q++ = options[i].c; + } + } *p = '\0'; + *q = '\0'; } if (what == OF_CMDLINE) { @@ -375,6 +383,7 @@ parse_args(argv, what, setargsp) switch (optc) { case 'A': arrayset = set ? 1 : -1; + array = go.optarg; break; case 'o': @@ -413,7 +422,7 @@ parse_args(argv, what, setargsp) sortargs = 1; break; } - for (i = 0; options[i].name; i++) + for (i = 0; i < NELEM(options); i++) if (optc == options[i].c && (what & options[i].flags)) { @@ -421,7 +430,7 @@ parse_args(argv, what, setargsp) set); break; } - if (!options[i].name) { + if (i == NELEM(options)) { internal_errorf(1, "parse_args: `%c'", optc); return -1; /* not reached */ } @@ -442,18 +451,10 @@ parse_args(argv, what, setargsp) *setargsp = !arrayset && ((go.info & GI_MINUSMINUS) || argv[go.optind]); - if (arrayset) { - array = argv[go.optind++]; - if (!array) { - bi_errorf("-A: missing array name"); - return -1; - } - if (!*array || *skip_varname(array, FALSE)) { - bi_errorf("%s: is not an identifier", array); - return -1; - } - } else - array = (char *) 0; /* keep gcc happy */ + if (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++) ; @@ -588,7 +589,7 @@ has_globbing(xp, xpe) return 0; in_bracket = 0; } - } else if ((c & 0x80) && strchr("*+?@!", c & 0x7f)) { + } else if ((c & 0x80) && strchr("*+?@! ", c & 0x7f)) { saw_glob = 1; if (in_bracket) bnest++; @@ -657,8 +658,11 @@ do_gmatch(s, se, p, pe, isfile) } while (s++ < se); return 0; -#ifdef KSH - /* [*+?@!](pattern|pattern|..) */ + /* + * [*+?@!](pattern|pattern|..) + * + * Not ifdef'd KSH as this is needed for ${..%..}, etc. + */ case 0x80|'+': /* matches one or more times */ case 0x80|'*': /* matches zero or more times */ if (!(prest = pat_scan(p, pe, 0))) @@ -687,6 +691,7 @@ do_gmatch(s, se, p, pe, isfile) 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--; @@ -732,7 +737,6 @@ do_gmatch(s, se, p, pe, isfile) return 1; } return 0; -#endif /* KSH */ default: if (sc != p[-1]) @@ -757,8 +761,12 @@ cclass(p, sub) c = *p++; if (ISMAGIC(c)) { c = *p++; - if ((c & 0x80) && !ISMAGIC(c)) + 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 */ @@ -800,7 +808,7 @@ pat_scan(p, pe, match_sep) if ((*++p == /*(*/ ')' && nest-- == 0) || (*p == '|' && match_sep && nest == 0)) return ++p; - if ((*p & 0x80) && strchr("*+?@!", *p & 0x7f)) + if ((*p & 0x80) && strchr("*+?@! ", *p & 0x7f)) nest++; } return (const unsigned char *) 0; @@ -945,8 +953,7 @@ ksh_getopt_reset(go, flags) * Used for 'typeset -LZ4'. * - accepts +c as well as -c IF the GF_PLUSOPT flag is present. If an * option starting with + is accepted, the GI_PLUS flag will be set - * in go->info. Once a - or + has been seen, all other options must - * start with the same character. + * in go->info. */ int ksh_getopt(argv, go, options) @@ -968,15 +975,15 @@ ksh_getopt(argv, go, options) return EOF; } if (arg == (char *) 0 - || ((flag != '-' || (go->info & GI_PLUS)) - && (!(go->flags & GF_PLUSOPT) || (go->info & GI_MINUS) - || flag != '+')) + || ((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++; diff --git a/bin/ksh/path.c b/bin/ksh/path.c index d0a08761d9a..38a14e03c87 100644 --- a/bin/ksh/path.c +++ b/bin/ksh/path.c @@ -1,4 +1,4 @@ -/* $OpenBSD: path.c,v 1.3 1997/06/19 13:58:46 kstailey Exp $ */ +/* $OpenBSD: path.c,v 1.4 1998/06/25 19:02:14 millert Exp $ */ #include "sh.h" #include "ksh_stat.h" @@ -14,11 +14,8 @@ /* * $Log: path.c,v $ - * Revision 1.3 1997/06/19 13:58:46 kstailey - * back out - * - * Revision 1.1.1.1 1996/08/14 06:19:11 downsj - * Import pdksh 5.2.7. + * Revision 1.4 1998/06/25 19:02:14 millert + * pdksh-5.2.13 + local changes * * Revision 1.2 1994/05/19 18:32:40 michael * Merge complete, stdio replaced, various fixes. (pre autoconf) diff --git a/bin/ksh/proto.h b/bin/ksh/proto.h index 608b451f593..7cc0c1606d5 100644 --- a/bin/ksh/proto.h +++ b/bin/ksh/proto.h @@ -1,4 +1,4 @@ -/* $OpenBSD: proto.h,v 1.3 1996/11/21 07:59:34 downsj Exp $ */ +/* $OpenBSD: proto.h,v 1.4 1998/06/25 19:02:15 millert Exp $ */ /* * prototypes for PD-KSH @@ -242,6 +242,7 @@ char * snptreef ARGS((char *s, int n, const char *fmt, ...)); struct op * tcopy ARGS((struct op *t, Area *ap)); char * wdcopy ARGS((const char *wp, Area *ap)); char * wdscan ARGS((const char *wp, int c)); +char * wdstrip ARGS((const char *wp)); void tfree ARGS((struct op *t, Area *ap)); /* var.c */ void newblock ARGS((void)); diff --git a/bin/ksh/sh.1 b/bin/ksh/sh.1 index ee54ea99549..68c9213a103 100644 --- a/bin/ksh/sh.1 +++ b/bin/ksh/sh.1 @@ -1,5 +1,5 @@ '\" t -.\" $OpenBSD: sh.1,v 1.4 1998/03/18 03:04:56 marc Exp $ +.\" $OpenBSD: sh.1,v 1.5 1998/06/25 19:02:17 millert Exp $ .\"{{{}}} .\"{{{ Notes about man page .\" - use the pseudo-macros .sh( and .sh) to begin and end sh-specific @@ -1117,6 +1117,11 @@ the \fB$0\fP parameter is set to the name of the function parameter assignments preceeding function calls are not kept in the shell environment (executing Bourne-style functions will keep assignments). +.IP \ \ \(bu +\fBOPTIND\fP is saved/reset and restored on entry and exit from the function +so \fBgetopts\fP can be used properly both inside and outside the function +(Bourne-style functions leave \fBOPTIND\fP untouched, so using \fBgetopts\fP +inside a function interferes with using \fBgetopts\fP outside the function). .nr PD \n(P2 In the future, the following differences will also be added: .nr P2 \n(PD @@ -1210,6 +1215,11 @@ a i in 1 2; do echo i=$i j=$j; done .ft P .RE uses parameter \fBi\fP in posix mode, \fBj\fP in non-posix mode. +.IP \ \ \(bu +test: in posix mode, the expression "\fB-t\fP" (preceded by +some number of "\fB!\fP" arguments) is always true as it is a non-zero length +string; in non-posix mode, it tests if file descriptor 1 is a tty (\fIi.e.\fP, +the \fIfd\fP argument to the \fB-t\fP test may be left out and defaults to 1). .nr PD \n(P2 .\"}}} .\"{{{ Command Execution (built-in commands) @@ -1285,11 +1295,23 @@ environment the command is used in. The null command. Exit status is set to zero. .\"}}} -.\"{{{ alias [ -d | -t [ -r ] ] [-x] [name1[=value1] ...] -.IP "\fBalias\fP [ \fB\-d\fP | \fB\-t\fP [\fB\-r\fP] ] [\fB\-x\fP] [\fIname1\fP[\fB=\fP\fIvalue1\fP] ...]" -Without arguments, \fBalias\fP lists all aliases and their values. For -any name without a value, its value is listed. Any name with a value -defines an alias (see Aliases above). +.\"{{{ alias [ -d | +-t [ -r ] ] [+-px] [+-] [name1[=value1] ...] +.IP "\fBalias\fP [ \fB\-d\fP | \fB\(+-t\fP [\fB\-r\fP] ] [\fB\(+-px\fP] [\fB\(+-\fP] [\fIname1\fP[\fB=\fP\fIvalue1\fP] ...]" +Without arguments, \fBalias\fP lists all aliases. +For any name without a value, the existing alias is listed. +Any name with a value defines an alias (see Aliases above). +.sp +When listing aliases, one of two formats is used: +normally, aliases are listed as \fIname\fP\fB=\fP\fIvalue\fP, where +\fIvalue\fP is quoted; if options were preceded with \fB+\fP +or a lone \fB+\fP is given on the command line, only \fIname\fP +is printed. +In addition, if the \fB\-p\fP option is used, each alias +is prefixed with the string "\fBalias\fP\ ". +.sp +The \fB\-x\fP option sets (\fB+x\fP clears) the export attribute of an alias, +or, if no names are given, lists the aliases with the export attribute +(exporting an alias has no affect). .sp The \fB\-x\fP option sets the export attribute of an alias, or, if no names are given, lists the aliases with the export attribute @@ -1394,6 +1416,10 @@ The command is executed without forking, replacing the shell process. .sp If no arguments are given, any IO redirection is permanent and the shell is not replaced. +Any file descriptors which are opened or \fIdup\fP(2)-ed +in this way are made available to other executed commands +(note that the Korn shell differs here: it does not pass on +file descriptors greater than 2). .\"}}} .\"{{{ exit [status] .IP "\fBexit\fP [\fIstatus\fP]" @@ -1441,9 +1467,7 @@ options. \fIoptstring\fP contains the option letters that \fBgetopts\fP is to recognize. If a letter is followed by a colon, the option is expected to have an argument. -Arguments containing options must all start with either a \fB\-\fP or -a \fB+\fP, options that do not take arguments may be grouped in a single -argument. +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 @@ -1854,41 +1878,43 @@ T} \fIfile\fP \-ef \fIfile\fP T{ first \fIfile\fP is the same file as second \fIfile\fP T} -\-t [\fIfd\fP] T{ +\-t\ [\fIfd\fP] T{ file descriptor is a tty device. -Default value of \fIfd\fP is 1. +If the posix option (\fBset \-o posix\fP, see POSIX Mode above) is not +set, \fIfd\fP may be left out, in which case it is taken to be 1 +(the behaviour differs due to the special POSIX rules described below). T} \fIstring\fP T{ \fIstring\fP is not empty T} -\-z \fIstring\fP T{ +\-z\ \fIstring\fP T{ \fIstring\fP is empty T} -\-n \fIstring\fP T{ +\-n\ \fIstring\fP T{ \fIstring\fP is not empty T} -\fIstring\fP = \fIstring\fP T{ +\fIstring\fP\ =\ \fIstring\fP T{ strings are equal T} -\fIstring\fP != \fIstring\fP T{ +\fIstring\fP\ !=\ \fIstring\fP T{ strings are not equal T} -\fInumber\fP \-eq \fInumber\fP T{ +\fInumber\fP\ \-eq\ \fInumber\fP T{ numbers compare equal T} -\fInumber\fP \-ne \fInumber\fP T{ +\fInumber\fP\ \-ne\ \fInumber\fP T{ numbers compare not equal T} -\fInumber\fP \-ge \fInumber\fP T{ +\fInumber\fP\ \-ge\ \fInumber\fP T{ numbers compare greater than or equal T} -\fInumber\fP \-gt \fInumber\fP T{ +\fInumber\fP\ \-gt\ \fInumber\fP T{ numbers compare greater than T} -\fInumber\fP \-le \fInumber\fP T{ +\fInumber\fP\ \-le\ \fInumber\fP T{ numbers compare less than or equal T} -\fInumber\fP \-lt \fInumber\fP T{ +\fInumber\fP\ \-lt\ \fInumber\fP T{ numbers compare less than T} .TE @@ -1964,8 +1990,8 @@ The original Korn shell's \fBDEBUG\fP trap and the handling of \fBERR\fP and .IP \fBtrue\fP A command that exits with a zero value. .\"}}} -.\"{{{ typeset [[+-Ulrtux] [-L[n]] [-R[n]] [-Z[n]] [-i[n]] | -f [-tux]] [name[=value] ...] -.IP "\fBtypeset\fP [[\(+-Ulrtux] [\fB\-L\fP[\fIn\fP]] [\fB\-R\fP[\fIn\fP]] [\fB\-Z\fP[\fIn\fP]] [\fB\-i\fP[\fIn\fP]] | \fB\-f\fP [\fB\-tux\fP]] [\fIname\fP[\fB=\fP\fIvalue\fP] ...]" +.\"{{{ typeset [[+-Ulprtux] [-L[n]] [-R[n]] [-Z[n]] [-i[n]] | -f [-tux]] [name[=value] ...] +.IP "\fBtypeset\fP [[\(+-Ulprtux] [\fB\-L\fP[\fIn\fP]] [\fB\-R\fP[\fIn\fP]] [\fB\-Z\fP[\fIn\fP]] [\fB\-i\fP[\fIn\fP]] | \fB\-f\fP [\fB\-tux\fP]] [\fIname\fP[\fB=\fP\fIvalue\fP] ...]" Display or set parameter attributes. With no \fIname\fP arguments, parameter attributes are displayed: if no options arg used, the current attributes of all parameters are printed as typeset @@ -2029,6 +2055,11 @@ lower case. (In the original Korn shell, this parameter meant `long integer' when used with the \fB\-i\fP option). T} +\-p T{ +Print complete 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 compatability). +T} \-r T{ Readonly attribute: parameters with the this attribute may not be assigned to or unset. @@ -2348,7 +2379,7 @@ ftp.cs.mun.ca:pub/pdksh/. This shell is based on the public domain 7th edition Bourne shell clone by Charles Forsyth and parts of the BRL shell by Doug A.\& Gwyn, Doug Kingston, Ron Natalie, Arnold Robbins, Lou Salkind and others. The first release -was created by Eric Gisin, and it was subsequently maintained by +of pdksh was created by Eric Gisin, and it was subsequently maintained by John R.\& MacMillan (chance!john@sq.sq.com), and Simon J.\& Gerraty (sjg@zen.void.oz.au). The current maintainer is Michael Rendell (michael@cs.mun.ca). diff --git a/bin/ksh/sh.1tbl b/bin/ksh/sh.1tbl index 1bfedfe059e..7c48bcdfd36 100644 --- a/bin/ksh/sh.1tbl +++ b/bin/ksh/sh.1tbl @@ -1,5 +1,5 @@ '\" t -.\" $OpenBSD: sh.1tbl,v 1.4 1998/03/18 03:04:56 marc Exp $ +.\" $OpenBSD: sh.1tbl,v 1.5 1998/06/25 19:02:17 millert Exp $ .\"{{{}}} .\"{{{ Notes about man page .\" - use the pseudo-macros .sh( and .sh) to begin and end sh-specific @@ -1117,6 +1117,11 @@ the \fB$0\fP parameter is set to the name of the function parameter assignments preceeding function calls are not kept in the shell environment (executing Bourne-style functions will keep assignments). +.IP \ \ \(bu +\fBOPTIND\fP is saved/reset and restored on entry and exit from the function +so \fBgetopts\fP can be used properly both inside and outside the function +(Bourne-style functions leave \fBOPTIND\fP untouched, so using \fBgetopts\fP +inside a function interferes with using \fBgetopts\fP outside the function). .nr PD \n(P2 In the future, the following differences will also be added: .nr P2 \n(PD @@ -1210,6 +1215,11 @@ a i in 1 2; do echo i=$i j=$j; done .ft P .RE uses parameter \fBi\fP in posix mode, \fBj\fP in non-posix mode. +.IP \ \ \(bu +test: in posix mode, the expression "\fB-t\fP" (preceded by +some number of "\fB!\fP" arguments) is always true as it is a non-zero length +string; in non-posix mode, it tests if file descriptor 1 is a tty (\fIi.e.\fP, +the \fIfd\fP argument to the \fB-t\fP test may be left out and defaults to 1). .nr PD \n(P2 .\"}}} .\"{{{ Command Execution (built-in commands) @@ -1285,11 +1295,23 @@ environment the command is used in. The null command. Exit status is set to zero. .\"}}} -.\"{{{ alias [ -d | -t [ -r ] ] [-x] [name1[=value1] ...] -.IP "\fBalias\fP [ \fB\-d\fP | \fB\-t\fP [\fB\-r\fP] ] [\fB\-x\fP] [\fIname1\fP[\fB=\fP\fIvalue1\fP] ...]" -Without arguments, \fBalias\fP lists all aliases and their values. For -any name without a value, its value is listed. Any name with a value -defines an alias (see Aliases above). +.\"{{{ alias [ -d | +-t [ -r ] ] [+-px] [+-] [name1[=value1] ...] +.IP "\fBalias\fP [ \fB\-d\fP | \fB\(+-t\fP [\fB\-r\fP] ] [\fB\(+-px\fP] [\fB\(+-\fP] [\fIname1\fP[\fB=\fP\fIvalue1\fP] ...]" +Without arguments, \fBalias\fP lists all aliases. +For any name without a value, the existing alias is listed. +Any name with a value defines an alias (see Aliases above). +.sp +When listing aliases, one of two formats is used: +normally, aliases are listed as \fIname\fP\fB=\fP\fIvalue\fP, where +\fIvalue\fP is quoted; if options were preceded with \fB+\fP +or a lone \fB+\fP is given on the command line, only \fIname\fP +is printed. +In addition, if the \fB\-p\fP option is used, each alias +is prefixed with the string "\fBalias\fP\ ". +.sp +The \fB\-x\fP option sets (\fB+x\fP clears) the export attribute of an alias, +or, if no names are given, lists the aliases with the export attribute +(exporting an alias has no affect). .sp The \fB\-x\fP option sets the export attribute of an alias, or, if no names are given, lists the aliases with the export attribute @@ -1394,6 +1416,10 @@ The command is executed without forking, replacing the shell process. .sp If no arguments are given, any IO redirection is permanent and the shell is not replaced. +Any file descriptors which are opened or \fIdup\fP(2)-ed +in this way are made available to other executed commands +(note that the Korn shell differs here: it does not pass on +file descriptors greater than 2). .\"}}} .\"{{{ exit [status] .IP "\fBexit\fP [\fIstatus\fP]" @@ -1441,9 +1467,7 @@ options. \fIoptstring\fP contains the option letters that \fBgetopts\fP is to recognize. If a letter is followed by a colon, the option is expected to have an argument. -Arguments containing options must all start with either a \fB\-\fP or -a \fB+\fP, options that do not take arguments may be grouped in a single -argument. +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 @@ -1854,41 +1878,43 @@ T} \fIfile\fP \-ef \fIfile\fP T{ first \fIfile\fP is the same file as second \fIfile\fP T} -\-t [\fIfd\fP] T{ +\-t\ [\fIfd\fP] T{ file descriptor is a tty device. -Default value of \fIfd\fP is 1. +If the posix option (\fBset \-o posix\fP, see POSIX Mode above) is not +set, \fIfd\fP may be left out, in which case it is taken to be 1 +(the behaviour differs due to the special POSIX rules described below). T} \fIstring\fP T{ \fIstring\fP is not empty T} -\-z \fIstring\fP T{ +\-z\ \fIstring\fP T{ \fIstring\fP is empty T} -\-n \fIstring\fP T{ +\-n\ \fIstring\fP T{ \fIstring\fP is not empty T} -\fIstring\fP = \fIstring\fP T{ +\fIstring\fP\ =\ \fIstring\fP T{ strings are equal T} -\fIstring\fP != \fIstring\fP T{ +\fIstring\fP\ !=\ \fIstring\fP T{ strings are not equal T} -\fInumber\fP \-eq \fInumber\fP T{ +\fInumber\fP\ \-eq\ \fInumber\fP T{ numbers compare equal T} -\fInumber\fP \-ne \fInumber\fP T{ +\fInumber\fP\ \-ne\ \fInumber\fP T{ numbers compare not equal T} -\fInumber\fP \-ge \fInumber\fP T{ +\fInumber\fP\ \-ge\ \fInumber\fP T{ numbers compare greater than or equal T} -\fInumber\fP \-gt \fInumber\fP T{ +\fInumber\fP\ \-gt\ \fInumber\fP T{ numbers compare greater than T} -\fInumber\fP \-le \fInumber\fP T{ +\fInumber\fP\ \-le\ \fInumber\fP T{ numbers compare less than or equal T} -\fInumber\fP \-lt \fInumber\fP T{ +\fInumber\fP\ \-lt\ \fInumber\fP T{ numbers compare less than T} .TE @@ -1964,8 +1990,8 @@ The original Korn shell's \fBDEBUG\fP trap and the handling of \fBERR\fP and .IP \fBtrue\fP A command that exits with a zero value. .\"}}} -.\"{{{ typeset [[+-Ulrtux] [-L[n]] [-R[n]] [-Z[n]] [-i[n]] | -f [-tux]] [name[=value] ...] -.IP "\fBtypeset\fP [[\(+-Ulrtux] [\fB\-L\fP[\fIn\fP]] [\fB\-R\fP[\fIn\fP]] [\fB\-Z\fP[\fIn\fP]] [\fB\-i\fP[\fIn\fP]] | \fB\-f\fP [\fB\-tux\fP]] [\fIname\fP[\fB=\fP\fIvalue\fP] ...]" +.\"{{{ typeset [[+-Ulprtux] [-L[n]] [-R[n]] [-Z[n]] [-i[n]] | -f [-tux]] [name[=value] ...] +.IP "\fBtypeset\fP [[\(+-Ulprtux] [\fB\-L\fP[\fIn\fP]] [\fB\-R\fP[\fIn\fP]] [\fB\-Z\fP[\fIn\fP]] [\fB\-i\fP[\fIn\fP]] | \fB\-f\fP [\fB\-tux\fP]] [\fIname\fP[\fB=\fP\fIvalue\fP] ...]" Display or set parameter attributes. With no \fIname\fP arguments, parameter attributes are displayed: if no options arg used, the current attributes of all parameters are printed as typeset @@ -2029,6 +2055,11 @@ lower case. (In the original Korn shell, this parameter meant `long integer' when used with the \fB\-i\fP option). T} +\-p T{ +Print complete 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 compatability). +T} \-r T{ Readonly attribute: parameters with the this attribute may not be assigned to or unset. @@ -2348,7 +2379,7 @@ ftp.cs.mun.ca:pub/pdksh/. This shell is based on the public domain 7th edition Bourne shell clone by Charles Forsyth and parts of the BRL shell by Doug A.\& Gwyn, Doug Kingston, Ron Natalie, Arnold Robbins, Lou Salkind and others. The first release -was created by Eric Gisin, and it was subsequently maintained by +of pdksh was created by Eric Gisin, and it was subsequently maintained by John R.\& MacMillan (chance!john@sq.sq.com), and Simon J.\& Gerraty (sjg@zen.void.oz.au). The current maintainer is Michael Rendell (michael@cs.mun.ca). diff --git a/bin/ksh/sh.h b/bin/ksh/sh.h index 588abd2c18e..89955883d66 100644 --- a/bin/ksh/sh.h +++ b/bin/ksh/sh.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sh.h,v 1.5 1997/01/02 09:34:10 downsj Exp $ */ +/* $OpenBSD: sh.h,v 1.6 1998/06/25 19:02:19 millert Exp $ */ /* * Public Domain Bourne/Korn shell @@ -30,6 +30,7 @@ /* just a useful subset of what stdlib.h would have */ extern char * getenv ARGS((const char *)); extern void * malloc ARGS((size_t)); +extern void * realloc ARGS((void *, size_t)); extern int free ARGS((void *)); extern int exit ARGS((int)); extern int rand ARGS((void)); @@ -238,7 +239,8 @@ extern int ksh_execve(char *cmd, char **args, char **env); #endif /* HAVE_SIGSETJMP */ /* Find a integer type that is at least 32 bits (or die) - SIZEOF_* defined - * by autoconf (assumes an 8 bit byte, but I'm not concerned) + * by autoconf (assumes an 8 bit byte, but I'm not concerned). + * NOTE: INT32 may end up being more than 32 bits. */ #if SIZEOF_INT >= 4 # define INT32 int @@ -355,6 +357,7 @@ typedef INT32 Tflag; EXTERN const char *kshname; /* $0 */ EXTERN pid_t kshpid; /* $$, shell pid */ EXTERN pid_t procpid; /* pid of executing process */ +EXTERN int 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 */ @@ -427,7 +430,8 @@ EXTERN struct env { #define OF_CMDLINE 0x01 /* command line */ #define OF_SET 0x02 /* set builtin */ #define OF_SPECIAL 0x04 /* a special variable changing */ -#define OF_ANY (OF_CMDLINE | OF_SET | OF_SPECIAL) +#define OF_INTERNAL 0x08 /* set internally by shell */ +#define OF_ANY (OF_CMDLINE | OF_SET | OF_SPECIAL | OF_INTERNAL) struct option { const char *name; /* long name of option */ @@ -484,6 +488,7 @@ enum sh_flag { FVIESCCOMPLETE, /* enable ESC as file name completion in command mode */ #endif FXTRACE, /* -x: execution trace */ + FTALKING_I, /* (internal): initial shell was interactive */ FNFLAGS /* (place holder: how many flags are there) */ }; @@ -621,6 +626,7 @@ EXTERN int ifs0 I__(' '); /* for "$*" */ typedef struct { int optind; + int uoptind;/* what user sees in $OPTIND */ char *optarg; int flags; /* see GF_* */ int info; /* see GI_* */ @@ -629,6 +635,7 @@ typedef struct { } Getopt; EXTERN Getopt builtin_opt; /* for shell builtin commands */ +EXTERN Getopt user_opt; /* parsing state for getopts builtin command */ #ifdef KSH diff --git a/bin/ksh/syn.c b/bin/ksh/syn.c index 4b37baa819d..c0e679554c6 100644 --- a/bin/ksh/syn.c +++ b/bin/ksh/syn.c @@ -1,4 +1,4 @@ -/* $OpenBSD: syn.c,v 1.8 1997/09/01 18:30:12 deraadt Exp $ */ +/* $OpenBSD: syn.c,v 1.9 1998/06/25 19:02:20 millert Exp $ */ /* * shell parser (C version) @@ -7,16 +7,15 @@ #include "sh.h" #include "c_test.h" -struct multiline_state { - int on; /* set in multiline commands (\n becomes ;) */ - int start_token; /* token multiline is for (eg, FOR, {, etc.) */ - int start_line; /* line multiline command started on */ +struct nesting_state { + int start_token; /* token than began nesting (eg, FOR) */ + int start_line; /* line nesting began on */ }; static void yyparse ARGS((void)); static struct op *pipeline ARGS((int cf)); static struct op *andor ARGS((void)); -static struct op *c_list ARGS((void)); +static struct op *c_list ARGS((int multi)); static struct ioword *synio ARGS((int cf)); static void musthave ARGS((int c, int cf)); static struct op *nested ARGS((int type, int smark, int emark)); @@ -33,8 +32,8 @@ static struct op *block ARGS((int type, struct op *t1, struct op *t2, static struct op *newtp ARGS((int type)); static void syntaxerr ARGS((const char *what)) GCC_FUNC_ATTR(noreturn); -static void multiline_push ARGS((struct multiline_state *save, int tok)); -static void multiline_pop ARGS((struct multiline_state *saved)); +static void nesting_push ARGS((struct nesting_state *save, int tok)); +static void nesting_pop ARGS((struct nesting_state *saved)); static int assign_command ARGS((char *s)); static int inalias ARGS((struct source *s)); #ifdef KSH @@ -48,7 +47,7 @@ static void dbtestp_error ARGS((Test_env *te, int offset, const char *msg)); static struct op *outtree; /* yyparse output */ -static struct multiline_state multiline; /* \n changed to ; */ +static struct nesting_state nesting; /* \n changed to ; */ static int reject; /* token(cf) gets symbol again */ static int symbol; /* yylex value */ @@ -66,9 +65,8 @@ yyparse() int c; ACCEPT; - yynerrs = 0; - outtree = c_list(); + outtree = c_list(source->type == SSTRING); c = tpeek(0); if (c == 0 && !outtree) outtree = newtp(TEOF); @@ -116,37 +114,41 @@ andor() } static struct op * -c_list() +c_list(multi) + int multi; { - register struct op *t, *p, *tl = NULL; + register struct op *t = NULL, *p, *tl = NULL; register int c; + int have_sep; - t = andor(); - if (t != NULL) { + while (1) { + p = andor(); /* Token has always been read/rejected at this point, so - * we don't worray about what flags to pass token() + * we don't worry about what flags to pass token() */ - while ((c = token(0)) == ';' || c == '&' || c == COPROC || - (c == '\n' && (multiline.on || inalias(source)))) - { - if (c == '&' || c == COPROC) { - int type = c == '&' ? TASYNC : TCOPROC; - if (tl) - tl->right = block(type, tl->right, - NOBLOCK, NOWORDS); - else - t = block(type, t, NOBLOCK, NOWORDS); - } - if ((p = andor()) == NULL) - return (t); - if (tl == NULL) - t = tl = block(TLIST, t, p, NOWORDS); - else - tl = tl->right = block(TLIST, tl->right, p, NOWORDS); - } - REJECT; + 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; } - return (t); + REJECT; + return t; } static struct ioword * @@ -187,12 +189,12 @@ nested(type, smark, emark) int type, smark, emark; { register struct op *t; - struct multiline_state old_multiline; + struct nesting_state old_nesting; - multiline_push(&old_multiline, smark); - t = c_list(); + nesting_push(&old_nesting, smark); + t = c_list(TRUE); musthave(emark, KEYWORD|ALIAS); - multiline_pop(&old_multiline); + nesting_pop(&old_nesting); return (block(type, t, NOBLOCK, NOWORDS)); } @@ -204,18 +206,13 @@ get_command(cf) register int c, iopn = 0, syniocf; struct ioword *iop, **iops; XPtrV args, vars; - struct multiline_state old_multiline; + struct nesting_state old_nesting; iops = (struct ioword **) alloc(sizeofN(struct ioword *, NUFILE+1), ATEMP); XPinit(args, 16); XPinit(vars, 16); - /* Don't want to pass CONTIN if reading interactively as just hitting - * return would print PS2 instead of PS1. - */ - if (multiline.on || inalias(source)) - cf = CONTIN; syniocf = KEYWORD|ALIAS; switch (c = token(cf|KEYWORD|ALIAS|VARASN)) { default: @@ -296,7 +293,7 @@ get_command(cf) { static const char let_cmd[] = { CHAR, 'l', CHAR, 'e', CHAR, 't', EOS }; - syniocf &= ~(KEYWORD|ALIAS); + /* Leave KEYWORD in syniocf (allow if (( 1 )) then ...) */ t = newtp(TCOM); ACCEPT; XPput(args, wdcopy(let_cmd, ATEMP)); @@ -308,7 +305,7 @@ get_command(cf) #ifdef KSH case DBRACKET: /* [[ .. ]] */ - syniocf &= ~(KEYWORD|ALIAS); + /* Leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */ t = newtp(TDBRACKET); ACCEPT; { @@ -334,37 +331,37 @@ get_command(cf) yyerror("%s: bad identifier\n", c == FOR ? "for" : "select"); t->str = str_save(ident, ATEMP); - multiline_push(&old_multiline, c); + nesting_push(&old_nesting, c); t->vars = wordlist(); t->left = dogroup(); - multiline_pop(&old_multiline); + nesting_pop(&old_nesting); break; case WHILE: case UNTIL: - multiline_push(&old_multiline, c); + nesting_push(&old_nesting, c); t = newtp((c == WHILE) ? TWHILE : TUNTIL); - t->left = c_list(); + t->left = c_list(TRUE); t->right = dogroup(); - multiline_pop(&old_multiline); + nesting_pop(&old_nesting); break; case CASE: t = newtp(TCASE); musthave(LWORD, 0); t->str = yylval.cp; - multiline_push(&old_multiline, c); + nesting_push(&old_nesting, c); t->left = caselist(); - multiline_pop(&old_multiline); + nesting_pop(&old_nesting); break; case IF: - multiline_push(&old_multiline, c); + nesting_push(&old_nesting, c); t = newtp(TIF); - t->left = c_list(); + t->left = c_list(TRUE); t->right = thenpart(); musthave(FI, KEYWORD|ALIAS); - multiline_pop(&old_multiline); + nesting_pop(&old_nesting); break; case BANG: @@ -434,7 +431,7 @@ dogroup() c = '}'; else syntaxerr((char *) 0); - list = c_list(); + list = c_list(TRUE); musthave(c, KEYWORD|ALIAS); return list; } @@ -446,7 +443,7 @@ thenpart() musthave(THEN, KEYWORD|ALIAS); t = newtp(0); - t->left = c_list(); + t->left = c_list(TRUE); if (t->left == NULL) syntaxerr((char *) 0); t->right = elsepart(); @@ -460,13 +457,13 @@ elsepart() switch (token(KEYWORD|ALIAS|VARASN)) { case ELSE: - if ((t = c_list()) == NULL) + if ((t = c_list(TRUE)) == NULL) syntaxerr((char *) 0); return (t); case ELIF: t = newtp(TELIF); - t->left = c_list(); + t->left = c_list(TRUE); t->right = thenpart(); return (t); @@ -524,7 +521,8 @@ casepart(endtok) t->vars = (char **) XPclose(ptns); musthave(')', 0); - t->left = c_list(); + t->left = c_list(TRUE); + /* Note: Posix requires the ;; */ if ((tpeek(CONTIN|KEYWORD|ALIAS)) != endtok) musthave(BREAK, CONTIN|KEYWORD|ALIAS); return (t); @@ -535,32 +533,23 @@ function_body(name, ksh_func) char *name; int ksh_func; /* function foo { ... } vs foo() { .. } */ { - XString xs; - char *xp, *p; + char *sname, *p; struct op *t; int old_func_parse; - Xinit(xs, xp, 16, ATEMP); - for (p = name; ; ) { - if ((*p == EOS && Xlength(xs, xp) == 0) - || (*p != EOS && *p != CHAR && *p != QCHAR - && *p != OQUOTE && *p != CQUOTE)) - { - p = snptreef((char *) 0, 32, "%S", name); - yyerror("%s: invalid function name\n", p); - } - Xcheck(xs, xp); - if (*p == EOS) { - Xput(xs, xp, '\0'); - break; - } else if (*p == CHAR || *p == QCHAR) { - Xput(xs, xp, p[1]); - p += 2; - } else - p++; /* OQUOTE/CQUOTE */ - } + sname = wdstrip(name); + /* Check for valid characters in name. posix and ksh93 say only + * allow [a-zA-Z_0-9] but this allows more as old pdksh's have + * allowed more (the following were never allowed: + * nul space nl tab $ ' " \ ` ( ) & | ; = < > + * C_QUOTE covers all but = and adds # [ ? *) + */ + for (p = sname; *p; p++) + if (ctype(*p, C_QUOTE) || *p == '=') + yyerror("%s: invalid function name\n", sname); + t = newtp(TFUNCT); - t->str = Xclose(xs, xp); + t->str = sname; t->u.ksh_func = ksh_func; /* Note that POSIX allows only compound statements after foo(), sh and @@ -596,6 +585,7 @@ wordlist() 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; @@ -709,10 +699,9 @@ syntaxerr(what) Again: switch (c) { case 0: - if (multiline.on && multiline.start_token) { - multiline.on = FALSE; /* avoid infinate loops */ - c = multiline.start_token; - source->errline = multiline.start_line; + if (nesting.start_token) { + c = nesting.start_token; + source->errline = nesting.start_line; what = "unmatched"; goto Again; } @@ -748,21 +737,20 @@ syntaxerr(what) } static void -multiline_push(save, tok) - struct multiline_state *save; +nesting_push(save, tok) + struct nesting_state *save; int tok; { - *save = multiline; - multiline.on = TRUE; - multiline.start_token = tok; - multiline.start_line = source->line; + *save = nesting; + nesting.start_token = tok; + nesting.start_line = source->line; } static void -multiline_pop(saved) - struct multiline_state *saved; +nesting_pop(saved) + struct nesting_state *saved; { - multiline = *saved; + nesting = *saved; } static struct op * @@ -785,10 +773,8 @@ struct op * compile(s) Source *s; { - yynerrs = 0; - multiline.on = s->type == SSTRING; - multiline.start_token = 0; - multiline.start_line = 0; + nesting.start_token = 0; + nesting.start_line = 0; herep = heres; source = s; yyparse(); @@ -803,7 +789,7 @@ compile(s) * a=[ab] * $ x=typeset; $x a=[ab]; echo "$a" * a=a - * $ + * $ */ static int assign_command(s) diff --git a/bin/ksh/table.h b/bin/ksh/table.h index 75215719c22..cdadbb9b6bd 100644 --- a/bin/ksh/table.h +++ b/bin/ksh/table.h @@ -1,4 +1,4 @@ -/* $OpenBSD: table.h,v 1.3 1996/11/21 07:59:35 downsj Exp $ */ +/* $OpenBSD: table.h,v 1.4 1998/06/25 19:02:22 millert Exp $ */ /* $From: table.h,v 1.3 1994/05/31 13:34:34 michael Exp $ */ @@ -112,8 +112,10 @@ struct block { /*struct arg_info argi;*/ char **argv; int argc; + int flags; /* see BF_* */ struct table vars; /* local variables */ struct table funs; /* local functions */ + Getopt getopts_state; #if 1 char * error; /* error handler */ char * exit; /* exit handler */ @@ -123,6 +125,9 @@ struct block { 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. */ @@ -169,8 +174,8 @@ extern const struct builtin shbuiltins [], kshbuiltins []; #define PS1 0 /* command */ #define PS2 1 /* command continuation */ -EXTERN const char *path; /* PATH value */ -EXTERN const char *def_path; /* path to use if PATH not set */ -EXTERN char *tmpdir; /* TMPDIR value */ -EXTERN const char *prompt; -EXTERN int cur_prompt; /* PS1 or PS2 */ +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 */ diff --git a/bin/ksh/tests/alias.t b/bin/ksh/tests/alias.t index 0db6a2594cd..511744b59c1 100644 --- a/bin/ksh/tests/alias.t +++ b/bin/ksh/tests/alias.t @@ -89,3 +89,24 @@ 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/bin/ksh/tests/eglob.t b/bin/ksh/tests/eglob.t index ea69f635fcf..9b1b4d71289 100644 --- a/bin/ksh/tests/eglob.t +++ b/bin/ksh/tests/eglob.t @@ -1,8 +1,9 @@ name: eglob-bad-1 description: Check that globbing isn't done when glob has syntax error -perl-setup: - &touch("abcx", "abcz", "bbc"); +file-setup: file 644 "abcx" +file-setup: file 644 "abcz" +file-setup: file 644 "bbc" stdin: echo !([*)* echo +(a|b[)* @@ -15,8 +16,9 @@ name: eglob-bad-2 description: Check that globbing isn't done when glob has syntax error (at&t ksh fails this test) -perl-setup: - &touch("abcx", "abcz", "bbc"); +file-setup: file 644 "abcx" +file-setup: file 644 "abcz" +file-setup: file 644 "bbc" stdin: echo [a*(]*)z expected-stdout: @@ -27,8 +29,7 @@ name: eglob-infinite-plus description: Check that shell doesn't go into infinite loop expanding +(...) expressions. -perl-setup: - &touch("abc"); +file-setup: file 644 "abc" time-limit: 3 stdin: echo +()c @@ -45,8 +46,7 @@ expected-stdout: name: eglob-subst-1 description: Check that eglobbing isn't done on substitution results -perl-setup: - &touch("abc"); +file-setup: file 644 "abc" stdin: x='@(*)' echo $x @@ -58,26 +58,31 @@ name: eglob-nomatch-1 description: Check that the pattern doesn't match stdin: - echo no-file+(a|b)stuff - echo no-file+(a*(c)|b)stuff + echo 1: no-file+(a|b)stuff + echo 2: no-file+(a*(c)|b)stuff + echo 3: no-file+((((c)))|b)stuff expected-stdout: - no-file+(a|b)stuff - no-file+(a*(c)|b)stuff + 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 -perl-setup: - &touch("abd", "acd"); +file-setup: file 644 "abd" +file-setup: file 644 "acd" +file-setup: file 644 "abac" stdin: - echo a+(b|c)d - echo a!(@(b|B))d - echo a[b*(foo|bar)]d + 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: - abd acd - acd - abd + 1: abd acd + 2: acd + 3: abac + 4: abd --- name: eglob-case-1 @@ -127,12 +132,14 @@ description: Check eglobing works in trims... stdin: x=abcdef - echo ${x#*(a|b)cd} - echo "${x#*(a|b)cd}" - echo ${x#"*(a|b)cd"} + 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: - ef - ef - abcdef + 1: ef + 2: ef + 3: abcdef + 4: cdef --- diff --git a/bin/ksh/tests/glob.t b/bin/ksh/tests/glob.t index c7d8e26808d..592b74737d5 100644 --- a/bin/ksh/tests/glob.t +++ b/bin/ksh/tests/glob.t @@ -1,9 +1,8 @@ name: glob-bad-1 description: Check that globbing isn't done when glob has syntax error -perl-setup: - mkdir("[x", 0777) || die "couldn't make directory [x - $!\n"; - &touch("[x/foo"); +file-setup: dir 755 "[x" +file-setup: file 644 "[x/foo" stdin: echo [* echo *[x @@ -17,10 +16,9 @@ expected-stdout: name: glob-bad-2 description: Check that symbolic links aren't stat()'d -perl-setup: - mkdir("dir", 0777) || die "couldn't make directory dir - $!\n"; - &touch("dir/abc"); - symlink("non-existent-file", "dir/abc"); +file-setup: dir 755 "dir" +file-setup: symlink 644 "dir/abc" + non-existant-file stdin: echo d*/* echo d*/abc @@ -32,8 +30,11 @@ expected-stdout: name: glob-range-1 description: Test range matching -perl-setup: - &touch(".bc", "abc", "bbc", "cbc", "-bc"); +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]* @@ -52,8 +53,7 @@ name: glob-range-2 description: Test range matching (at&t ksh fails this; POSIX says invalid) -perl-setup: - &touch("abc"); +file-setup: file 644 "abc" stdin: echo [a--]* expected-stdout: @@ -63,8 +63,7 @@ expected-stdout: name: glob-range-3 description: Check that globbing matches the right things... -perl-setup: - &touch("a\302c"); +file-setup: file 644 "aÂc" stdin: echo a[Á-Ú]* expected-stdout: @@ -74,8 +73,7 @@ expected-stdout: name: glob-range-4 description: Results unspecified according to POSIX -perl-setup: - &touch(".bc"); +file-setup: file 644 ".bc" stdin: echo [a.]* expected-stdout: @@ -86,8 +84,12 @@ name: glob-range-5 description: Results unspecified according to POSIX (at&t ksh treats this like [a-cc-e]*) -perl-setup: - &touch("abc", "bbc", "cbc", "dbc", "ebc", "-bc"); +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: diff --git a/bin/ksh/tests/history.t b/bin/ksh/tests/history.t index 078aeb60969..2b6df93154b 100644 --- a/bin/ksh/tests/history.t +++ b/bin/ksh/tests/history.t @@ -7,7 +7,8 @@ description: See if we can test history at all arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo hi fc -l @@ -23,7 +24,8 @@ description: Check if more recent command is executed arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo hi echo there @@ -42,7 +44,8 @@ description: is re-executed. arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: exec 2>&1 echo hi @@ -61,7 +64,8 @@ description: (ksh88 loops on this) arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: fc -e - echo ok @@ -76,7 +80,8 @@ description: Check if "fc -e -" command output goes to stdout. arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo abc fc -e - | (read x; echo "A $x") @@ -94,7 +99,8 @@ description: fc is replaced in history by new command. arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo abc def echo ghi jkl @@ -117,7 +123,8 @@ description: (ksh88 fails 'cause it lists the fc command) arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo line 1 echo line 2 @@ -140,7 +147,8 @@ description: (ksh88 fails 'cause it lists the fc command) arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo line 1 echo line 2 @@ -162,7 +170,8 @@ description: Can give number `options' to fc arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo line 1 echo line 2 @@ -185,7 +194,8 @@ description: -1 refers to previous command arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo line 1 echo line 2 @@ -207,7 +217,8 @@ description: List command stays in history arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo line 1 echo line 2 @@ -233,7 +244,8 @@ description: (ksh88 fails 'cause it lists the fc command) arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo line 1 echo line 2 @@ -258,7 +270,8 @@ description: fc allows too old/new errors in range specification arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file!HISTSIZE=3! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo line 1 echo line 2 @@ -284,7 +297,8 @@ description: test -r flag in history arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo line 1 echo line 2 @@ -310,7 +324,8 @@ description: If first is newer than last, -r is implied. arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo line 1 echo line 2 @@ -336,7 +351,8 @@ description: If first is newer than last, -r is cancelled. arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo line 1 echo line 2 @@ -362,7 +378,8 @@ description: Basic substitution arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo abc def echo ghi jkl @@ -380,7 +397,8 @@ description: Does subst find previous command? arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo abc def echo ghi jkl @@ -398,7 +416,8 @@ description: Does subst find previous command when no arguments given arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo abc def echo ghi jkl @@ -417,7 +436,8 @@ description: (ksh88 and ksh93 do not have -g option) arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo abc def asjj sadjhasdjh asdjhasd fc -e - -g a=FooBAR @@ -434,7 +454,8 @@ description: (ksh88/ksh93 don't have the ? prefix thing so they fail this test) arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo abc def echo ghi jkl @@ -453,7 +474,8 @@ description: that prints no prompts). arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo abc def fc echo @@ -474,7 +496,8 @@ description: Correct command is edited when number given arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo line 1 echo line 2 is here @@ -504,7 +527,8 @@ description: (ksh88 fails 'cause it lists the fc command) arguments: !-i! env-setup: !ENV=./Env!HISTFILE=hist.file! -perl-setup: system("echo PS1=X > Env"); +file-setup: file 644 "Env" + PS1=X stdin: echo abc def fc echo diff --git a/bin/ksh/tests/regress.t b/bin/ksh/tests/regress.t index 675ae53497a..cf99d21aaeb 100644 --- a/bin/ksh/tests/regress.t +++ b/bin/ksh/tests/regress.t @@ -768,3 +768,197 @@ expected-stdout: 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 +--- + + diff --git a/bin/ksh/tests/syntax.t b/bin/ksh/tests/syntax.t new file mode 100644 index 00000000000..3fe2c819706 --- /dev/null +++ b/bin/ksh/tests/syntax.t @@ -0,0 +1,10 @@ +name: syntax-1 +description: + Check that lone ampersand is a syntax error +stdin: + & +expected-exit: e != 0 +expected-stderr-pattern: + /syntax error/ +--- + diff --git a/bin/ksh/tests/th b/bin/ksh/tests/th index ebe54718ece..c2b821f9239 100644 --- a/bin/ksh/tests/th +++ b/bin/ksh/tests/th @@ -1,4 +1,4 @@ -#!/usr/bin/perl +#!/usr/local/bin/perl # # Test harness for pdksh tests. @@ -57,6 +57,16 @@ # 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. # time-limit Time limit - the program is sent a # SIGKILL N seconds. Default is no # limit. @@ -82,6 +92,8 @@ # M value can be multiple lines (prefixed by a tab) and consists # of multiple fields, delimited by a field seperator 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. # require 'signal.ph'; @@ -91,20 +103,22 @@ require 'getopts.pl'; ($prog = $0) =~ s#.*/##; $Usage = <<EOF ; -Usage: $prog [-s test-set] [-p prog] [-v] test-name ... +Usage: $prog [-s test-set] [-p prog] [-v] [-e e=v] test-name ... -p p Use p as the program to test -s s Read tests from file s; if s is a directory, it is recursively scaned for test files (which end in .t). -t t Use t as default time limit for tests (default is unlimited) + -P program (-p) string has multiple words, and the program is in + the path (kludge option) -v Verbose mode: print reason test failed. + -e e=v Set the environment variable e to v for all tests + (if no =v is given, the current value is used) test-name(s) specifies the name of the test(s) to run; if none are specified, all tests are run. EOF # -# r - required -# m - can be multi-line -# M - multi-line, but without trailing newline +# See comment above for flag meanings # %test_fields = ( 'name', 'r', @@ -115,6 +129,7 @@ EOF 'perl-setup', 'm', 'perl-cleanup', 'm', 'env-setup', 'M', + 'file-setup', 'mps', 'time-limit', '', 'expected-fail', '', 'expected-exit', '', @@ -142,7 +157,7 @@ $nxpassed = 0; %known_tests = (); -if (!&Getopts('p:s:t:v')) { +if (!&Getopts('p:Ps:t:ve:')) { print STDERR $Usage; exit 1; } @@ -157,6 +172,7 @@ if (defined $opt_t) { if $opt_t !~ /^\d+$/ || $opt_t <= 0; $default_time_limit = $opt_t; } +$program_kludge = defined $opt_P ? $opt_P : 0; # Note which tests are to be run. %do_test = (); @@ -168,6 +184,14 @@ $all_tests = @ARGV == 0; 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. @@ -183,8 +207,10 @@ 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); -$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; +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; +} @trap_sigs = ('TERM', 'QUIT', 'INT', 'PIPE', 'HUP'); @SIG{@trap_sigs} = ('cleanup_exit') x @trap_sigs; @@ -282,7 +308,7 @@ process_test_file local($ret); if (!open(IN, $file)) { - die "$prog: can't open $file - $!\n"; + print STDERR "$prog: can't open $file - $!\n"; return undef; } while (1) { @@ -323,6 +349,47 @@ run_test 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); + 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 '') { @@ -366,7 +433,11 @@ run_test print STDOUT "$prog: couldn't open $tempe in child - $!\n"; kill('TERM', $$); } - @argv = ($test_prog); + if ($program_kludge) { + @argv = split(' ', $test_prog); + } else { + @argv = ($test_prog); + } if (defined $test{'arguments'}) { push(@argv, split(substr($test{'arguments'}, 0, 1), @@ -586,21 +657,21 @@ first_diff { local($exp, $got) = @_; local($lineno, $char) = (1, 1); - local($i, $len); + local($i, $exp_len, $got_len); local($ce, $cg); - $len = length($exp); - if ($len != length($got)) { - if ($len < length($got)) { - if (substr($got, 0, $len) eq $exp) { + $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, $len) eq $got) { + } elsif (substr($exp, 0, $got_len) eq $got) { return "got too little output"; } - $len = length($got); } - for ($i = 0; $i < $len; $i++) { + for ($i = 0; $i < $exp_len; $i++) { $ce = substr($exp, $i, 1); $cg = substr($got, $i, 1); last if $ce ne $cg; @@ -610,7 +681,38 @@ first_diff $char = 1; } } - return "first difference: line $lineno, char $char" + 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 @@ -644,8 +746,10 @@ 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 /^ *#/; @@ -656,21 +760,43 @@ read_test return undef; } ($field, $val) = ($1, $2); - if (defined $test{$field}) { - print STDERR "$prog:$file:$.: multiple \"$field\" fields\n"; - return undef; - } + $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 '!') { + if ($val eq '' || $val eq '!' || $flags =~ /p/) { if ($flags =~ /[Mm]/) { - $do_chop = 1 if $val eq '!'; - $val = ''; + 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 .= $'; @@ -678,6 +804,47 @@ read_test 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: $type\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; + } + } } elsif ($val eq '') { print STDERR "$prog:$file:$.: no value given for field \"$field\"\n"; @@ -685,7 +852,7 @@ read_test } } $val .= "\n" if !$do_chop; - $test{$field} = $val; + $test{$sfield} = $val; redo if $need_redo; } if ($_ eq '') { @@ -781,21 +948,6 @@ read_test } sub -touch -{ - local(@files) = @_; - local($file); - - foreach $file (@files) { - if (!open(T, "> $file")) { - die "Couldn't touch $file\n"; - } - close(T); - } - return 1; -} - -sub tty_msg { local($msg) = @_; @@ -811,7 +963,6 @@ never_called_funcs { return 0; &tty_msg("hi\n"); - &touch("/tmp/foo"); &never_called_funcs(); &catch_sigalrm(); $old_env{'foo'} = 'bar'; diff --git a/bin/ksh/tests/th-sh b/bin/ksh/tests/th-sh new file mode 100644 index 00000000000..3ca78e8c955 --- /dev/null +++ b/bin/ksh/tests/th-sh @@ -0,0 +1,28 @@ +#!/bin/sh + +# +# Simple script to find perl and run it +# + +# Avoid common problems with ENV (though perl shouldn't let it through) +# (can you believe some shells don't have an unset???) +unset ENV + +x=x +[ -x /bin/sh ] 2> /dev/null || x=f + +IFS=:$IFS +perl= +for i in $PATH; do + [ X"$i" = X ] && i=. + for j in perl perl4 perl5 ; do + [ -$x "$i/$j" ] && perl=$i/$j && break 2 + done +done + +[ X"$perl" = X ] && { + echo "$0: can't find perl - bye\n" 1>&2 + exit 1 + } + +exec $perl "$@" diff --git a/bin/ksh/tests/unclass2.t b/bin/ksh/tests/unclass2.t index d9e14822e06..56be75156b9 100644 --- a/bin/ksh/tests/unclass2.t +++ b/bin/ksh/tests/unclass2.t @@ -15,12 +15,12 @@ name: xxx-set-option-1 description: Check option parsing in set stdin: - set -A -vs A 1 3 2 - echo ${A[*]} + set -vsA foo -- A 1 3 2 + echo ${foo[*]} expected-stderr: - echo ${A[*]} + echo ${foo[*]} expected-stdout: - 1 2 3 + 1 2 3 A --- name: xxx-exec-1 @@ -148,19 +148,16 @@ name: env-prompt description: Check that prompt not printed when processing ENV env-setup: !ENV=./foo! -perl-setup: - system("cat > foo << EOF - XXX=12 - PS1='X ' +file-setup: file 644 "foo" + XXX=_ + PS1=X false && echo hmmm - EOF - "); arguments: !-i! stdin: echo hi${XXX}there expected-stdout: - hi12there + hi_there expected-stderr: ! - X X + XX --- diff --git a/bin/ksh/tests/version.t b/bin/ksh/tests/version.t index 23d834015fa..048544b88d4 100644 --- a/bin/ksh/tests/version.t +++ b/bin/ksh/tests/version.t @@ -4,5 +4,5 @@ description: stdin: echo $KSH_VERSION expected-stdout: - @(#)PD KSH v5.2.12 96/10/29 + @(#)PD KSH v5.2.13 97/10/27 --- diff --git a/bin/ksh/tree.c b/bin/ksh/tree.c index 93e3f9d7de7..6539765f7f7 100644 --- a/bin/ksh/tree.c +++ b/bin/ksh/tree.c @@ -1,4 +1,4 @@ -/* $OpenBSD: tree.c,v 1.5 1997/09/01 18:30:15 deraadt Exp $ */ +/* $OpenBSD: tree.c,v 1.6 1998/06/25 19:02:24 millert Exp $ */ /* * command tree climbing @@ -153,7 +153,9 @@ ptree(t, indent, shf) fptreef(shf, indent, "%T& ", t->left); break; case TFUNCT: - fptreef(shf, indent, "function %s %T", t->str, t->left); + 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); @@ -282,6 +284,13 @@ tputS(wp, shf) { register int c, quoted=0; + /* problems: + * `...` -> $(...) + * 'foo' -> "foo" + * could change encoding to: + * OQUOTE ["'] ... CQUOTE ["'] + * COMSUB [(`] ...\0 (handle $ ` \ and maybe " in `...` case) + */ while (1) switch ((c = *wp++)) { case EOS: @@ -321,12 +330,14 @@ tputS(wp, shf) break; case OSUBST: tputc('$', shf); - tputc('{', shf); + if (*wp++ == '{') + tputc('{', shf); while ((c = *wp++) != 0) tputC(c, shf); break; case CSUBST: - tputc('}', shf); + if (*wp++ == '}') + tputc('}', shf); break; #ifdef KSH case OPAT: @@ -553,6 +564,7 @@ wdscan(wp, c) ; break; case CSUBST: + wp++; if (c == CSUBST && nest == 0) return (char *) wp; nest--; @@ -573,6 +585,78 @@ wdscan(wp, c) } } +/* return a copy of wp without any of the mark up characters and + * with quote characters (" ' \) stripped. + * (string is allocated from ATEMP) + */ +char * +wdstrip(wp) + const char *wp; +{ + struct shf shf; + int c; + + shf_sopen((char *) 0, 32, SHF_WR | SHF_DYNAMIC, &shf); + + /* problems: + * `...` -> $(...) + * x${foo:-"hi"} -> x${foo:-hi} + * x${foo:-'hi'} -> x${foo:-hi} + */ + while (1) + switch ((c = *wp++)) { + case EOS: + return shf_sclose(&shf); /* null terminates */ + case CHAR: + case QCHAR: + shf_putchar(*wp++, &shf); + break; + case COMSUB: + shf_putchar('$', &shf); + shf_putchar('(', &shf); + while (*wp != 0) + shf_putchar(*wp++, &shf); + shf_putchar(')', &shf); + break; + case EXPRSUB: + shf_putchar('$', &shf); + shf_putchar('(', &shf); + shf_putchar('(', &shf); + while (*wp != 0) + shf_putchar(*wp++, &shf); + shf_putchar(')', &shf); + shf_putchar(')', &shf); + break; + case OQUOTE: + break; + case CQUOTE: + break; + case OSUBST: + shf_putchar('$', &shf); + if (*wp++ == '{') + shf_putchar('{', &shf); + while ((c = *wp++) != 0) + shf_putchar(c, &shf); + break; + case CSUBST: + if (*wp++ == '}') + shf_putchar('}', &shf); + break; +#ifdef KSH + case OPAT: + shf_putchar(*wp++, &shf); + shf_putchar('(', &shf); + break; + case SPAT: + shf_putchar('|', &shf); + break; + case CPAT: + shf_putchar(')', &shf); + break; +#endif /* KSH */ + } +} + static struct ioword ** iocopy(iow, ap) register struct ioword **iow; diff --git a/bin/ksh/tree.h b/bin/ksh/tree.h index 863061dd91d..6c73cc85515 100644 --- a/bin/ksh/tree.h +++ b/bin/ksh/tree.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tree.h,v 1.2 1996/08/19 20:09:02 downsj Exp $ */ +/* $OpenBSD: tree.h,v 1.3 1998/06/25 19:02:25 millert Exp $ */ /* * command trees for compile/execute @@ -64,8 +64,8 @@ struct op { #define EXPRSUB 4 /* $(()) substitution (0 terminated) */ #define OQUOTE 5 /* opening " or ' */ #define CQUOTE 6 /* closing " or ' */ -#define OSUBST 7 /* opening ${ substitution */ -#define CSUBST 8 /* closing } of above */ +#define 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 /* seperate pattern: | */ #define CPAT 11 /* close pattern: ) */ diff --git a/bin/ksh/var.c b/bin/ksh/var.c index 8350846a148..5f6ec98832a 100644 --- a/bin/ksh/var.c +++ b/bin/ksh/var.c @@ -1,4 +1,4 @@ -/* $OpenBSD: var.c,v 1.6 1997/09/01 18:30:16 deraadt Exp $ */ +/* $OpenBSD: var.c,v 1.7 1998/06/25 19:02:27 millert Exp $ */ #include "sh.h" #include "ksh_time.h" @@ -36,6 +36,7 @@ newblock() static char *const empty[] = {null}; l = (struct block *) alloc(sizeof(struct block), ATEMP); + l->flags = 0; ainit(&l->area); if (!e->loc) { l->argc = 0; @@ -68,6 +69,8 @@ popblock() setspec(vq); else unsetspec(vq); + if (l->flags & BF_DOGETOPTS) + user_opt = l->getopts_state; afreeall(&l->area); afree(l, ATEMP); } @@ -883,6 +886,11 @@ getspec(vp) vp->flag |= SPECIAL; break; #endif /* HISTORY */ + case V_OPTIND: + vp->flag &= ~SPECIAL; + setint(vp, (long) user_opt.uoptind); + vp->flag |= SPECIAL; + break; } } @@ -894,7 +902,9 @@ setspec(vp) switch (special(vp->name)) { case V_PATH: - path = str_val(vp); + if (path) + afree(path, APERM); + path = str_save(str_val(vp), APERM); flushcom(1); /* clear tracked aliases */ break; case V_IFS: @@ -902,7 +912,9 @@ setspec(vp) 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); @@ -981,7 +993,9 @@ unsetspec(vp) { switch (special(vp->name)) { case V_PATH: - path = def_path; + if (path) + afree(path, APERM); + path = str_save(def_path, APERM); flushcom(1); /* clear tracked aliases */ break; case V_IFS: diff --git a/bin/ksh/version.c b/bin/ksh/version.c index 54394213105..78c24b233aa 100644 --- a/bin/ksh/version.c +++ b/bin/ksh/version.c @@ -1,4 +1,4 @@ -/* $OpenBSD: version.c,v 1.5 1996/11/21 07:59:37 downsj Exp $ */ +/* $OpenBSD: version.c,v 1.6 1998/06/25 19:02:30 millert Exp $ */ /* * value of $KSH_VERSION (or $SH_VERSION) @@ -7,4 +7,4 @@ #include "sh.h" const char ksh_version [] = - "@(#)PD KSH v5.2.12 96/10/29"; + "@(#)PD KSH v5.2.13 97/10/27"; |