]> git.saurik.com Git - apple/shell_cmds.git/commitdiff
shell_cmds-198.tar.gz macos-1012 macos-10121 macos-10122 macos-10123 macos-10124 macos-10125 macos-10126 os-x-1012 v198
authorApple <opensource@apple.com>
Wed, 18 Nov 2015 22:36:50 +0000 (22:36 +0000)
committerApple <opensource@apple.com>
Wed, 18 Nov 2015 22:36:50 +0000 (22:36 +0000)
549 files changed:
date/date.1
date/date.c
date/date.plist.part [new file with mode: 0644]
date/extern.h
date/netdate.c
date/vary.c
date/vary.h
kill/kill.1
kill/kill.c
kill/kill.plist.part [new file with mode: 0644]
mktemp/mktemp.1
mktemp/mktemp.plist.part
printf/printf.1
printf/printf.c
printf/printf.plist.part [new file with mode: 0644]
sh/Makefile [new file with mode: 0644]
sh/Makefile.depend [new file with mode: 0644]
sh/TOUR [new file with mode: 0644]
sh/alias.c [new file with mode: 0644]
sh/alias.h [new file with mode: 0644]
sh/arith.h [new file with mode: 0644]
sh/arith_yacc.c [new file with mode: 0644]
sh/arith_yacc.h [new file with mode: 0644]
sh/arith_yylex.c [new file with mode: 0644]
sh/bltin/bltin.h [new file with mode: 0644]
sh/bltin/echo.c [new file with mode: 0644]
sh/builtins.def [new file with mode: 0644]
sh/cd.c [new file with mode: 0644]
sh/cd.h [new file with mode: 0644]
sh/error.c [new file with mode: 0644]
sh/error.h [new file with mode: 0644]
sh/eval.c [new file with mode: 0644]
sh/eval.h [new file with mode: 0644]
sh/exec.c [new file with mode: 0644]
sh/exec.h [new file with mode: 0644]
sh/expand.c [new file with mode: 0644]
sh/expand.h [new file with mode: 0644]
sh/funcs/cmv [new file with mode: 0644]
sh/funcs/dirs [new file with mode: 0644]
sh/funcs/login [new file with mode: 0644]
sh/funcs/newgrp [new file with mode: 0644]
sh/funcs/popd [new file with mode: 0644]
sh/funcs/pushd [new file with mode: 0644]
sh/funcs/suspend [new file with mode: 0644]
sh/histedit.c [new file with mode: 0644]
sh/input.c [new file with mode: 0644]
sh/input.h [new file with mode: 0644]
sh/jobs.c [new file with mode: 0644]
sh/jobs.h [new file with mode: 0644]
sh/mail.c [new file with mode: 0644]
sh/mail.h [new file with mode: 0644]
sh/main.c [new file with mode: 0644]
sh/main.h [new file with mode: 0644]
sh/memalloc.c [new file with mode: 0644]
sh/memalloc.h [new file with mode: 0644]
sh/miscbltin.c [new file with mode: 0644]
sh/mkbuiltins [new file with mode: 0755]
sh/mknodes.c [new file with mode: 0644]
sh/mksyntax.c [new file with mode: 0644]
sh/mktokens [new file with mode: 0644]
sh/myhistedit.h [new file with mode: 0644]
sh/mystring.c [new file with mode: 0644]
sh/mystring.h [new file with mode: 0644]
sh/nodes.c.pat [new file with mode: 0644]
sh/nodetypes [new file with mode: 0644]
sh/options.c [new file with mode: 0644]
sh/options.h [new file with mode: 0644]
sh/output.c [new file with mode: 0644]
sh/output.h [new file with mode: 0644]
sh/parser.c [new file with mode: 0644]
sh/parser.h [new file with mode: 0644]
sh/redir.c [new file with mode: 0644]
sh/redir.h [new file with mode: 0644]
sh/sh.1 [new file with mode: 0644]
sh/sh.plist.part [new file with mode: 0644]
sh/shell.h [new file with mode: 0644]
sh/show.c [new file with mode: 0644]
sh/show.h [new file with mode: 0644]
sh/tests/Makefile [new file with mode: 0644]
sh/tests/builtins/Makefile [new file with mode: 0644]
sh/tests/builtins/alias.0 [new file with mode: 0644]
sh/tests/builtins/alias.0.stdout [new file with mode: 0644]
sh/tests/builtins/alias.1 [new file with mode: 0644]
sh/tests/builtins/alias.1.stderr [new file with mode: 0644]
sh/tests/builtins/alias3.0 [new file with mode: 0644]
sh/tests/builtins/alias3.0.stdout [new file with mode: 0644]
sh/tests/builtins/alias4.0 [new file with mode: 0644]
sh/tests/builtins/break1.0 [new file with mode: 0644]
sh/tests/builtins/break2.0 [new file with mode: 0644]
sh/tests/builtins/break2.0.stdout [new file with mode: 0644]
sh/tests/builtins/break3.0 [new file with mode: 0644]
sh/tests/builtins/break4.4 [new file with mode: 0644]
sh/tests/builtins/break5.4 [new file with mode: 0644]
sh/tests/builtins/break6.0 [new file with mode: 0644]
sh/tests/builtins/builtin1.0 [new file with mode: 0644]
sh/tests/builtins/case1.0 [new file with mode: 0644]
sh/tests/builtins/case10.0 [new file with mode: 0644]
sh/tests/builtins/case11.0 [new file with mode: 0644]
sh/tests/builtins/case12.0 [new file with mode: 0644]
sh/tests/builtins/case13.0 [new file with mode: 0644]
sh/tests/builtins/case14.0 [new file with mode: 0644]
sh/tests/builtins/case15.0 [new file with mode: 0644]
sh/tests/builtins/case16.0 [new file with mode: 0644]
sh/tests/builtins/case17.0 [new file with mode: 0644]
sh/tests/builtins/case18.0 [new file with mode: 0644]
sh/tests/builtins/case19.0 [new file with mode: 0644]
sh/tests/builtins/case2.0 [new file with mode: 0644]
sh/tests/builtins/case3.0 [new file with mode: 0644]
sh/tests/builtins/case4.0 [new file with mode: 0644]
sh/tests/builtins/case5.0 [new file with mode: 0644]
sh/tests/builtins/case6.0 [new file with mode: 0644]
sh/tests/builtins/case7.0 [new file with mode: 0644]
sh/tests/builtins/case8.0 [new file with mode: 0644]
sh/tests/builtins/case9.0 [new file with mode: 0644]
sh/tests/builtins/cd1.0 [new file with mode: 0644]
sh/tests/builtins/cd2.0 [new file with mode: 0644]
sh/tests/builtins/cd3.0 [new file with mode: 0644]
sh/tests/builtins/cd4.0 [new file with mode: 0644]
sh/tests/builtins/cd5.0 [new file with mode: 0644]
sh/tests/builtins/cd6.0 [new file with mode: 0644]
sh/tests/builtins/cd7.0 [new file with mode: 0644]
sh/tests/builtins/cd8.0 [new file with mode: 0644]
sh/tests/builtins/command1.0 [new file with mode: 0644]
sh/tests/builtins/command10.0 [new file with mode: 0644]
sh/tests/builtins/command11.0 [new file with mode: 0644]
sh/tests/builtins/command12.0 [new file with mode: 0644]
sh/tests/builtins/command2.0 [new file with mode: 0644]
sh/tests/builtins/command3.0 [new file with mode: 0644]
sh/tests/builtins/command3.0.stdout [new file with mode: 0644]
sh/tests/builtins/command4.0 [new file with mode: 0644]
sh/tests/builtins/command5.0 [new file with mode: 0644]
sh/tests/builtins/command5.0.stdout [new file with mode: 0644]
sh/tests/builtins/command6.0 [new file with mode: 0644]
sh/tests/builtins/command6.0.stdout [new file with mode: 0644]
sh/tests/builtins/command7.0 [new file with mode: 0644]
sh/tests/builtins/command8.0 [new file with mode: 0644]
sh/tests/builtins/command9.0 [new file with mode: 0644]
sh/tests/builtins/dot1.0 [new file with mode: 0644]
sh/tests/builtins/dot2.0 [new file with mode: 0644]
sh/tests/builtins/dot3.0 [new file with mode: 0644]
sh/tests/builtins/dot4.0 [new file with mode: 0644]
sh/tests/builtins/eval1.0 [new file with mode: 0644]
sh/tests/builtins/eval2.0 [new file with mode: 0644]
sh/tests/builtins/eval3.0 [new file with mode: 0644]
sh/tests/builtins/eval4.0 [new file with mode: 0644]
sh/tests/builtins/eval5.0 [new file with mode: 0644]
sh/tests/builtins/eval6.0 [new file with mode: 0644]
sh/tests/builtins/eval7.0 [new file with mode: 0644]
sh/tests/builtins/eval8.7 [new file with mode: 0644]
sh/tests/builtins/exec1.0 [new file with mode: 0644]
sh/tests/builtins/exec2.0 [new file with mode: 0644]
sh/tests/builtins/exit1.0 [new file with mode: 0644]
sh/tests/builtins/exit2.8 [new file with mode: 0644]
sh/tests/builtins/exit3.0 [new file with mode: 0644]
sh/tests/builtins/export1.0 [new file with mode: 0644]
sh/tests/builtins/fc1.0 [new file with mode: 0644]
sh/tests/builtins/fc2.0 [new file with mode: 0644]
sh/tests/builtins/for1.0 [new file with mode: 0644]
sh/tests/builtins/for2.0 [new file with mode: 0644]
sh/tests/builtins/for3.0 [new file with mode: 0644]
sh/tests/builtins/getopts1.0 [new file with mode: 0644]
sh/tests/builtins/getopts1.0.stdout [new file with mode: 0644]
sh/tests/builtins/getopts2.0 [new file with mode: 0644]
sh/tests/builtins/getopts2.0.stdout [new file with mode: 0644]
sh/tests/builtins/getopts3.0 [new file with mode: 0644]
sh/tests/builtins/getopts4.0 [new file with mode: 0644]
sh/tests/builtins/getopts5.0 [new file with mode: 0644]
sh/tests/builtins/getopts6.0 [new file with mode: 0644]
sh/tests/builtins/getopts7.0 [new file with mode: 0644]
sh/tests/builtins/getopts8.0 [new file with mode: 0644]
sh/tests/builtins/getopts8.0.stdout [new file with mode: 0644]
sh/tests/builtins/getopts9.0 [new file with mode: 0644]
sh/tests/builtins/getopts9.0.stdout [new file with mode: 0644]
sh/tests/builtins/hash1.0 [new file with mode: 0644]
sh/tests/builtins/hash1.0.stdout [new file with mode: 0644]
sh/tests/builtins/hash2.0 [new file with mode: 0644]
sh/tests/builtins/hash2.0.stdout [new file with mode: 0644]
sh/tests/builtins/hash3.0 [new file with mode: 0644]
sh/tests/builtins/hash3.0.stdout [new file with mode: 0644]
sh/tests/builtins/hash4.0 [new file with mode: 0644]
sh/tests/builtins/jobid1.0 [new file with mode: 0644]
sh/tests/builtins/jobid2.0 [new file with mode: 0644]
sh/tests/builtins/kill1.0 [new file with mode: 0644]
sh/tests/builtins/kill2.0 [new file with mode: 0644]
sh/tests/builtins/lineno.0 [new file with mode: 0644]
sh/tests/builtins/lineno.0.stdout [new file with mode: 0644]
sh/tests/builtins/lineno2.0 [new file with mode: 0644]
sh/tests/builtins/lineno3.0 [new file with mode: 0644]
sh/tests/builtins/lineno3.0.stdout [new file with mode: 0644]
sh/tests/builtins/local1.0 [new file with mode: 0644]
sh/tests/builtins/local2.0 [new file with mode: 0644]
sh/tests/builtins/local3.0 [new file with mode: 0644]
sh/tests/builtins/local4.0 [new file with mode: 0644]
sh/tests/builtins/locale1.0 [new file with mode: 0644]
sh/tests/builtins/printf1.0 [new file with mode: 0644]
sh/tests/builtins/printf2.0 [new file with mode: 0644]
sh/tests/builtins/printf3.0 [new file with mode: 0644]
sh/tests/builtins/printf4.0 [new file with mode: 0644]
sh/tests/builtins/read1.0 [new file with mode: 0644]
sh/tests/builtins/read1.0.stdout [new file with mode: 0644]
sh/tests/builtins/read2.0 [new file with mode: 0644]
sh/tests/builtins/read3.0 [new file with mode: 0644]
sh/tests/builtins/read3.0.stdout [new file with mode: 0644]
sh/tests/builtins/read4.0 [new file with mode: 0644]
sh/tests/builtins/read4.0.stdout [new file with mode: 0644]
sh/tests/builtins/read5.0 [new file with mode: 0644]
sh/tests/builtins/read6.0 [new file with mode: 0644]
sh/tests/builtins/read7.0 [new file with mode: 0644]
sh/tests/builtins/return1.0 [new file with mode: 0644]
sh/tests/builtins/return2.1 [new file with mode: 0644]
sh/tests/builtins/return3.1 [new file with mode: 0644]
sh/tests/builtins/return4.0 [new file with mode: 0644]
sh/tests/builtins/return5.0 [new file with mode: 0644]
sh/tests/builtins/return6.4 [new file with mode: 0644]
sh/tests/builtins/return7.4 [new file with mode: 0644]
sh/tests/builtins/return8.0 [new file with mode: 0644]
sh/tests/builtins/set1.0 [new file with mode: 0644]
sh/tests/builtins/set2.0 [new file with mode: 0644]
sh/tests/builtins/trap1.0 [new file with mode: 0644]
sh/tests/builtins/trap10.0 [new file with mode: 0644]
sh/tests/builtins/trap11.0 [new file with mode: 0644]
sh/tests/builtins/trap12.0 [new file with mode: 0644]
sh/tests/builtins/trap13.0 [new file with mode: 0644]
sh/tests/builtins/trap14.0 [new file with mode: 0644]
sh/tests/builtins/trap15.0 [new file with mode: 0644]
sh/tests/builtins/trap16.0 [new file with mode: 0644]
sh/tests/builtins/trap2.0 [new file with mode: 0644]
sh/tests/builtins/trap3.0 [new file with mode: 0644]
sh/tests/builtins/trap4.0 [new file with mode: 0644]
sh/tests/builtins/trap5.0 [new file with mode: 0644]
sh/tests/builtins/trap6.0 [new file with mode: 0644]
sh/tests/builtins/trap7.0 [new file with mode: 0644]
sh/tests/builtins/trap8.0 [new file with mode: 0644]
sh/tests/builtins/trap9.0 [new file with mode: 0644]
sh/tests/builtins/type1.0 [new file with mode: 0644]
sh/tests/builtins/type1.0.stderr [new file with mode: 0644]
sh/tests/builtins/type2.0 [new file with mode: 0644]
sh/tests/builtins/type3.0 [new file with mode: 0644]
sh/tests/builtins/unalias.0 [new file with mode: 0644]
sh/tests/builtins/var-assign.0 [new file with mode: 0644]
sh/tests/builtins/var-assign2.0 [new file with mode: 0644]
sh/tests/builtins/wait1.0 [new file with mode: 0644]
sh/tests/builtins/wait10.0 [new file with mode: 0644]
sh/tests/builtins/wait2.0 [new file with mode: 0644]
sh/tests/builtins/wait3.0 [new file with mode: 0644]
sh/tests/builtins/wait4.0 [new file with mode: 0644]
sh/tests/builtins/wait5.0 [new file with mode: 0644]
sh/tests/builtins/wait6.0 [new file with mode: 0644]
sh/tests/builtins/wait7.0 [new file with mode: 0644]
sh/tests/builtins/wait8.0 [new file with mode: 0644]
sh/tests/builtins/wait9.127 [new file with mode: 0644]
sh/tests/errors/Makefile [new file with mode: 0644]
sh/tests/errors/assignment-error1.0 [new file with mode: 0644]
sh/tests/errors/assignment-error2.0 [new file with mode: 0644]
sh/tests/errors/backquote-error1.0 [new file with mode: 0644]
sh/tests/errors/backquote-error2.0 [new file with mode: 0644]
sh/tests/errors/bad-binary1.126 [new file with mode: 0644]
sh/tests/errors/bad-keyword1.0 [new file with mode: 0644]
sh/tests/errors/bad-parm-exp1.0 [new file with mode: 0644]
sh/tests/errors/bad-parm-exp2.2 [new file with mode: 0644]
sh/tests/errors/bad-parm-exp2.2.stderr [new file with mode: 0644]
sh/tests/errors/bad-parm-exp3.2 [new file with mode: 0644]
sh/tests/errors/bad-parm-exp3.2.stderr [new file with mode: 0644]
sh/tests/errors/bad-parm-exp4.2 [new file with mode: 0644]
sh/tests/errors/bad-parm-exp4.2.stderr [new file with mode: 0644]
sh/tests/errors/bad-parm-exp5.2 [new file with mode: 0644]
sh/tests/errors/bad-parm-exp5.2.stderr [new file with mode: 0644]
sh/tests/errors/bad-parm-exp6.2 [new file with mode: 0644]
sh/tests/errors/bad-parm-exp6.2.stderr [new file with mode: 0644]
sh/tests/errors/option-error.0 [new file with mode: 0644]
sh/tests/errors/redirection-error.0 [new file with mode: 0644]
sh/tests/errors/redirection-error2.2 [new file with mode: 0644]
sh/tests/errors/redirection-error3.0 [new file with mode: 0644]
sh/tests/errors/redirection-error4.0 [new file with mode: 0644]
sh/tests/errors/redirection-error5.0 [new file with mode: 0644]
sh/tests/errors/redirection-error6.0 [new file with mode: 0644]
sh/tests/errors/redirection-error7.0 [new file with mode: 0644]
sh/tests/errors/write-error1.0 [new file with mode: 0644]
sh/tests/execution/Makefile [new file with mode: 0644]
sh/tests/execution/bg1.0 [new file with mode: 0644]
sh/tests/execution/bg10.0 [new file with mode: 0644]
sh/tests/execution/bg10.0.stdout [new file with mode: 0644]
sh/tests/execution/bg2.0 [new file with mode: 0644]
sh/tests/execution/bg3.0 [new file with mode: 0644]
sh/tests/execution/bg4.0 [new file with mode: 0644]
sh/tests/execution/bg5.0 [new file with mode: 0644]
sh/tests/execution/bg6.0 [new file with mode: 0644]
sh/tests/execution/bg6.0.stdout [new file with mode: 0644]
sh/tests/execution/bg7.0 [new file with mode: 0644]
sh/tests/execution/bg8.0 [new file with mode: 0644]
sh/tests/execution/bg9.0 [new file with mode: 0644]
sh/tests/execution/fork1.0 [new file with mode: 0644]
sh/tests/execution/fork2.0 [new file with mode: 0644]
sh/tests/execution/fork3.0 [new file with mode: 0644]
sh/tests/execution/func1.0 [new file with mode: 0644]
sh/tests/execution/func2.0 [new file with mode: 0644]
sh/tests/execution/func3.0 [new file with mode: 0644]
sh/tests/execution/hash1.0 [new file with mode: 0644]
sh/tests/execution/int-cmd1.0 [new file with mode: 0644]
sh/tests/execution/killed1.0 [new file with mode: 0644]
sh/tests/execution/killed2.0 [new file with mode: 0644]
sh/tests/execution/not1.0 [new file with mode: 0644]
sh/tests/execution/not2.0 [new file with mode: 0644]
sh/tests/execution/path1.0 [new file with mode: 0644]
sh/tests/execution/redir1.0 [new file with mode: 0644]
sh/tests/execution/redir2.0 [new file with mode: 0644]
sh/tests/execution/redir3.0 [new file with mode: 0644]
sh/tests/execution/redir4.0 [new file with mode: 0644]
sh/tests/execution/redir5.0 [new file with mode: 0644]
sh/tests/execution/redir6.0 [new file with mode: 0644]
sh/tests/execution/redir7.0 [new file with mode: 0644]
sh/tests/execution/set-n1.0 [new file with mode: 0644]
sh/tests/execution/set-n2.0 [new file with mode: 0644]
sh/tests/execution/set-n3.0 [new file with mode: 0644]
sh/tests/execution/set-n4.0 [new file with mode: 0644]
sh/tests/execution/set-x1.0 [new file with mode: 0644]
sh/tests/execution/set-x2.0 [new file with mode: 0644]
sh/tests/execution/set-x3.0 [new file with mode: 0644]
sh/tests/execution/set-x4.0 [new file with mode: 0644]
sh/tests/execution/shellproc1.0 [new file with mode: 0644]
sh/tests/execution/subshell1.0 [new file with mode: 0644]
sh/tests/execution/subshell1.0.stdout [new file with mode: 0644]
sh/tests/execution/subshell2.0 [new file with mode: 0644]
sh/tests/execution/subshell3.0 [new file with mode: 0644]
sh/tests/execution/subshell4.0 [new file with mode: 0644]
sh/tests/execution/unknown1.0 [new file with mode: 0644]
sh/tests/execution/var-assign1.0 [new file with mode: 0644]
sh/tests/expansion/Makefile [new file with mode: 0644]
sh/tests/expansion/arith1.0 [new file with mode: 0644]
sh/tests/expansion/arith10.0 [new file with mode: 0644]
sh/tests/expansion/arith11.0 [new file with mode: 0644]
sh/tests/expansion/arith12.0 [new file with mode: 0644]
sh/tests/expansion/arith13.0 [new file with mode: 0644]
sh/tests/expansion/arith14.0 [new file with mode: 0644]
sh/tests/expansion/arith2.0 [new file with mode: 0644]
sh/tests/expansion/arith3.0 [new file with mode: 0644]
sh/tests/expansion/arith4.0 [new file with mode: 0644]
sh/tests/expansion/arith5.0 [new file with mode: 0644]
sh/tests/expansion/arith6.0 [new file with mode: 0644]
sh/tests/expansion/arith7.0 [new file with mode: 0644]
sh/tests/expansion/arith8.0 [new file with mode: 0644]
sh/tests/expansion/arith9.0 [new file with mode: 0644]
sh/tests/expansion/assign1.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst1.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst10.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst11.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst12.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst13.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst14.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst15.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst16.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst17.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst2.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst3.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst4.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst5.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst6.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst7.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst8.0 [new file with mode: 0644]
sh/tests/expansion/cmdsubst9.0 [new file with mode: 0644]
sh/tests/expansion/export1.0 [new file with mode: 0644]
sh/tests/expansion/export2.0 [new file with mode: 0644]
sh/tests/expansion/export3.0 [new file with mode: 0644]
sh/tests/expansion/heredoc1.0 [new file with mode: 0644]
sh/tests/expansion/heredoc2.0 [new file with mode: 0644]
sh/tests/expansion/ifs1.0 [new file with mode: 0644]
sh/tests/expansion/ifs2.0 [new file with mode: 0644]
sh/tests/expansion/ifs3.0 [new file with mode: 0644]
sh/tests/expansion/ifs4.0 [new file with mode: 0644]
sh/tests/expansion/ifs5.0 [new file with mode: 0644]
sh/tests/expansion/ifs6.0 [new file with mode: 0644]
sh/tests/expansion/ifs7.0 [new file with mode: 0644]
sh/tests/expansion/length1.0 [new file with mode: 0644]
sh/tests/expansion/length2.0 [new file with mode: 0644]
sh/tests/expansion/length3.0 [new file with mode: 0644]
sh/tests/expansion/length4.0 [new file with mode: 0644]
sh/tests/expansion/length5.0 [new file with mode: 0644]
sh/tests/expansion/length6.0 [new file with mode: 0644]
sh/tests/expansion/length7.0 [new file with mode: 0644]
sh/tests/expansion/length8.0 [new file with mode: 0644]
sh/tests/expansion/local1.0 [new file with mode: 0644]
sh/tests/expansion/local2.0 [new file with mode: 0644]
sh/tests/expansion/pathname1.0 [new file with mode: 0644]
sh/tests/expansion/pathname2.0 [new file with mode: 0644]
sh/tests/expansion/pathname3.0 [new file with mode: 0644]
sh/tests/expansion/pathname4.0 [new file with mode: 0644]
sh/tests/expansion/pathname5.0 [new file with mode: 0644]
sh/tests/expansion/plus-minus1.0 [new file with mode: 0644]
sh/tests/expansion/plus-minus2.0 [new file with mode: 0644]
sh/tests/expansion/plus-minus3.0 [new file with mode: 0644]
sh/tests/expansion/plus-minus4.0 [new file with mode: 0644]
sh/tests/expansion/plus-minus5.0 [new file with mode: 0644]
sh/tests/expansion/plus-minus6.0 [new file with mode: 0644]
sh/tests/expansion/plus-minus7.0 [new file with mode: 0644]
sh/tests/expansion/plus-minus8.0 [new file with mode: 0644]
sh/tests/expansion/question1.0 [new file with mode: 0644]
sh/tests/expansion/readonly1.0 [new file with mode: 0644]
sh/tests/expansion/redir1.0 [new file with mode: 0644]
sh/tests/expansion/set-u1.0 [new file with mode: 0644]
sh/tests/expansion/set-u2.0 [new file with mode: 0644]
sh/tests/expansion/set-u3.0 [new file with mode: 0644]
sh/tests/expansion/tilde1.0 [new file with mode: 0644]
sh/tests/expansion/tilde2.0 [new file with mode: 0644]
sh/tests/expansion/trim1.0 [new file with mode: 0644]
sh/tests/expansion/trim2.0 [new file with mode: 0644]
sh/tests/expansion/trim3.0 [new file with mode: 0644]
sh/tests/expansion/trim4.0 [new file with mode: 0644]
sh/tests/expansion/trim5.0 [new file with mode: 0644]
sh/tests/expansion/trim6.0 [new file with mode: 0644]
sh/tests/expansion/trim7.0 [new file with mode: 0644]
sh/tests/expansion/trim8.0 [new file with mode: 0644]
sh/tests/functional_test.sh [new file with mode: 0755]
sh/tests/parameters/Makefile [new file with mode: 0644]
sh/tests/parameters/env1.0 [new file with mode: 0644]
sh/tests/parameters/exitstatus1.0 [new file with mode: 0644]
sh/tests/parameters/mail1.0 [new file with mode: 0644]
sh/tests/parameters/mail2.0 [new file with mode: 0644]
sh/tests/parameters/optind1.0 [new file with mode: 0644]
sh/tests/parameters/optind2.0 [new file with mode: 0644]
sh/tests/parameters/positional1.0 [new file with mode: 0644]
sh/tests/parameters/positional2.0 [new file with mode: 0644]
sh/tests/parameters/positional3.0 [new file with mode: 0644]
sh/tests/parameters/positional4.0 [new file with mode: 0644]
sh/tests/parameters/positional5.0 [new file with mode: 0644]
sh/tests/parameters/positional6.0 [new file with mode: 0644]
sh/tests/parameters/positional7.0 [new file with mode: 0644]
sh/tests/parameters/pwd1.0 [new file with mode: 0644]
sh/tests/parameters/pwd2.0 [new file with mode: 0644]
sh/tests/parser/Makefile [new file with mode: 0644]
sh/tests/parser/alias1.0 [new file with mode: 0644]
sh/tests/parser/alias10.0 [new file with mode: 0644]
sh/tests/parser/alias11.0 [new file with mode: 0644]
sh/tests/parser/alias12.0 [new file with mode: 0644]
sh/tests/parser/alias13.0 [new file with mode: 0644]
sh/tests/parser/alias14.0 [new file with mode: 0644]
sh/tests/parser/alias15.0 [new file with mode: 0644]
sh/tests/parser/alias15.0.stdout [new file with mode: 0644]
sh/tests/parser/alias2.0 [new file with mode: 0644]
sh/tests/parser/alias3.0 [new file with mode: 0644]
sh/tests/parser/alias4.0 [new file with mode: 0644]
sh/tests/parser/alias5.0 [new file with mode: 0644]
sh/tests/parser/alias6.0 [new file with mode: 0644]
sh/tests/parser/alias7.0 [new file with mode: 0644]
sh/tests/parser/alias8.0 [new file with mode: 0644]
sh/tests/parser/alias9.0 [new file with mode: 0644]
sh/tests/parser/and-pipe-not.0 [new file with mode: 0644]
sh/tests/parser/case1.0 [new file with mode: 0644]
sh/tests/parser/case2.0 [new file with mode: 0644]
sh/tests/parser/dollar-quote1.0 [new file with mode: 0644]
sh/tests/parser/dollar-quote10.0 [new file with mode: 0644]
sh/tests/parser/dollar-quote11.0 [new file with mode: 0644]
sh/tests/parser/dollar-quote2.0 [new file with mode: 0644]
sh/tests/parser/dollar-quote3.0 [new file with mode: 0644]
sh/tests/parser/dollar-quote4.0 [new file with mode: 0644]
sh/tests/parser/dollar-quote5.0 [new file with mode: 0644]
sh/tests/parser/dollar-quote6.0 [new file with mode: 0644]
sh/tests/parser/dollar-quote7.0 [new file with mode: 0644]
sh/tests/parser/dollar-quote8.0 [new file with mode: 0644]
sh/tests/parser/dollar-quote9.0 [new file with mode: 0644]
sh/tests/parser/empty-braces1.0 [new file with mode: 0644]
sh/tests/parser/empty-cmd1.0 [new file with mode: 0644]
sh/tests/parser/for1.0 [new file with mode: 0644]
sh/tests/parser/for2.0 [new file with mode: 0644]
sh/tests/parser/func1.0 [new file with mode: 0644]
sh/tests/parser/func2.0 [new file with mode: 0644]
sh/tests/parser/func3.0 [new file with mode: 0644]
sh/tests/parser/heredoc1.0 [new file with mode: 0644]
sh/tests/parser/heredoc10.0 [new file with mode: 0644]
sh/tests/parser/heredoc11.0 [new file with mode: 0644]
sh/tests/parser/heredoc12.0 [new file with mode: 0644]
sh/tests/parser/heredoc2.0 [new file with mode: 0644]
sh/tests/parser/heredoc3.0 [new file with mode: 0644]
sh/tests/parser/heredoc4.0 [new file with mode: 0644]
sh/tests/parser/heredoc5.0 [new file with mode: 0644]
sh/tests/parser/heredoc6.0 [new file with mode: 0644]
sh/tests/parser/heredoc7.0 [new file with mode: 0644]
sh/tests/parser/heredoc8.0 [new file with mode: 0644]
sh/tests/parser/heredoc9.0 [new file with mode: 0644]
sh/tests/parser/line-cont1.0 [new file with mode: 0644]
sh/tests/parser/line-cont10.0 [new file with mode: 0644]
sh/tests/parser/line-cont11.0 [new file with mode: 0644]
sh/tests/parser/line-cont2.0 [new file with mode: 0644]
sh/tests/parser/line-cont3.0 [new file with mode: 0644]
sh/tests/parser/line-cont4.0 [new file with mode: 0644]
sh/tests/parser/line-cont5.0 [new file with mode: 0644]
sh/tests/parser/line-cont6.0 [new file with mode: 0644]
sh/tests/parser/line-cont7.0 [new file with mode: 0644]
sh/tests/parser/line-cont8.0 [new file with mode: 0644]
sh/tests/parser/line-cont9.0 [new file with mode: 0644]
sh/tests/parser/no-space1.0 [new file with mode: 0644]
sh/tests/parser/no-space2.0 [new file with mode: 0644]
sh/tests/parser/only-redir1.0 [new file with mode: 0644]
sh/tests/parser/only-redir2.0 [new file with mode: 0644]
sh/tests/parser/only-redir3.0 [new file with mode: 0644]
sh/tests/parser/only-redir4.0 [new file with mode: 0644]
sh/tests/parser/pipe-not1.0 [new file with mode: 0644]
sh/tests/parser/var-assign1.0 [new file with mode: 0644]
sh/tests/set-e/Makefile [new file with mode: 0644]
sh/tests/set-e/and1.0 [new file with mode: 0644]
sh/tests/set-e/and2.1 [new file with mode: 0644]
sh/tests/set-e/and3.0 [new file with mode: 0644]
sh/tests/set-e/and4.0 [new file with mode: 0644]
sh/tests/set-e/background1.0 [new file with mode: 0644]
sh/tests/set-e/cmd1.0 [new file with mode: 0644]
sh/tests/set-e/cmd2.1 [new file with mode: 0644]
sh/tests/set-e/elif1.0 [new file with mode: 0644]
sh/tests/set-e/elif2.0 [new file with mode: 0644]
sh/tests/set-e/eval1.0 [new file with mode: 0644]
sh/tests/set-e/eval2.1 [new file with mode: 0644]
sh/tests/set-e/for1.0 [new file with mode: 0644]
sh/tests/set-e/func1.0 [new file with mode: 0644]
sh/tests/set-e/func2.1 [new file with mode: 0644]
sh/tests/set-e/if1.0 [new file with mode: 0644]
sh/tests/set-e/if2.0 [new file with mode: 0644]
sh/tests/set-e/if3.0 [new file with mode: 0644]
sh/tests/set-e/not1.0 [new file with mode: 0644]
sh/tests/set-e/not2.0 [new file with mode: 0644]
sh/tests/set-e/or1.0 [new file with mode: 0644]
sh/tests/set-e/or2.0 [new file with mode: 0644]
sh/tests/set-e/or3.1 [new file with mode: 0644]
sh/tests/set-e/pipe1.1 [new file with mode: 0644]
sh/tests/set-e/pipe2.0 [new file with mode: 0644]
sh/tests/set-e/return1.0 [new file with mode: 0644]
sh/tests/set-e/semi1.1 [new file with mode: 0644]
sh/tests/set-e/semi2.1 [new file with mode: 0644]
sh/tests/set-e/subshell1.0 [new file with mode: 0644]
sh/tests/set-e/subshell2.1 [new file with mode: 0644]
sh/tests/set-e/until1.0 [new file with mode: 0644]
sh/tests/set-e/until2.0 [new file with mode: 0644]
sh/tests/set-e/until3.0 [new file with mode: 0644]
sh/tests/set-e/while1.0 [new file with mode: 0644]
sh/tests/set-e/while2.0 [new file with mode: 0644]
sh/tests/set-e/while3.0 [new file with mode: 0644]
sh/trap.c [new file with mode: 0644]
sh/trap.h [new file with mode: 0644]
sh/var.c [new file with mode: 0644]
sh/var.h [new file with mode: 0644]
shell_cmds.xcodeproj/project.pbxproj
shell_cmds.xcodeproj/project.xcworkspace/contents.xcworkspacedata [new file with mode: 0644]
test/TEST.csh [deleted file]
test/[.1
test/test.1
test/test.c
test/test.plist.part [new file with mode: 0644]
test/tests/Makefile [new file with mode: 0644]
test/tests/legacy_test.sh [new file with mode: 0644]
xcconfigs/base.xcconfig [new file with mode: 0644]
xcconfigs/sh.xcconfig [new file with mode: 0644]
xcodescripts/builtins-manpages.txt

index d5f2d65f248467e654ebc6c17a85722ce0d393d2..0cabb01f1755d59358515b1ec3b638f2da01c997 100644 (file)
@@ -30,9 +30,9 @@
 .\" SUCH DAMAGE.
 .\"
 .\"     @(#)date.1     8.3 (Berkeley) 4/28/95
-.\" $FreeBSD: src/bin/date/date.1,v 1.72 2005/02/13 22:25:09 ru Exp $
+.\" $FreeBSD$
 .\"
-.Dd August 16, 2007
+.Dd May 7, 2015
 .Dt DATE 1
 .Os
 .Sh NAME
@@ -40,8 +40,8 @@
 .Nd display or set date and time
 .Sh SYNOPSIS
 .Nm
-.Op Fl ju
-.Op Fl r Ar seconds
+.Op Fl jRu
+.Op Fl r Ar seconds | Ar filename
 .Oo
 .Fl v
 .Sm off
@@ -58,7 +58,7 @@
 .Ar MM Oo Oo Ar cc Oc Ar yy Oc Op Ar .ss
 .Sm on
 .Nm
-.Op Fl jnu
+.Op Fl jnRu
 .Fl f Ar input_fmt new_date
 .Op Cm + Ns Ar output_fmt
 .Nm
@@ -80,7 +80,7 @@ both the kernel clock and the hardware clock are updated.
 .Pp
 Only the superuser may set the date,
 and if the system securelevel (see
-.Xr securelevel 8 )
+.Xr securelevel 7 )
 is greater than 1,
 the time may not be changed by more than 1 second.
 .Pp
@@ -132,6 +132,16 @@ The
 .Fl n
 option suppresses this behavior and causes the time to be set only on the
 current machine.
+.It Fl R
+Use RFC 2822 date and time output format. This is equivalent to use
+.Dq Li %a, %d %b %Y \&%T %z
+as
+.Ar output_fmt
+while
+.Ev LC_TIME
+is set to the
+.Dq C
+locale .
 .It Fl r Ar seconds
 Print the date and time represented by
 .Ar seconds ,
@@ -142,6 +152,9 @@ is the number of seconds since the Epoch
 see
 .Xr time 3 ) ,
 and can be specified in decimal, octal, or hex.
+.It Fl r Ar filename
+Print the date and time of the last modification of
+.Ar filename .
 .It Fl t Ar minutes_west
 Set the system's value for minutes west of
 .Tn GMT .
@@ -221,6 +234,14 @@ When the date is adjusted to a specific value that occurs twice
 the resulting timezone will be set so that the date matches the earlier of
 the two times.
 .Pp
+It is not possible to adjust a date to an invalid absolute day, so using
+the switches
+.Fl v No 31d Fl v No 12m
+will simply fail five months of the year.
+It is therefore usual to set the month before setting the day; using
+.Fl v No 12m Fl v No 31d
+always works.
+.Pp
 Adjusting the date by months is inherently ambiguous because
 a month is a unit of variable length depending on the current date.
 This kind of date adjustment is applied in the most intuitive way.
@@ -339,9 +360,9 @@ will display the last day of February in the year 2000:
 .Pp
 .Dl "Tue Feb 29 03:18:00 GMT 2000"
 .Pp
-So will do the command:
+So will the command:
 .Pp
-.Dl "date -v30d -v3m -v0y -v-1m"
+.Dl "date -v3m -v30d -v0y -v-1m"
 .Pp
 because there is no such date as the 30th of February.
 .Pp
@@ -423,10 +444,11 @@ Able to set the local date, but unable to set it globally
 For more information about legacy mode, see
 .Xr compat 5 .
 .Sh SEE ALSO
+.Xr locale 1 ,
 .Xr gettimeofday 2 ,
+.Xr getutxent 3 ,
 .Xr strftime 3 ,
 .Xr strptime 3 ,
-.Xr utmpx 5 ,
 .Xr timed 8
 .Rs
 .%T "TSP: The Time Synchronization Protocol for UNIX 4.3BSD"
@@ -438,6 +460,11 @@ The
 .Nm
 utility is expected to be compatible with
 .St -p1003.2 .
+The
+.Fl d , f , j , n , r , t ,
+and
+.Fl v
+options are all extensions to the standard.
 .Sh HISTORY
 A
 .Nm
index 23c3b8c6d069efb65b8dcf289110d1d8caaed98a..a45e110062b6ddeb74362cdc81a9c768fda65dcc 100644 (file)
@@ -40,17 +40,15 @@ static char sccsid[] = "@(#)date.c  8.2 (Berkeley) 4/28/95";
 #endif
 
 #include <sys/cdefs.h>
-__FBSDID("$FreeBSD: src/bin/date/date.c,v 1.47 2005/01/10 08:39:21 imp Exp $");
+__FBSDID("$FreeBSD$");
 
 #include <sys/param.h>
 #include <sys/time.h>
+#include <sys/stat.h>
 
 #include <ctype.h>
 #include <err.h>
 #include <locale.h>
-#ifndef __APPLE__
-#include <libutil.h>
-#endif /* !__APPLE__ */
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -60,7 +58,6 @@ __FBSDID("$FreeBSD: src/bin/date/date.c,v 1.47 2005/01/10 08:39:21 imp Exp $");
 
 #ifdef __APPLE__
 #include <get_compat.h>
-#include <util.h>
 #else
 #define COMPAT_MODE(a,b) (1)
 #endif /* __APPLE__ */
@@ -72,20 +69,26 @@ __FBSDID("$FreeBSD: src/bin/date/date.c,v 1.47 2005/01/10 08:39:21 imp Exp $");
 #define        TM_YEAR_BASE    1900
 #endif
 
+#ifdef __APPLE__
+#define st_mtim st_mtimespec
+#endif
+
 static time_t tval;
 int retval;
-int unix2003_std = 0;  /* to determine legacy vs std mode */
+static int unix2003_std;       /* to determine legacy vs std mode */
 
 static void setthetime(const char *, const char *, int, int);
 static void badformat(void);
 static void usage(void);
 
+static const char *rfc2822_format = "%a, %d %b %Y %T %z";
+
 int
 main(int argc, char *argv[])
 {
        struct timezone tz;
        int ch, rflag;
-       int jflag, nflag;
+       int jflag, nflag, Rflag;
        const char *format;
        char buf[1024];
        char *endptr, *fmt;
@@ -94,6 +97,7 @@ main(int argc, char *argv[])
        struct vary *v;
        const struct vary *badv;
        struct tm lt;
+       struct stat sb;
 
        unix2003_std = COMPAT_MODE("bin/date", "unix2003");     /* Determine the STD */
 
@@ -102,9 +106,9 @@ main(int argc, char *argv[])
        (void) setlocale(LC_TIME, "");
        tz.tz_dsttime = tz.tz_minuteswest = 0;
        rflag = 0;
-       jflag = nflag = 0;
+       jflag = nflag = Rflag = 0;
        set_timezone = 0;
-       while ((ch = getopt(argc, argv, "d:f:jnr:t:uv:")) != -1)
+       while ((ch = getopt(argc, argv, "d:f:jnRr:t:uv:")) != -1)
                switch((char)ch) {
                case 'd':               /* daylight savings time */
                        tz.tz_dsttime = strtol(optarg, &endptr, 10) ? 1 : 0;
@@ -121,11 +125,18 @@ main(int argc, char *argv[])
                case 'n':               /* don't set network */
                        nflag = 1;
                        break;
+               case 'R':               /* RFC 2822 datetime format */
+                       Rflag = 1;
+                       break;
                case 'r':               /* user specified seconds */
                        rflag = 1;
                        tval = strtoq(optarg, &tmp, 0);
-                       if (*tmp != 0)
-                               usage();
+                       if (*tmp != 0) {
+                               if (stat(optarg, &sb) == 0)
+                                       tval = sb.st_mtim.tv_sec;
+                               else
+                                       usage();
+                       }
                        break;
                case 't':               /* minutes west of UTC */
                                        /* error check; don't allow "PST" */
@@ -150,7 +161,7 @@ main(int argc, char *argv[])
         * If -d or -t, set the timezone or daylight savings time; this
         * doesn't belong here; the kernel should not know about either.
         */
-       if (set_timezone && settimeofday((struct timeval *)NULL, &tz))
+       if (set_timezone && settimeofday(NULL, &tz) != 0)
                err(1, "settimeofday (timezone)");
 
        if (!rflag && time(&tval) == -1)
@@ -158,6 +169,9 @@ main(int argc, char *argv[])
 
        format = "%+";
 
+       if (Rflag)
+               format = rfc2822_format;
+
        /* allow the operands in any order */
        if (*argv && **argv == '+') {
                format = *argv + 1;
@@ -191,6 +205,14 @@ main(int argc, char *argv[])
                usage();
        }
        vary_destroy(v);
+
+       if (format == rfc2822_format)
+               /*
+                * When using RFC 2822 datetime format, don't honor the
+                * locale.
+                */
+               setlocale(LC_TIME, "C");
+
        (void)strftime(buf, sizeof(buf), format, &lt);
        (void)printf("%s\n", buf);
        if (fflush(stdout))
@@ -211,13 +233,17 @@ main(int argc, char *argv[])
 static void
 setthetime(const char *fmt, const char *p, int jflag, int nflag)
 {
+       struct utmpx utx;
        struct tm *lt;
+       struct timeval tv;
        const char *dot, *t;
        int century;
        size_t length;
 
+       lt = localtime(&tval);
+       lt->tm_isdst = -1;              /* divine correct DST */
+
        if (fmt != NULL) {
-               lt = localtime(&tval);
                t = strptime(p, fmt, lt);
                if (t == NULL) {
                        fprintf(stderr, "Failed conversion of ``%s''"
@@ -238,8 +264,6 @@ setthetime(const char *fmt, const char *p, int jflag, int nflag)
                        badformat();
                }
 
-               lt = localtime(&tval);
-
                if (dot != NULL) {                      /* .ss */
                        dot++; /* *dot++ = '\0'; */
                        if (strlen(dot) != 2)
@@ -294,9 +318,6 @@ setthetime(const char *fmt, const char *p, int jflag, int nflag)
                }
        }
 
-       /* Let mktime() decide whether summer time is in effect. */
-       lt->tm_isdst = -1;
-
        /* convert broken-down time to GMT clock time */
        if ((tval = mktime(lt)) == -1)
                errx(1, "nonexistent time");
@@ -304,17 +325,16 @@ setthetime(const char *fmt, const char *p, int jflag, int nflag)
        if (!jflag) {
                /* set the time */
                if (nflag || netsettime(tval)) {
-                       struct utmpx ut;
-                       bzero(&ut, sizeof(ut));
-                       ut.ut_type = OLD_TIME;
-                       (void)gettimeofday(&ut.ut_tv, NULL);
-                       pututxline(&ut);
-                       ut.ut_tv.tv_sec = tval;
-                       ut.ut_tv.tv_usec = 0;
-                       if (settimeofday(&ut.ut_tv, NULL))
+                       utx.ut_type = OLD_TIME;
+                       (void)gettimeofday(&utx.ut_tv, NULL);
+                       pututxline(&utx);
+                       tv.tv_sec = tval;
+                       tv.tv_usec = 0;
+                       if (settimeofday(&tv, NULL) != 0)
                                err(1, "settimeofday (timeval)");
-                       ut.ut_type = NEW_TIME;
-                       pututxline(&ut);
+                       utx.ut_type = NEW_TIME;
+                       (void)gettimeofday(&utx.ut_tv, NULL);
+                       pututxline(&utx);
                }
 
                if ((p = getlogin()) == NULL)
@@ -334,9 +354,10 @@ static void
 usage(void)
 {
        (void)fprintf(stderr, "%s\n%s\n",
-           "usage: date [-jnu] [-d dst] [-r seconds] [-t west] "
+           "usage: date [-jnRu] [-d dst] [-r seconds] [-t west] "
            "[-v[+|-]val[ymwdHMS]] ... ",
-               unix2003_std ? "            "
+           unix2003_std ?
+           "            "
            "[-f fmt date | [[[mm]dd]HH]MM[[cc]yy][.ss]] [+format]" :
            "            "
            "[-f fmt date | [[[[[cc]yy]mm]dd]HH]MM[.ss]] [+format]");
diff --git a/date/date.plist.part b/date/date.plist.part
new file mode 100644 (file)
index 0000000..da97d84
--- /dev/null
@@ -0,0 +1,22 @@
+       <dict>
+               <key>OpenSourceProject</key>
+               <string>date</string>
+               <key>OpenSourceVersion</key>
+               <string>2015-05-07</string>
+               <key>OpenSourceWebsiteURL</key>
+               <string>http://svnweb.freebsd.org/base/head/bin/date/</string>
+               <key>OpenSourceSCM</key>
+               <string>svn co http://svn.freebsd.org/base/head/bin/date/</string>
+               <key>OpenSourceImportDate</key>
+               <string>2015-11-06</string>
+               <key>OpenSourceModifications</key>
+               <array>
+                       <string>date.1: discuss conformance changes</string>
+                       <string>date.1: remove bogus utmpx path</string>
+                   <string>date.c: conformance changes for date format and exit value</string>
+                   <string>date.c: fix crash on invalid dates (7999711)</string>
+                       <string>date.c: st_mtim compatibility</string>
+               </array>
+               <key>OpenSourceLicense</key>
+               <string>bsd</string>
+       </dict>
index fe2baee4effc88f88b83d65f0d0fc51abb3b75e3..91aeab2e07e16925c289e6bba0f31f1f2a4d59cd 100644 (file)
@@ -27,7 +27,9 @@
  * SUCH DAMAGE.
  *
  *     @(#)extern.h    8.1 (Berkeley) 5/31/93
- * $FreeBSD: src/bin/date/extern.h,v 1.7 2004/04/06 20:06:45 markm Exp $
+ * $FreeBSD$
  */
 
+extern int retval;
+
 int    netsettime(time_t);
index 4f7852a492ee9a9123f8f90d1170122b72c8bba5..e506e6d0ec1890c72dc808f6e6ae8551d31da4e8 100644 (file)
@@ -34,7 +34,7 @@ static char sccsid[] = "@(#)netdate.c 8.1 (Berkeley) 5/31/93";
 #endif
 
 #include <sys/cdefs.h>
-__FBSDID("$FreeBSD: src/bin/date/netdate.c,v 1.18 2004/04/06 20:06:45 markm Exp $");
+__FBSDID("$FreeBSD$");
 
 #include <sys/param.h>
 #include <sys/time.h>
@@ -55,8 +55,6 @@ __FBSDID("$FreeBSD: src/bin/date/netdate.c,v 1.18 2004/04/06 20:06:45 markm Exp
 #define        WAITACK         2       /* seconds */
 #define        WAITDATEACK     5       /* seconds */
 
-extern int retval;
-
 /*
  * Set the date in the machines controlled by timedaemons by communicating the
  * new date to the local timedaemon.  If the timedaemon is in the master state,
@@ -87,7 +85,7 @@ netsettime(time_t tval)
        dest.sin_addr.s_addr = htonl((u_long)INADDR_ANY);
        s = socket(AF_INET, SOCK_DGRAM, 0);
        if (s < 0) {
-               if (errno != EPROTONOSUPPORT)
+               if (errno != EAFNOSUPPORT)
                        warn("timed");
                return (retval = 2);
        }
@@ -108,14 +106,14 @@ netsettime(time_t tval)
                warnx("all ports in use");
                goto bad;
        }
+       memset(&msg, 0, sizeof(msg));
        msg.tsp_type = TSP_SETDATE;
        msg.tsp_vers = TSPVERSION;
        if (gethostname(hostname, sizeof(hostname))) {
                warn("gethostname");
                goto bad;
        }
-       (void)strncpy(msg.tsp_name, hostname, sizeof(msg.tsp_name) - 1);
-       msg.tsp_name[sizeof(msg.tsp_name) - 1] = '\0';
+       (void)strlcpy(msg.tsp_name, hostname, sizeof(msg.tsp_name));
        msg.tsp_seq = htons((u_short)0);
        msg.tsp_time.tv_sec = htonl((u_long)tval);
        msg.tsp_time.tv_usec = htonl((u_long)0);
index 82a6cea2ad2dc91d46a1e0b1a01b0315983f3079..5f0123110ee32db325e0481740c8e5f3a4b6296c 100644 (file)
@@ -25,7 +25,7 @@
  */
 
 #include <sys/cdefs.h>
-__FBSDID("$FreeBSD: src/bin/date/vary.c,v 1.16 2004/08/09 13:43:39 yar Exp $");
+__FBSDID("$FreeBSD$");
 
 #include <err.h>
 #include <time.h>
index 27eddf7e74fe005e53d5fe663063839092ae9523..b39306a2bb2332fdf5dc20491994b6046820482e 100644 (file)
@@ -23,7 +23,7 @@
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
- * $FreeBSD: src/bin/date/vary.h,v 1.4 1999/08/27 23:14:00 peter Exp $
+ * $FreeBSD$
  */
 
 struct vary {
index 8e8891db7981c1845585b2eca153fc407691d4ce..07699e66179d4990670027e592e5ab90f43ae34a 100644 (file)
@@ -1,3 +1,4 @@
+.\"-
 .\" Copyright (c) 1980, 1990, 1993
 .\"    The Regents of the University of California.  All rights reserved.
 .\"
 .\" 2. Redistributions in binary form must reproduce the above copyright
 .\"    notice, this list of conditions and the following disclaimer in the
 .\"    documentation and/or other materials provided with the distribution.
-.\" 3. All advertising materials mentioning features or use of this software
-.\"    must display the following acknowledgement:
-.\"    This product includes software developed by the University of
-.\"    California, Berkeley and its contributors.
 .\" 4. Neither the name of the University nor the names of its contributors
 .\"    may be used to endorse or promote products derived from this software
 .\"    without specific prior written permission.
@@ -33,7 +30,7 @@
 .\" SUCH DAMAGE.
 .\"
 .\"    @(#)kill.1      8.2 (Berkeley) 4/28/95
-.\" $FreeBSD: src/bin/kill/kill.1,v 1.11.2.1 2000/12/08 13:34:35 ru Exp $
+.\" $FreeBSD$
 .\"
 .Dd April 28, 1995
 .Dt KILL 1
 .Sh SYNOPSIS
 .Nm
 .Op Fl s Ar signal_name
-.Ar pid
-\&...
+.Ar pid ...
 .Nm
 .Fl l
 .Op Ar exit_status
 .Nm
-.Fl signal_name
-.Ar pid
-\&...
+.Fl Ar signal_name
+.Ar pid ...
 .Nm
-.Fl signal_number
-.Ar pid
-\&...
+.Fl Ar signal_number
+.Ar pid ...
 .Sh DESCRIPTION
 The
 .Nm
-utility sends a signal to the processes specified by the pid operand(s).
+utility sends a signal to the processes specified by the
+.Ar pid
+operands.
 .Pp
 Only the super-user may send signals to other users' processes.
 .Pp
 The options are as follows:
-.Pp
-.Bl -tag -width Ds
+.Bl -tag -width indent
 .It Fl s Ar signal_name
 A symbolic signal name specifying the signal to be sent instead of the
 default
@@ -75,25 +70,26 @@ default
 If no operand is given, list the signal names; otherwise, write
 the signal name corresponding to
 .Ar exit_status .
-.It Fl signal_name
+.It Fl Ar signal_name
 A symbolic signal name specifying the signal to be sent instead of the
 default
 .Dv TERM .
-.It Fl signal_number
+.It Fl Ar signal_number
 A non-negative decimal integer, specifying the signal to be sent instead
 of the default
 .Dv TERM .
 .El
 .Pp
-The following pids have special meanings:
-.Bl -tag -width Ds -compact
+The following PIDs have special meanings:
+.Bl -tag -width indent
 .It -1
 If superuser, broadcast the signal to all processes; otherwise broadcast
 to all processes belonging to the user.
 .El
 .Pp
 Some of the more commonly used signals:
-.Bl -tag -width Ds -compact
+.Pp
+.Bl -tag -width indent -compact
 .It 1
 HUP (hang up)
 .It 2
@@ -116,27 +112,41 @@ command which is similar or identical to this utility.
 Consult the
 .Xr builtin 1
 manual page.
+.Sh EXIT STATUS
+.Ex -std
+.Sh EXAMPLES
+Terminate
+the processes with PIDs 142 and 157:
+.Pp
+.Dl "kill 142 157"
+.Pp
+Send the hangup signal
+.Pq Dv SIGHUP
+to the process with PID 507:
+.Pp
+.Dl "kill -s HUP 507"
 .Sh SEE ALSO
 .Xr builtin 1 ,
 .Xr csh 1 ,
 .Xr killall 1 ,
 .Xr ps 1 ,
+.Xr sh 1 ,
 .Xr kill 2 ,
 .Xr sigaction 2
 .Sh STANDARDS
 The
 .Nm
-function is expected to be
+utility is expected to be
 .St -p1003.2
 compatible.
 .Sh HISTORY
 A
 .Nm
 command appeared in
-.At v6 .
+.At v3 .
 .Sh BUGS
 A replacement for the command
 .Dq Li kill 0
 for
-.Xr csh  1
+.Xr csh 1
 users should be provided.
index f2c56b7af37e928f1b20cd854c990530575d627b..258f691dbc9525bb292797d2ceb29905056c9c95 100644 (file)
@@ -1,4 +1,4 @@
-/*
+/*-
  * Copyright (c) 1988, 1993, 1994
  *     The Regents of the University of California.  All rights reserved.
  *
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- *    must display the following acknowledgement:
- *     This product includes software developed by the University of
- *     California, Berkeley and its contributors.
  * 4. Neither the name of the University nor the names of its contributors
  *    may be used to endorse or promote products derived from this software
  *    without specific prior written permission.
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+/*
+ * Important: This file is used both as a standalone program /bin/kill and
+ * as a builtin for /bin/sh (#define SHELL).
+ */
 
+#if 0
 #ifndef lint
 static char const copyright[] =
 "@(#) Copyright (c) 1988, 1993, 1994\n\
@@ -38,12 +39,11 @@ static char const copyright[] =
 #endif /* not lint */
 
 #ifndef lint
-#if 0
 static char sccsid[] = "@(#)kill.c     8.4 (Berkeley) 4/28/95";
-#endif
-static const char rcsid[] =
-  "$FreeBSD: src/bin/kill/kill.c,v 1.11.2.1 2001/08/01 02:42:56 obrien Exp $";
 #endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
 
 #include <ctype.h>
 #include <err.h>
@@ -53,18 +53,24 @@ static const char rcsid[] =
 #include <stdlib.h>
 #include <string.h>
 
-int main __P((int, char *[]));
-void nosig __P((char *));
-void printsignals __P((FILE *));
-int signame_to_signum __P((char *));
-void usage __P((void));
+#ifdef SHELL
+#define main killcmd
+#include "bltin/bltin.h"
+#endif
+
+static void nosig(const char *);
+static void printsignals(FILE *);
+static int signame_to_signum(const char *);
+static void usage(void);
+
+#ifdef __APPLE__
+#define sys_nsig NSIG
+#endif /* __APPLE__ */
 
 int
-main(argc, argv)
-       int argc;
-       char *argv[];
+main(int argc, char *argv[])
 {
-       int errors, numsig, pid;
+       int errors, numsig, pid, ret;
        char *ep;
 
        if (argc < 2)
@@ -82,16 +88,16 @@ main(argc, argv)
                                usage();
                        numsig = strtol(*argv, &ep, 10);
                        if (!**argv || *ep)
-                               errx(1, "illegal signal number: %s", *argv);
+                               errx(2, "illegal signal number: %s", *argv);
                        if (numsig >= 128)
                                numsig -= 128;
-                       if (numsig <= 0 || numsig >= NSIG)
+                       if (numsig <= 0 || numsig >= sys_nsig)
                                nosig(*argv);
                        printf("%s\n", sys_signame[numsig]);
-                       exit(0);
+                       return (0);
                }
                printsignals(stdout);
-               exit(0);
+               return (0);
        }
 
        if (!strcmp(*argv, "-s")) {
@@ -106,7 +112,7 @@ main(argc, argv)
                } else
                        numsig = 0;
                argc--, argv++;
-       } else if (**argv == '-') {
+       } else if (**argv == '-' && *(*argv + 1) != '-') {
                ++*argv;
                if (isalpha(**argv)) {
                        if ((numsig = signame_to_signum(*argv)) < 0)
@@ -114,73 +120,84 @@ main(argc, argv)
                } else if (isdigit(**argv)) {
                        numsig = strtol(*argv, &ep, 10);
                        if (!**argv || *ep)
-                               errx(1, "illegal signal number: %s", *argv);
-                       if (numsig < 0 || numsig >= NSIG)
+                               errx(2, "illegal signal number: %s", *argv);
+                       if (numsig < 0)
                                nosig(*argv);
                } else
                        nosig(*argv);
                argc--, argv++;
        }
 
+       if (argc > 0 && strncmp(*argv, "--", 2) == 0)
+               argc--, argv++;
+
        if (argc == 0)
                usage();
 
        for (errors = 0; argc; argc--, argv++) {
-               pid = strtol(*argv, &ep, 10);
-               if (!**argv || *ep) {
-                       warnx("illegal process id: %s", *argv);
-                       errors = 1;
-               } else if (kill(pid, numsig) == -1) {
+#ifdef SHELL
+               if (**argv == '%')
+                       ret = killjob(*argv, numsig);
+               else
+#endif
+               {
+                       pid = strtol(*argv, &ep, 10);
+                       if (!**argv || *ep)
+                               errx(2, "illegal process id: %s", *argv);
+                       ret = kill(pid, numsig);
+               }
+               if (ret == -1) {
                        warn("%s", *argv);
                        errors = 1;
                }
        }
 
-       exit(errors);
+       return (errors);
 }
 
-int
-signame_to_signum(sig)
-       char *sig;
+static int
+signame_to_signum(const char *sig)
 {
        int n;
 
-       if (!strncasecmp(sig, "sig", (size_t)3))
+       if (strncasecmp(sig, "SIG", 3) == 0)
                sig += 3;
-       for (n = 1; n < NSIG; n++) {
+       for (n = 1; n < sys_nsig; n++) {
                if (!strcasecmp(sys_signame[n], sig))
                        return (n);
        }
        return (-1);
 }
 
-void
-nosig(name)
-       char *name;
+static void
+nosig(const char *name)
 {
 
        warnx("unknown signal %s; valid signals:", name);
        printsignals(stderr);
-       exit(1);
+#ifdef SHELL
+       error(NULL);
+#else
+       exit(2);
+#endif
 }
 
-void
-printsignals(fp)
-       FILE *fp;
+static void
+printsignals(FILE *fp)
 {
        int n;
 
-       for (n = 1; n < NSIG; n++) {
+       for (n = 1; n < sys_nsig; n++) {
                (void)fprintf(fp, "%s", sys_signame[n]);
-               if (n == (NSIG / 2) || n == (NSIG - 1))
+               if (n == (sys_nsig / 2) || n == (sys_nsig - 1))
                        (void)fprintf(fp, "\n");
                else
                        (void)fprintf(fp, " ");
        }
 }
 
-void
-usage()
+static void
+usage(void)
 {
 
        (void)fprintf(stderr, "%s\n%s\n%s\n%s\n",
@@ -188,5 +205,9 @@ usage()
                "       kill -l [exit_status]",
                "       kill -signal_name pid ...",
                "       kill -signal_number pid ...");
-       exit(1);
+#ifdef SHELL
+       error(NULL);
+#else
+       exit(2);
+#endif
 }
diff --git a/kill/kill.plist.part b/kill/kill.plist.part
new file mode 100644 (file)
index 0000000..f7fcb9e
--- /dev/null
@@ -0,0 +1,18 @@
+       <dict>
+               <key>OpenSourceProject</key>
+               <string>kill</string>
+               <key>OpenSourceVersion</key>
+               <string>2015-03-01</string>
+               <key>OpenSourceWebsiteURL</key>
+               <string>http://svnweb.freebsd.org/base/head/bin/kill/</string>
+               <key>OpenSourceSCM</key>
+               <string>svn co http://svn.freebsd.org/base/head/bin/kill/</string>
+               <key>OpenSourceImportDate</key>
+               <string>2015-07-24</string>
+               <key>OpenSourceModifications</key>
+               <array>
+                   <string>kill.c: sys_nsig</string>
+               </array>
+               <key>OpenSourceLicense</key>
+               <string>bsd</string>
+       </dict>
index 4067e3182f3c9900d6c6643f17073fa7ab80480f..921ffa3c981ef93363b613ec8ef9629088e464b3 100644 (file)
@@ -94,11 +94,13 @@ option is given,
 will generate a template string based on the
 .Ar prefix
 and the
+.Dv _CS_DARWIN_USER_TEMP_DIR
+configuration variable if available.
+Fallback locations if
+.Dv _CS_DARWIN_USER_TEMP_DIR
+is not available are
 .Ev TMPDIR
-environment variable if set.
-The default location if
-.Ev TMPDIR
-is not set is
+and
 .Pa /tmp .
 Care should
 be taken to ensure that it is appropriate to use an environment variable
@@ -197,6 +199,7 @@ fi
 .Xr mkdtemp 3 ,
 .Xr mkstemp 3 ,
 .Xr mktemp 3 ,
+.Xr confstr 3 ,
 .Xr environ 7
 .Sh HISTORY
 A
index 3c927c23cd9a8fdce6105107f74881ea96f8f367..db10919917bd4bd37720ecda17002ed3805169f7 100644 (file)
@@ -14,6 +14,7 @@
                        <string>Avoid double-slash in output. (5334050)</string>
                        <string>Document case-insensitive file system behavior. (14280248)</string>
                        <string>Prefer _CS_DARWIN_USER_TEMP_DIR over TMPDIR and _PATH_TMP. (14338140)</string>
+                       <string>mktemp.1: Clarification of _CS_DARWIN_USER_TEMP_DIR/TMPDIR/_PATH_TMP behavior. (22954705)</string>
                </array>
                <key>OpenSourceLicense</key>
                <string>bsd</string>
index fcdbfb335cc59ed6799b4c997206b25bcd2062a2..7256b53df3abc241cd072c704ce73b1b98e893f8 100644 (file)
 .\" 2. Redistributions in binary form must reproduce the above copyright
 .\"    notice, this list of conditions and the following disclaimer in the
 .\"    documentation and/or other materials provided with the distribution.
-.\" 3. All advertising materials mentioning features or use of this software
-.\"    must display the following acknowledgement:
-.\"    This product includes software developed by the University of
-.\"    California, Berkeley and its contributors.
 .\" 4. Neither the name of the University nor the names of its contributors
 .\"    may be used to endorse or promote products derived from this software
 .\"    without specific prior written permission.
@@ -33,9 +29,9 @@
 .\" SUCH DAMAGE.
 .\"
 .\"    @(#)printf.1    8.1 (Berkeley) 6/6/93
-.\" $FreeBSD: src/usr.bin/printf/printf.1,v 1.34 2005/06/14 11:50:52 ru Exp $
+.\" $FreeBSD$
 .\"
-.Dd April 14, 2005
+.Dd April 21, 2014
 .Dt PRINTF 1
 .Os
 .Sh NAME
@@ -72,8 +68,7 @@ otherwise it is evaluated as a C constant, with the following extensions:
 A leading plus or minus sign is allowed.
 .It
 If the leading character is a single or double quote, the value is the
-.Tn ASCII
-code of the next character.
+character code of the next character.
 .El
 .Pp
 The format string is reused as often as necessary to satisfy the
@@ -109,12 +104,13 @@ Write a <single quote> character.
 .It Cm \e\e
 Write a backslash character.
 .It Cm \e Ns Ar num
-.It Cm \e0 Ns Ar num
-Write an 8-bit character whose
-.Tn ASCII
+Write a byte whose
 value is the 1-, 2-, or 3-digit
 octal number
 .Ar num .
+Multibyte characters can be constructed using multiple
+.Cm \e Ns Ar num
+sequences.
 .El
 .Pp
 Each format specification is introduced by the percent character
@@ -128,9 +124,9 @@ in the following order:
 A `#' character
 specifying that the value should be printed in an ``alternate form''.
 For
-.Cm c , d ,
+.Cm b , c , d , s
 and
-.Cm s ,
+.Cm u
 formats, this option has no effect.
 For the
 .Cm o
@@ -144,9 +140,9 @@ format, a non-zero result has the string
 .Pq Li 0X
 prepended to it.
 For
-.Cm e , E , f , g ,
+.Cm a , A , e , E , f , F , g
 and
-.Cm G ,
+.Cm G
 formats, the result will always contain a decimal point, even if no
 digits follow the point (normally, a decimal point only appears in the
 results of those formats if a digit follows the decimal point).
@@ -175,7 +171,7 @@ A `\-' overrides a `0' if both are used;
 .It "Field Width:"
 An optional digit string specifying a
 .Em field width ;
-if the output string has fewer characters than the field width it will
+if the output string has fewer bytes than the field width it will
 be blank-padded on the left (or right, if the left-adjustment indicator
 has been given) to make up the field width (note that a leading zero
 is a flag, but an embedded zero is part of a field width);
@@ -189,7 +185,7 @@ for
 .Cm e
 and
 .Cm f
-formats, or the maximum number of characters to be printed
+formats, or the maximum number of bytes to be printed
 from a string; if the digit string is missing, the precision is treated
 as zero;
 .It Format:
@@ -275,20 +271,28 @@ and
 .Ql nan ,
 respectively.
 .It Cm c
-The first character of
+The first byte of
 .Ar argument
 is printed.
 .It Cm s
-Characters from the string
+Bytes from the string
 .Ar argument
-are printed until the end is reached or until the number of characters
+are printed until the end is reached or until the number of bytes
 indicated by the precision specification is reached; however if the
-precision is 0 or missing, all characters in the string are printed.
+precision is 0 or missing, the string is printed entirely.
 .It Cm b
 As for
 .Cm s ,
 but interpret character escapes in backslash notation in the string
 .Ar argument .
+The permitted escape sequences are slightly different in that
+octal escapes are
+.Cm \e0 Ns Ar num
+instead of
+.Cm \e Ns Ar num .
+.It Cm n$
+Allows reordering of the output according to
+.Ar argument .
 .It Cm \&%
 Print a `%'; no argument is used.
 .El
@@ -300,6 +304,13 @@ character is defined in the program's locale (category
 In no case does a non-existent or small field width cause truncation of
 a field; padding takes place only if the specified field width exceeds
 the actual width.
+.Pp
+Some shells may provide a builtin
+.Nm
+command which is similar or identical to this utility.
+Consult the
+.Xr builtin 1
+manual page.
 .Sh EXIT STATUS
 .Ex -std
 .Sh COMPATIBILITY
@@ -310,12 +321,14 @@ with a digit to the
 .Tn ASCII
 code of the first character is not supported.
 .Sh SEE ALSO
+.Xr builtin 1 ,
 .Xr echo 1 ,
+.Xr sh 1 ,
 .Xr printf 3
 .Sh STANDARDS
 The
 .Nm
-command is expected to be mostly compatible with the
+command is expected to be compatible with the
 .St -p1003.2
 specification.
 .Sh HISTORY
@@ -326,6 +339,27 @@ command appeared in
 It is modeled
 after the standard library function,
 .Xr printf 3 .
+.Sh CAVEATS
+.Tn ANSI
+hexadecimal character constants were deliberately not provided.
+.Pp
+Trying to print a dash ("-") as the first character causes
+.Nm
+to interpret the dash as a program argument.
+.Nm --
+must be used before
+.Ar format .
+.Pp
+If the locale contains multibyte characters
+(such as UTF-8),
+the
+.Cm c
+format and
+.Cm b
+and
+.Cm s
+formats with a precision
+may not operate as expected.
 .Sh BUGS
 Since the floating point numbers are translated from
 .Tn ASCII
@@ -337,9 +371,6 @@ The
 .Cm L
 modifier may produce additional precision, depending on the hardware platform.)
 .Pp
-.Tn ANSI
-hexadecimal character constants were deliberately not provided.
-.Pp
 The escape sequence \e000 is the string terminator.
 When present in the argument for the
 .Cm b
@@ -349,8 +380,3 @@ Multibyte characters are not recognized in format strings (this is only
 a problem if
 .Ql %
 can appear inside a multibyte character).
-.Pp
-Parsing of - arguments is also somewhat different from
-.Xr printf 3 ,
-where unknown arguments are simply printed instead of being
-flagged as errors.
index 5c82411c08d9bb5a2c51514e4906a614723b67cb..8636cc13c1ef0c338244047eb862f2d4f6f1df2a 100644 (file)
@@ -1,4 +1,6 @@
-/*
+/*-
+ * Copyright 2014 Garrett D'Amore <garrett@damore.org>
+ * Copyright 2010 Nexenta Systems, Inc.  All rights reserved.
  * Copyright (c) 1989, 1993
  *     The Regents of the University of California.  All rights reserved.
  *
  * 2. Redistributions in binary form must reproduce the above copyright
  *    notice, this list of conditions and the following disclaimer in the
  *    documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- *    must display the following acknowledgement:
- *     This product includes software developed by the University of
- *     California, Berkeley and its contributors.
  * 4. Neither the name of the University nor the names of its contributors
  *    may be used to endorse or promote products derived from this software
  *    without specific prior written permission.
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  */
+/*
+ * Important: This file is used both as a standalone program /usr/bin/printf
+ * and as a builtin for /bin/sh (#define SHELL).
+ */
 
-#if !defined(BUILTIN) && !defined(SHELL)
+#ifndef SHELL
 #ifndef lint
 static char const copyright[] =
 "@(#) Copyright (c) 1989, 1993\n\
@@ -44,53 +46,48 @@ static char const copyright[] =
 static char const sccsid[] = "@(#)printf.c     8.1 (Berkeley) 7/20/93";
 #endif
 static const char rcsid[] =
-  "$FreeBSD: src/usr.bin/printf/printf.c,v 1.37 2005/08/05 08:18:00 stefanf Exp $";
+  "$FreeBSD$";
 #endif /* not lint */
 
 #include <sys/types.h>
 
+#include <ctype.h>
 #include <err.h>
 #include <errno.h>
 #include <inttypes.h>
 #include <limits.h>
+#include <locale.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <wchar.h>
 
 #ifdef SHELL
-#define main printfcmd
+#define        main printfcmd
 #include "bltin/bltin.h"
-#include "memalloc.h"
-#else
-#define        warnx1(a, b, c)         warnx(a)
-#define        warnx2(a, b, c)         warnx(a, b)
-#define        warnx3(a, b, c)         warnx(a, b, c)
+#include "options.h"
 #endif
 
-#ifndef BUILTIN
-#include <locale.h>
-#endif
-
-#define PF(f, func) do { \
-       char *b = NULL; \
-       if (havewidth) \
-               if (haveprec) \
+#define        PF(f, func) do {                                                \
+       char *b = NULL;                                                 \
+       if (havewidth)                                                  \
+               if (haveprec)                                           \
                        (void)asprintf(&b, f, fieldwidth, precision, func); \
-               else \
-                       (void)asprintf(&b, f, fieldwidth, func); \
-       else if (haveprec) \
-               (void)asprintf(&b, f, precision, func); \
-       else \
-               (void)asprintf(&b, f, func); \
-       if (b) { \
-               (void)fputs(b, stdout); \
-               free(b); \
-       } \
+               else                                                    \
+                       (void)asprintf(&b, f, fieldwidth, func);        \
+       else if (haveprec)                                              \
+               (void)asprintf(&b, f, precision, func);                 \
+       else                                                            \
+               (void)asprintf(&b, f, func);                            \
+       if (b) {                                                        \
+               (void)fputs(b, stdout);                                 \
+               free(b);                                                \
+       }                                                               \
 } while (0)
 
 static int      asciicode(void);
-static char    *doformat(char *, int *);
+static char    *printf_doformat(char *, int *);
 static int      escape(char *, int, size_t *);
 static int      getchr(void);
 static int      getfloating(long double *, int);
@@ -98,25 +95,35 @@ static int   getint(int *);
 static int      getnum(intmax_t *, uintmax_t *, int);
 static const char
                *getstr(void);
-static char    *mknum(char *, int);
+static char    *mknum(char *, char);
 static void     usage(void);
 
+static const char digits[] = "0123456789";
+
+static char end_fmt[1];
+
+static int  myargc;
+static char **myargv;
 static char **gargv;
+static char **maxargv;
 
 int
-#ifdef BUILTIN
-progprintf(int argc, char *argv[])
-#else
 main(int argc, char *argv[])
-#endif
 {
        size_t len;
-       int ch, chopped, end, rval;
+       int end, rval;
        char *format, *fmt, *start;
+#ifndef SHELL
+       int ch;
 
-#ifndef BUILTIN
-       (void) setlocale(LC_NUMERIC, "");
+       (void) setlocale(LC_ALL, "");
 #endif
+
+#ifdef SHELL
+       nextopt("");
+       argc -= argptr - argv;
+       argv = argptr;
+#else
        while ((ch = getopt(argc, argv, "")) != -1)
                switch (ch) {
                case '?':
@@ -126,12 +133,16 @@ main(int argc, char *argv[])
                }
        argc -= optind;
        argv += optind;
+#endif
 
        if (argc < 1) {
                usage();
                return (1);
        }
 
+#ifdef SHELL
+       INTOFF;
+#endif
        /*
         * Basic algorithm is to scan the format string for conversion
         * specifications -- once one is found, find out if the field
@@ -141,10 +152,16 @@ main(int argc, char *argv[])
         * up the format string.
         */
        fmt = format = *argv;
-       chopped = escape(fmt, 1, &len);         /* backslash interpretation */
+       escape(fmt, 1, &len);           /* backslash interpretation */
        rval = end = 0;
        gargv = ++argv;
+
        for (;;) {
+               maxargv = gargv;
+
+               myargv = gargv;
+               for (myargc = 0; gargv[myargc]; myargc++)
+                       /* nop */;
                start = fmt;
                while (fmt < format + len) {
                        if (fmt[0] == '%') {
@@ -154,23 +171,37 @@ main(int argc, char *argv[])
                                        putchar('%');
                                        fmt += 2;
                                } else {
-                                       fmt = doformat(fmt, &rval);
-                                       if (fmt == NULL)
-                                               return (1);
+                                       fmt = printf_doformat(fmt, &rval);
+                                       if (fmt == NULL || fmt == end_fmt) {
+#ifdef SHELL
+                                               INTON;
+#endif
+                                               return (fmt == NULL ? 1 : rval);
+                                       }
                                        end = 0;
                                }
                                start = fmt;
                        } else
                                fmt++;
+                       if (gargv > maxargv)
+                               maxargv = gargv;
                }
+               gargv = maxargv;
 
                if (end == 1) {
-                       warnx1("missing format character", NULL, NULL);
+                       warnx("missing format character");
+#ifdef SHELL
+                       INTON;
+#endif
                        return (1);
                }
                fwrite(start, 1, fmt - start, stdout);
-               if (chopped || !*gargv)
+               if (!*gargv) {
+#ifdef SHELL
+                       INTON;
+#endif
                        return (rval);
+               }
                /* Restart at the beginning of the format string. */
                fmt = format;
                end = 1;
@@ -180,48 +211,136 @@ main(int argc, char *argv[])
 
 
 static char *
-doformat(char *start, int *rval)
+printf_doformat(char *fmt, int *rval)
 {
        static const char skip1[] = "#'-+ 0";
-       static const char skip2[] = "0123456789";
-       char *fmt;
        int fieldwidth, haveprec, havewidth, mod_ldbl, precision;
        char convch, nextch;
+       char start[strlen(fmt) + 1];
+       char **fargv;
+       char *dptr;
+       int l;
+
+       dptr = start;
+       *dptr++ = '%';
+       *dptr = 0;
+
+       fmt++;
+
+       /* look for "n$" field index specifier */
+       l = strspn(fmt, digits);
+       if ((l > 0) && (fmt[l] == '$')) {
+               int idx = atoi(fmt);
+               if (idx <= myargc) {
+                       gargv = &myargv[idx - 1];
+               } else {
+                       gargv = &myargv[myargc];
+               }
+               if (gargv > maxargv)
+                       maxargv = gargv;
+               fmt += l + 1;
+
+               /* save format argument */
+               fargv = gargv;
+       } else {
+               fargv = NULL;
+       }
 
-       fmt = start + 1;
        /* skip to field width */
-       fmt += strspn(fmt, skip1);
+       while (*fmt && strchr(skip1, *fmt) != NULL) {
+               *dptr++ = *fmt++;
+               *dptr = 0;
+       }
+
        if (*fmt == '*') {
+
+               fmt++;
+               l = strspn(fmt, digits);
+               if ((l > 0) && (fmt[l] == '$')) {
+                       int idx = atoi(fmt);
+                       if (fargv == NULL) {
+                               warnx("incomplete use of n$");
+                               return (NULL);
+                       }
+                       if (idx <= myargc) {
+                               gargv = &myargv[idx - 1];
+                       } else {
+                               gargv = &myargv[myargc];
+                       }
+                       fmt += l + 1;
+               } else if (fargv != NULL) {
+                       warnx("incomplete use of n$");
+                       return (NULL);
+               }
+
                if (getint(&fieldwidth))
                        return (NULL);
+               if (gargv > maxargv)
+                       maxargv = gargv;
                havewidth = 1;
-               ++fmt;
+
+               *dptr++ = '*';
+               *dptr = 0;
        } else {
                havewidth = 0;
 
                /* skip to possible '.', get following precision */
-               fmt += strspn(fmt, skip2);
+               while (isdigit(*fmt)) {
+                       *dptr++ = *fmt++;
+                       *dptr = 0;
+               }
        }
+
        if (*fmt == '.') {
                /* precision present? */
-               ++fmt;
+               fmt++;
+               *dptr++ = '.';
+
                if (*fmt == '*') {
+
+                       fmt++;
+                       l = strspn(fmt, digits);
+                       if ((l > 0) && (fmt[l] == '$')) {
+                               int idx = atoi(fmt);
+                               if (fargv == NULL) {
+                                       warnx("incomplete use of n$");
+                                       return (NULL);
+                               }
+                               if (idx <= myargc) {
+                                       gargv = &myargv[idx - 1];
+                               } else {
+                                       gargv = &myargv[myargc];
+                               }
+                               fmt += l + 1;
+                       } else if (fargv != NULL) {
+                               warnx("incomplete use of n$");
+                               return (NULL);
+                       }
+
                        if (getint(&precision))
                                return (NULL);
+                       if (gargv > maxargv)
+                               maxargv = gargv;
                        haveprec = 1;
-                       ++fmt;
+                       *dptr++ = '*';
+                       *dptr = 0;
                } else {
                        haveprec = 0;
 
                        /* skip to conversion char */
-                       fmt += strspn(fmt, skip2);
+                       while (isdigit(*fmt)) {
+                               *dptr++ = *fmt++;
+                               *dptr = 0;
+                       }
                }
        } else
                haveprec = 0;
        if (!*fmt) {
-               warnx1("missing format character", NULL, NULL);
+               warnx("missing format character");
                return (NULL);
        }
+       *dptr++ = *fmt;
+       *dptr = 0;
 
        /*
         * Look for a length modifier.  POSIX doesn't have these, so
@@ -237,15 +356,21 @@ doformat(char *start, int *rval)
                mod_ldbl = 1;
                fmt++;
                if (!strchr("aAeEfFgG", *fmt)) {
-                       warnx2("bad modifier L for %%%c", *fmt, NULL);
+                       warnx("bad modifier L for %%%c", *fmt);
                        return (NULL);
                }
        } else {
                mod_ldbl = 0;
        }
 
+       /* save the current arg offset, and set to the format arg */
+       if (fargv != NULL) {
+               gargv = fargv;
+       }
+
        convch = *fmt;
        nextch = *++fmt;
+
        *fmt = '\0';
        switch (convch) {
        case 'b': {
@@ -253,26 +378,16 @@ doformat(char *start, int *rval)
                char *p;
                int getout;
 
-#ifdef SHELL
-               p = savestr(getstr());
-#else
                p = strdup(getstr());
-#endif
                if (p == NULL) {
-                       warnx2("%s", strerror(ENOMEM), NULL);
+                       warnx("%s", strerror(ENOMEM));
                        return (NULL);
                }
                getout = escape(p, 0, &len);
-               *(fmt - 1) = 's';
-               PF(start, p);
-               *(fmt - 1) = 'b';
-#ifdef SHELL
-               ckfree(p);
-#else
+               fputs(p, stdout);
                free(p);
-#endif
                if (getout)
-                       return (fmt);
+                       return (end_fmt);
                break;
        }
        case 'c': {
@@ -321,15 +436,16 @@ doformat(char *start, int *rval)
                break;
        }
        default:
-               warnx2("illegal format character %c", convch, NULL);
+               warnx("illegal format character %c", convch);
                return (NULL);
        }
        *fmt = nextch;
+       /* return the gargv to the next element */
        return (fmt);
 }
 
 static char *
-mknum(char *str, int ch)
+mknum(char *str, char ch)
 {
        static char *copy;
        static size_t copy_size;
@@ -339,13 +455,8 @@ mknum(char *str, int ch)
        len = strlen(str) + 2;
        if (len > copy_size) {
                newlen = ((len + 1023) >> 10) << 10;
-#ifdef SHELL
-               if ((newcopy = ckrealloc(copy, newlen)) == NULL)
-#else
-               if ((newcopy = realloc(copy, newlen)) == NULL)
-#endif
-               {
-                       warnx2("%s", strerror(ENOMEM), NULL);
+               if ((newcopy = realloc(copy, newlen)) == NULL) {
+                       warnx("%s", strerror(ENOMEM));
                        return (NULL);
                }
                copy = newcopy;
@@ -362,10 +473,10 @@ mknum(char *str, int ch)
 static int
 escape(char *fmt, int percent, size_t *len)
 {
-       char *save, *store;
-       int value, c;
+       char *save, *store, c;
+       int value;
 
-       for (save = store = fmt; (c = *fmt); ++fmt, ++store) {
+       for (save = store = fmt; ((c = *fmt) != 0); ++fmt, ++store) {
                if (c != '\\') {
                        *store = c;
                        continue;
@@ -387,9 +498,13 @@ escape(char *fmt, int percent, size_t *len)
                        *store = '\b';
                        break;
                case 'c':
-                       *store = '\0';
-                       *len = store - save;
-                       return (1);
+                       if (!percent) {
+                               *store = '\0';
+                               *len = store - save;
+                               return (1);
+                       }
+                       *store = 'c';
+                       break;
                case 'f':               /* form-feed */
                        *store = '\f';
                        break;
@@ -408,7 +523,8 @@ escape(char *fmt, int percent, size_t *len)
                                        /* octal constant */
                case '0': case '1': case '2': case '3':
                case '4': case '5': case '6': case '7':
-                       for (c = *fmt == '0' ? 4 : 3, value = 0;
+                       c = (!percent && *fmt == '0') ? 4 : 3;
+                       for (value = 0;
                            c-- && *fmt >= '0' && *fmt <= '7'; ++fmt) {
                                value <<= 3;
                                value += *fmt - '0';
@@ -418,7 +534,7 @@ escape(char *fmt, int percent, size_t *len)
                                *store++ = '%';
                                *store = '%';
                        } else
-                               *store = value;
+                               *store = (char)value;
                        break;
                default:
                        *store = *fmt;
@@ -457,7 +573,7 @@ getint(int *ip)
                return (1);
        rval = 0;
        if (val < INT_MIN || val > INT_MAX) {
-               warnx3("%s: %s", *gargv, strerror(ERANGE));
+               warnx("%s: %s", *gargv, strerror(ERANGE));
                rval = 1;
        }
        *ip = (int)val;
@@ -471,7 +587,7 @@ getnum(intmax_t *ip, uintmax_t *uip, int signedconv)
        int rval;
 
        if (!*gargv) {
-               *ip = 0;
+               *ip = *uip = 0;
                return (0);
        }
        if (**gargv == '"' || **gargv == '\'') {
@@ -488,15 +604,15 @@ getnum(intmax_t *ip, uintmax_t *uip, int signedconv)
        else
                *uip = strtoumax(*gargv, &ep, 0);
        if (ep == *gargv) {
-               warnx2("%s: expected numeric value", *gargv, NULL);
+               warnx("%s: expected numeric value", *gargv);
                rval = 1;
        }
        else if (*ep != '\0') {
-               warnx2("%s: not completely converted", *gargv, NULL);
+               warnx("%s: not completely converted", *gargv);
                rval = 1;
        }
        if (errno == ERANGE) {
-               warnx3("%s: %s", *gargv, strerror(ERANGE));
+               warnx("%s: %s", *gargv, strerror(ERANGE));
                rval = 1;
        }
        ++gargv;
@@ -524,14 +640,14 @@ getfloating(long double *dp, int mod_ldbl)
        else
                *dp = strtod(*gargv, &ep);
        if (ep == *gargv) {
-               warnx2("%s: expected numeric value", *gargv, NULL);
+               warnx("%s: expected numeric value", *gargv);
                rval = 1;
        } else if (*ep != '\0') {
-               warnx2("%s: not completely converted", *gargv, NULL);
+               warnx("%s: not completely converted", *gargv);
                rval = 1;
        }
        if (errno == ERANGE) {
-               warnx3("%s: %s", *gargv, strerror(ERANGE));
+               warnx("%s: %s", *gargv, strerror(ERANGE));
                rval = 1;
        }
        ++gargv;
@@ -542,10 +658,23 @@ static int
 asciicode(void)
 {
        int ch;
-
-       ch = **gargv;
-       if (ch == '\'' || ch == '"')
-               ch = (*gargv)[1];
+       wchar_t wch;
+       mbstate_t mbs;
+
+       ch = (unsigned char)**gargv;
+       if (ch == '\'' || ch == '"') {
+               memset(&mbs, 0, sizeof(mbs));
+               switch (mbrtowc(&wch, *gargv + 1, MB_LEN_MAX, &mbs)) {
+               case (size_t)-2:
+               case (size_t)-1:
+                       wch = (unsigned char)gargv[0][1];
+                       break;
+               case 0:
+                       wch = 0;
+                       break;
+               }
+               ch = wch;
+       }
        ++gargv;
        return (ch);
 }
diff --git a/printf/printf.plist.part b/printf/printf.plist.part
new file mode 100644 (file)
index 0000000..58f0040
--- /dev/null
@@ -0,0 +1,17 @@
+       <dict>
+               <key>OpenSourceProject</key>
+               <string>printf</string>
+               <key>OpenSourceVersion</key>
+               <string>2015-03-01</string>
+               <key>OpenSourceWebsiteURL</key>
+               <string>http://svnweb.freebsd.org/base/head/usr.bin/printf/</string>
+               <key>OpenSourceSCM</key>
+               <string>svn co http://svn.freebsd.org/base/head/usr.bin/printf/</string>
+               <key>OpenSourceImportDate</key>
+               <string>2015-07-23</string>
+               <key>OpenSourceModifications</key>
+               <array>
+               </array>
+               <key>OpenSourceLicense</key>
+               <string>bsd</string>
+       </dict>
diff --git a/sh/Makefile b/sh/Makefile
new file mode 100644 (file)
index 0000000..3f28a12
--- /dev/null
@@ -0,0 +1,67 @@
+#      @(#)Makefile    8.4 (Berkeley) 5/5/95
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG=  sh
+INSTALLFLAGS= -S
+SHSRCS=        alias.c arith_yacc.c arith_yylex.c cd.c echo.c error.c eval.c \
+       exec.c expand.c \
+       histedit.c input.c jobs.c kill.c mail.c main.c memalloc.c miscbltin.c \
+       mystring.c options.c output.c parser.c printf.c redir.c show.c \
+       test.c trap.c var.c
+GENSRCS= builtins.c nodes.c syntax.c
+GENHDRS= builtins.h nodes.h syntax.h token.h
+SRCS= ${SHSRCS} ${GENSRCS} ${GENHDRS}
+
+# MLINKS for Shell built in commands for which there are no userland
+# utilities of the same name are handled with the associated manpage,
+# builtin.1 in share/man/man1/.
+
+LIBADD=        edit
+
+CFLAGS+=-DSHELL -I. -I${.CURDIR}
+# for debug:
+# DEBUG_FLAGS+= -g -DDEBUG=2 -fno-inline
+WARNS?=        2
+WFORMAT=0
+
+.PATH: ${.CURDIR}/bltin \
+       ${.CURDIR}/../kill \
+       ${.CURDIR}/../test \
+       ${.CURDIR}/../../usr.bin/printf
+
+CLEANFILES+= mknodes mknodes.o \
+       mksyntax mksyntax.o
+CLEANFILES+= ${GENSRCS} ${GENHDRS}
+
+build-tools: mknodes mksyntax
+
+.ORDER: builtins.c builtins.h
+builtins.c builtins.h: mkbuiltins builtins.def
+       sh ${.CURDIR}/mkbuiltins ${.CURDIR}
+
+# XXX this is just to stop the default .c rule being used, so that the
+# intermediate object has a fixed name.
+# XXX we have a default .c rule, but no default .o rule.
+.o:
+       ${CC} ${CFLAGS} ${LDFLAGS} ${.IMPSRC} ${LDLIBS} -o ${.TARGET}
+mknodes: mknodes.o
+mksyntax: mksyntax.o
+
+.ORDER: nodes.c nodes.h
+nodes.c nodes.h: mknodes nodetypes nodes.c.pat
+       ./mknodes ${.CURDIR}/nodetypes ${.CURDIR}/nodes.c.pat
+
+.ORDER: syntax.c syntax.h
+syntax.c syntax.h: mksyntax
+       ./mksyntax
+
+token.h: mktokens
+       sh ${.CURDIR}/mktokens
+
+.if ${MK_TESTS} != "no"
+SUBDIR+=    tests
+.endif
+
+.include <bsd.prog.mk>
diff --git a/sh/Makefile.depend b/sh/Makefile.depend
new file mode 100644 (file)
index 0000000..9eb4fba
--- /dev/null
@@ -0,0 +1,114 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+       gnu/lib/csu \
+       gnu/lib/libgcc \
+       include \
+       include/xlocale \
+       lib/${CSU_DIR} \
+       lib/libc \
+       lib/libcompiler_rt \
+       lib/libedit \
+       lib/ncurses/ncursesw \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+alias.o: builtins.h
+alias.po: builtins.h
+arith_yylex.o: syntax.h
+arith_yylex.po: syntax.h
+builtins.o: builtins.c
+builtins.o: builtins.h
+builtins.po: builtins.c
+builtins.po: builtins.h
+cd.o: builtins.h
+cd.o: nodes.h
+cd.po: builtins.h
+cd.po: nodes.h
+echo.o: builtins.h
+echo.po: builtins.h
+error.o: nodes.h
+error.po: nodes.h
+eval.o: builtins.h
+eval.o: nodes.h
+eval.o: syntax.h
+eval.po: builtins.h
+eval.po: nodes.h
+eval.po: syntax.h
+exec.o: builtins.h
+exec.o: nodes.h
+exec.o: syntax.h
+exec.po: builtins.h
+exec.po: nodes.h
+exec.po: syntax.h
+expand.o: builtins.h
+expand.o: nodes.h
+expand.o: syntax.h
+expand.po: builtins.h
+expand.po: nodes.h
+expand.po: syntax.h
+histedit.o: builtins.h
+histedit.po: builtins.h
+input.o: syntax.h
+input.po: syntax.h
+jobs.o: builtins.h
+jobs.o: nodes.h
+jobs.o: syntax.h
+jobs.po: builtins.h
+jobs.po: nodes.h
+jobs.po: syntax.h
+kill.o: builtins.h
+kill.po: builtins.h
+main.o: builtins.h
+main.o: nodes.h
+main.po: builtins.h
+main.po: nodes.h
+miscbltin.o: syntax.h
+miscbltin.po: syntax.h
+mystring.o: syntax.h
+mystring.po: syntax.h
+nodes.o: nodes.c
+nodes.o: nodes.h
+nodes.po: nodes.c
+nodes.po: nodes.h
+options.o: builtins.h
+options.o: nodes.h
+options.po: builtins.h
+options.po: nodes.h
+output.o: syntax.h
+output.po: syntax.h
+parser.o: nodes.h
+parser.o: syntax.h
+parser.o: token.h
+parser.po: nodes.h
+parser.po: syntax.h
+parser.po: token.h
+printf.o: builtins.h
+printf.po: builtins.h
+redir.o: nodes.h
+redir.po: nodes.h
+show.o: nodes.h
+show.po: nodes.h
+syntax.o: syntax.c
+syntax.o: syntax.h
+syntax.po: syntax.c
+syntax.po: syntax.h
+test.o: builtins.h
+test.po: builtins.h
+trap.o: builtins.h
+trap.o: nodes.h
+trap.o: syntax.h
+trap.po: builtins.h
+trap.po: nodes.h
+trap.po: syntax.h
+var.o: builtins.h
+var.o: nodes.h
+var.o: syntax.h
+var.po: builtins.h
+var.po: nodes.h
+var.po: syntax.h
+.endif
diff --git a/sh/TOUR b/sh/TOUR
new file mode 100644 (file)
index 0000000..e9bbe9b
--- /dev/null
+++ b/sh/TOUR
@@ -0,0 +1,288 @@
+#      @(#)TOUR        8.1 (Berkeley) 5/31/93
+# $FreeBSD$
+
+NOTE -- This is the original TOUR paper distributed with ash and
+does not represent the current state of the shell.  It is provided anyway
+since it provides helpful information for how the shell is structured,
+but be warned that things have changed -- the current shell is
+still under development.
+
+================================================================
+
+                       A Tour through Ash
+
+               Copyright 1989 by Kenneth Almquist.
+
+
+DIRECTORIES:  The subdirectory bltin contains commands which can
+be compiled stand-alone.  The rest of the source is in the main
+ash directory.
+
+SOURCE CODE GENERATORS:  Files whose names begin with "mk" are
+programs that generate source code.  A complete list of these
+programs is:
+
+        program         input files         generates
+        -------         -----------         ---------
+        mkbuiltins      builtins            builtins.h builtins.c
+        mknodes         nodetypes           nodes.h nodes.c
+        mksyntax            -               syntax.h syntax.c
+        mktokens            -               token.h
+
+There are undoubtedly too many of these.
+
+EXCEPTIONS:  Code for dealing with exceptions appears in
+exceptions.c.  The C language doesn't include exception handling,
+so I implement it using setjmp and longjmp.  The global variable
+exception contains the type of exception.  EXERROR is raised by
+calling error.  EXINT is an interrupt.
+
+INTERRUPTS:  In an interactive shell, an interrupt will cause an
+EXINT exception to return to the main command loop.  (Exception:
+EXINT is not raised if the user traps interrupts using the trap
+command.)  The INTOFF and INTON macros (defined in exception.h)
+provide uninterruptible critical sections.  Between the execution
+of INTOFF and the execution of INTON, interrupt signals will be
+held for later delivery.  INTOFF and INTON can be nested.
+
+MEMALLOC.C:  Memalloc.c defines versions of malloc and realloc
+which call error when there is no memory left.  It also defines a
+stack oriented memory allocation scheme.  Allocating off a stack
+is probably more efficient than allocation using malloc, but the
+big advantage is that when an exception occurs all we have to do
+to free up the memory in use at the time of the exception is to
+restore the stack pointer.  The stack is implemented using a
+linked list of blocks.
+
+STPUTC:  If the stack were contiguous, it would be easy to store
+strings on the stack without knowing in advance how long the
+string was going to be:
+        p = stackptr;
+        *p++ = c;       /* repeated as many times as needed */
+        stackptr = p;
+The following three macros (defined in memalloc.h) perform these
+operations, but grow the stack if you run off the end:
+        STARTSTACKSTR(p);
+        STPUTC(c, p);   /* repeated as many times as needed */
+        grabstackstr(p);
+
+We now start a top-down look at the code:
+
+MAIN.C:  The main routine performs some initialization, executes
+the user's profile if necessary, and calls cmdloop.  Cmdloop
+repeatedly parses and executes commands.
+
+OPTIONS.C:  This file contains the option processing code.  It is
+called from main to parse the shell arguments when the shell is
+invoked, and it also contains the set builtin.  The -i and -m op-
+tions (the latter turns on job control) require changes in signal
+handling.  The routines setjobctl (in jobs.c) and setinteractive
+(in trap.c) are called to handle changes to these options.
+
+PARSING:  The parser code is all in parser.c.  A recursive des-
+cent parser is used.  Syntax tables (generated by mksyntax) are
+used to classify characters during lexical analysis.  There are
+four tables:  one for normal use, one for use when inside single
+quotes and dollar single quotes, one for use when inside double
+quotes and one for use in arithmetic.  The tables are machine
+dependent because they are indexed by character variables and
+the range of a char varies from machine to machine.
+
+PARSE OUTPUT:  The output of the parser consists of a tree of
+nodes.  The various types of nodes are defined in the file node-
+types.
+
+Nodes of type NARG are used to represent both words and the con-
+tents of here documents.  An early version of ash kept the con-
+tents of here documents in temporary files, but keeping here do-
+cuments in memory typically results in significantly better per-
+formance.  It would have been nice to make it an option to use
+temporary files for here documents, for the benefit of small
+machines, but the code to keep track of when to delete the tem-
+porary files was complex and I never fixed all the bugs in it.
+(AT&T has been maintaining the Bourne shell for more than ten
+years, and to the best of my knowledge they still haven't gotten
+it to handle temporary files correctly in obscure cases.)
+
+The text field of a NARG structure points to the text of the
+word.  The text consists of ordinary characters and a number of
+special codes defined in parser.h.  The special codes are:
+
+        CTLVAR              Variable substitution
+        CTLENDVAR           End of variable substitution
+        CTLBACKQ            Command substitution
+        CTLBACKQ|CTLQUOTE   Command substitution inside double quotes
+        CTLESC              Escape next character
+
+A variable substitution contains the following elements:
+
+        CTLVAR type name '=' [ alternative-text CTLENDVAR ]
+
+The type field is a single character specifying the type of sub-
+stitution.  The possible types are:
+
+        VSNORMAL            $var
+        VSMINUS             ${var-text}
+        VSMINUS|VSNUL       ${var:-text}
+        VSPLUS              ${var+text}
+        VSPLUS|VSNUL        ${var:+text}
+        VSQUESTION          ${var?text}
+        VSQUESTION|VSNUL    ${var:?text}
+        VSASSIGN            ${var=text}
+        VSASSIGN|VSNUL      ${var:=text}
+
+In addition, the type field will have the VSQUOTE flag set if the
+variable is enclosed in double quotes.  The name of the variable
+comes next, terminated by an equals sign.  If the type is not
+VSNORMAL, then the text field in the substitution follows, ter-
+minated by a CTLENDVAR byte.
+
+Commands in back quotes are parsed and stored in a linked list.
+The locations of these commands in the string are indicated by
+CTLBACKQ and CTLBACKQ+CTLQUOTE characters, depending upon whether
+the back quotes were enclosed in double quotes.
+
+The character CTLESC escapes the next character, so that in case
+any of the CTL characters mentioned above appear in the input,
+they can be passed through transparently.  CTLESC is also used to
+escape '*', '?', '[', and '!' characters which were quoted by the
+user and thus should not be used for file name generation.
+
+CTLESC characters have proved to be particularly tricky to get
+right.  In the case of here documents which are not subject to
+variable and command substitution, the parser doesn't insert any
+CTLESC characters to begin with (so the contents of the text
+field can be written without any processing).  Other here docu-
+ments, and words which are not subject to splitting and file name
+generation, have the CTLESC characters removed during the vari-
+able and command substitution phase.  Words which are subject to
+splitting and file name generation have the CTLESC characters re-
+moved as part of the file name phase.
+
+EXECUTION:  Command execution is handled by the following files:
+        eval.c     The top level routines.
+        redir.c    Code to handle redirection of input and output.
+        jobs.c     Code to handle forking, waiting, and job control.
+        exec.c     Code to do path searches and the actual exec sys call.
+        expand.c   Code to evaluate arguments.
+        var.c      Maintains the variable symbol table.  Called from expand.c.
+
+EVAL.C:  Evaltree recursively executes a parse tree.  The exit
+status is returned in the global variable exitstatus.  The alter-
+native entry evalbackcmd is called to evaluate commands in back
+quotes.  It saves the result in memory if the command is a buil-
+tin; otherwise it forks off a child to execute the command and
+connects the standard output of the child to a pipe.
+
+JOBS.C:  To create a process, you call makejob to return a job
+structure, and then call forkshell (passing the job structure as
+an argument) to create the process.  Waitforjob waits for a job
+to complete.  These routines take care of process groups if job
+control is defined.
+
+REDIR.C:  Ash allows file descriptors to be redirected and then
+restored without forking off a child process.  This is accom-
+plished by duplicating the original file descriptors.  The redir-
+tab structure records where the file descriptors have been dupli-
+cated to.
+
+EXEC.C:  The routine find_command locates a command, and enters
+the command in the hash table if it is not already there.  The
+third argument specifies whether it is to print an error message
+if the command is not found.  (When a pipeline is set up,
+find_command is called for all the commands in the pipeline be-
+fore any forking is done, so to get the commands into the hash
+table of the parent process.  But to make command hashing as
+transparent as possible, we silently ignore errors at that point
+and only print error messages if the command cannot be found
+later.)
+
+The routine shellexec is the interface to the exec system call.
+
+EXPAND.C:  Arguments are processed in three passes.  The first
+(performed by the routine argstr) performs variable and command
+substitution.  The second (ifsbreakup) performs word splitting
+and the third (expandmeta) performs file name generation.
+
+VAR.C:  Variables are stored in a hash table.  Probably we should
+switch to extensible hashing.  The variable name is stored in the
+same string as the value (using the format "name=value") so that
+no string copying is needed to create the environment of a com-
+mand.  Variables which the shell references internally are preal-
+located so that the shell can reference the values of these vari-
+ables without doing a lookup.
+
+When a program is run, the code in eval.c sticks any environment
+variables which precede the command (as in "PATH=xxx command") in
+the variable table as the simplest way to strip duplicates, and
+then calls "environment" to get the value of the environment.
+
+BUILTIN COMMANDS:  The procedures for handling these are scat-
+tered throughout the code, depending on which location appears
+most appropriate.  They can be recognized because their names al-
+ways end in "cmd".  The mapping from names to procedures is
+specified in the file builtins, which is processed by the mkbuilt-
+ins command.
+
+A builtin command is invoked with argc and argv set up like a
+normal program.  A builtin command is allowed to overwrite its
+arguments.  Builtin routines can call nextopt to do option pars-
+ing.  This is kind of like getopt, but you don't pass argc and
+argv to it.  Builtin routines can also call error.  This routine
+normally terminates the shell (or returns to the main command
+loop if the shell is interactive), but when called from a builtin
+command it causes the builtin command to terminate with an exit
+status of 2.
+
+The directory bltins contains commands which can be compiled in-
+dependently but can also be built into the shell for efficiency
+reasons.  The makefile in this directory compiles these programs
+in the normal fashion (so that they can be run regardless of
+whether the invoker is ash), but also creates a library named
+bltinlib.a which can be linked with ash.  The header file bltin.h
+takes care of most of the differences between the ash and the
+stand-alone environment.  The user should call the main routine
+"main", and #define main to be the name of the routine to use
+when the program is linked into ash.  This #define should appear
+before bltin.h is included; bltin.h will #undef main if the pro-
+gram is to be compiled stand-alone.
+
+CD.C:  This file defines the cd and pwd builtins.
+
+SIGNALS:  Trap.c implements the trap command.  The routine set-
+signal figures out what action should be taken when a signal is
+received and invokes the signal system call to set the signal ac-
+tion appropriately.  When a signal that a user has set a trap for
+is caught, the routine "onsig" sets a flag.  The routine dotrap
+is called at appropriate points to actually handle the signal.
+When an interrupt is caught and no trap has been set for that
+signal, the routine "onint" in error.c is called.
+
+OUTPUT:  Ash uses it's own output routines.  There are three out-
+put structures allocated.  "Output" represents the standard out-
+put, "errout" the standard error, and "memout" contains output
+which is to be stored in memory.  This last is used when a buil-
+tin command appears in backquotes, to allow its output to be col-
+lected without doing any I/O through the UNIX operating system.
+The variables out1 and out2 normally point to output and errout,
+respectively, but they are set to point to memout when appropri-
+ate inside backquotes.
+
+INPUT:  The basic input routine is pgetc, which reads from the
+current input file.  There is a stack of input files; the current
+input file is the top file on this stack.  The code allows the
+input to come from a string rather than a file.  (This is for the
+-c option and the "." and eval builtin commands.)  The global
+variable plinno is saved and restored when files are pushed and
+popped from the stack.  The parser routines store the number of
+the current line in this variable.
+
+DEBUGGING:  If DEBUG is defined in shell.h, then the shell will
+write debugging information to the file $HOME/trace.  Most of
+this is done using the TRACE macro, which takes a set of printf
+arguments inside two sets of parenthesis.  Example:
+"TRACE(("n=%d0, n))".  The double parenthesis are necessary be-
+cause the preprocessor can't handle functions with a variable
+number of arguments.  Defining DEBUG also causes the shell to
+generate a core dump if it is sent a quit signal.  The tracing
+code is in show.c.
diff --git a/sh/alias.c b/sh/alias.c
new file mode 100644 (file)
index 0000000..a77ce99
--- /dev/null
@@ -0,0 +1,255 @@
+/*-
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)alias.c    8.3 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdlib.h>
+#include "shell.h"
+#include "output.h"
+#include "error.h"
+#include "memalloc.h"
+#include "mystring.h"
+#include "alias.h"
+#include "options.h"   /* XXX for argptr (should remove?) */
+#include "builtins.h"
+
+#define ATABSIZE 39
+
+static struct alias *atab[ATABSIZE];
+static int aliases;
+
+static void setalias(const char *, const char *);
+static int unalias(const char *);
+static struct alias **hashalias(const char *);
+
+static
+void
+setalias(const char *name, const char *val)
+{
+       struct alias *ap, **app;
+
+       app = hashalias(name);
+       for (ap = *app; ap; ap = ap->next) {
+               if (equal(name, ap->name)) {
+                       INTOFF;
+                       ckfree(ap->val);
+                       ap->val = savestr(val);
+                       INTON;
+                       return;
+               }
+       }
+       /* not found */
+       INTOFF;
+       ap = ckmalloc(sizeof (struct alias));
+       ap->name = savestr(name);
+       ap->val = savestr(val);
+       ap->flag = 0;
+       ap->next = *app;
+       *app = ap;
+       aliases++;
+       INTON;
+}
+
+static int
+unalias(const char *name)
+{
+       struct alias *ap, **app;
+
+       app = hashalias(name);
+
+       for (ap = *app; ap; app = &(ap->next), ap = ap->next) {
+               if (equal(name, ap->name)) {
+                       /*
+                        * if the alias is currently in use (i.e. its
+                        * buffer is being used by the input routine) we
+                        * just null out the name instead of freeing it.
+                        * We could clear it out later, but this situation
+                        * is so rare that it hardly seems worth it.
+                        */
+                       if (ap->flag & ALIASINUSE)
+                               *ap->name = '\0';
+                       else {
+                               INTOFF;
+                               *app = ap->next;
+                               ckfree(ap->name);
+                               ckfree(ap->val);
+                               ckfree(ap);
+                               INTON;
+                       }
+                       aliases--;
+                       return (0);
+               }
+       }
+
+       return (1);
+}
+
+static void
+rmaliases(void)
+{
+       struct alias *ap, *tmp;
+       int i;
+
+       INTOFF;
+       for (i = 0; i < ATABSIZE; i++) {
+               ap = atab[i];
+               atab[i] = NULL;
+               while (ap) {
+                       ckfree(ap->name);
+                       ckfree(ap->val);
+                       tmp = ap;
+                       ap = ap->next;
+                       ckfree(tmp);
+               }
+       }
+       aliases = 0;
+       INTON;
+}
+
+struct alias *
+lookupalias(const char *name, int check)
+{
+       struct alias *ap = *hashalias(name);
+
+       for (; ap; ap = ap->next) {
+               if (equal(name, ap->name)) {
+                       if (check && (ap->flag & ALIASINUSE))
+                               return (NULL);
+                       return (ap);
+               }
+       }
+
+       return (NULL);
+}
+
+static int
+comparealiases(const void *p1, const void *p2)
+{
+       const struct alias *const *a1 = p1;
+       const struct alias *const *a2 = p2;
+
+       return strcmp((*a1)->name, (*a2)->name);
+}
+
+static void
+printalias(const struct alias *a)
+{
+       out1fmt("%s=", a->name);
+       out1qstr(a->val);
+       out1c('\n');
+}
+
+static void
+printaliases(void)
+{
+       int i, j;
+       struct alias **sorted, *ap;
+
+       INTOFF;
+       sorted = ckmalloc(aliases * sizeof(*sorted));
+       j = 0;
+       for (i = 0; i < ATABSIZE; i++)
+               for (ap = atab[i]; ap; ap = ap->next)
+                       if (*ap->name != '\0')
+                               sorted[j++] = ap;
+       qsort(sorted, aliases, sizeof(*sorted), comparealiases);
+       for (i = 0; i < aliases; i++) {
+               printalias(sorted[i]);
+               if (int_pending())
+                       break;
+       }
+       ckfree(sorted);
+       INTON;
+}
+
+int
+aliascmd(int argc __unused, char **argv __unused)
+{
+       char *n, *v;
+       int ret = 0;
+       struct alias *ap;
+
+       nextopt("");
+
+       if (*argptr == NULL) {
+               printaliases();
+               return (0);
+       }
+       while ((n = *argptr++) != NULL) {
+               if ((v = strchr(n+1, '=')) == NULL) /* n+1: funny ksh stuff */
+                       if ((ap = lookupalias(n, 0)) == NULL) {
+                               warning("%s: not found", n);
+                               ret = 1;
+                       } else
+                               printalias(ap);
+               else {
+                       *v++ = '\0';
+                       setalias(n, v);
+               }
+       }
+
+       return (ret);
+}
+
+int
+unaliascmd(int argc __unused, char **argv __unused)
+{
+       int i;
+
+       while ((i = nextopt("a")) != '\0') {
+               if (i == 'a') {
+                       rmaliases();
+                       return (0);
+               }
+       }
+       for (i = 0; *argptr; argptr++)
+               i |= unalias(*argptr);
+
+       return (i);
+}
+
+static struct alias **
+hashalias(const char *p)
+{
+       unsigned int hashval;
+
+       hashval = (unsigned char)*p << 4;
+       while (*p)
+               hashval+= *p++;
+       return &atab[hashval % ATABSIZE];
+}
diff --git a/sh/alias.h b/sh/alias.h
new file mode 100644 (file)
index 0000000..546e91a
--- /dev/null
@@ -0,0 +1,45 @@
+/*-
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)alias.h     8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+#define ALIASINUSE     1
+
+struct alias {
+       struct alias *next;
+       char *name;
+       char *val;
+       int flag;
+};
+
+struct alias *lookupalias(const char *, int);
diff --git a/sh/arith.h b/sh/arith.h
new file mode 100644 (file)
index 0000000..5b18bd6
--- /dev/null
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (c) 1995
+ *      The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)arith.h     1.1 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+#include "shell.h"
+
+#define DIGITS(var) (3 + (2 + CHAR_BIT * sizeof((var))) / 3)
+
+arith_t arith(const char *);
+void arith_lex_reset(void);
diff --git a/sh/arith_yacc.c b/sh/arith_yacc.c
new file mode 100644 (file)
index 0000000..5000c6b
--- /dev/null
@@ -0,0 +1,381 @@
+/*-
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 2007
+ *     Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <limits.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "arith.h"
+#include "arith_yacc.h"
+#include "expand.h"
+#include "shell.h"
+#include "error.h"
+#include "memalloc.h"
+#include "output.h"
+#include "options.h"
+#include "var.h"
+
+#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ
+#error Arithmetic tokens are out of order.
+#endif
+
+static const char *arith_startbuf;
+
+const char *arith_buf;
+union yystype yylval;
+
+static int last_token;
+
+#define ARITH_PRECEDENCE(op, prec) [op - ARITH_BINOP_MIN] = prec
+
+static const char prec[ARITH_BINOP_MAX - ARITH_BINOP_MIN] = {
+       ARITH_PRECEDENCE(ARITH_MUL, 0),
+       ARITH_PRECEDENCE(ARITH_DIV, 0),
+       ARITH_PRECEDENCE(ARITH_REM, 0),
+       ARITH_PRECEDENCE(ARITH_ADD, 1),
+       ARITH_PRECEDENCE(ARITH_SUB, 1),
+       ARITH_PRECEDENCE(ARITH_LSHIFT, 2),
+       ARITH_PRECEDENCE(ARITH_RSHIFT, 2),
+       ARITH_PRECEDENCE(ARITH_LT, 3),
+       ARITH_PRECEDENCE(ARITH_LE, 3),
+       ARITH_PRECEDENCE(ARITH_GT, 3),
+       ARITH_PRECEDENCE(ARITH_GE, 3),
+       ARITH_PRECEDENCE(ARITH_EQ, 4),
+       ARITH_PRECEDENCE(ARITH_NE, 4),
+       ARITH_PRECEDENCE(ARITH_BAND, 5),
+       ARITH_PRECEDENCE(ARITH_BXOR, 6),
+       ARITH_PRECEDENCE(ARITH_BOR, 7),
+};
+
+#define ARITH_MAX_PREC 8
+
+int letcmd(int, char **);
+
+static __dead2 void yyerror(const char *s)
+{
+       error("arithmetic expression: %s: \"%s\"", s, arith_startbuf);
+       /* NOTREACHED */
+}
+
+static arith_t arith_lookupvarint(char *varname)
+{
+       const char *str;
+       char *p;
+       arith_t result;
+
+       str = lookupvar(varname);
+       if (uflag && str == NULL)
+               yyerror("variable not set");
+       if (str == NULL || *str == '\0')
+               str = "0";
+       errno = 0;
+       result = strtoarith_t(str, &p, 0);
+       if (errno != 0 || *p != '\0')
+               yyerror("variable conversion error");
+       return result;
+}
+
+static inline int arith_prec(int op)
+{
+       return prec[op - ARITH_BINOP_MIN];
+}
+
+static inline int higher_prec(int op1, int op2)
+{
+       return arith_prec(op1) < arith_prec(op2);
+}
+
+static arith_t do_binop(int op, arith_t a, arith_t b)
+{
+
+       switch (op) {
+       default:
+       case ARITH_REM:
+       case ARITH_DIV:
+               if (!b)
+                       yyerror("division by zero");
+               if (a == ARITH_MIN && b == -1)
+                       yyerror("divide error");
+               return op == ARITH_REM ? a % b : a / b;
+       case ARITH_MUL:
+               return (uintmax_t)a * (uintmax_t)b;
+       case ARITH_ADD:
+               return (uintmax_t)a + (uintmax_t)b;
+       case ARITH_SUB:
+               return (uintmax_t)a - (uintmax_t)b;
+       case ARITH_LSHIFT:
+               return (uintmax_t)a << (b & (sizeof(uintmax_t) * CHAR_BIT - 1));
+       case ARITH_RSHIFT:
+               return a >> (b & (sizeof(uintmax_t) * CHAR_BIT - 1));
+       case ARITH_LT:
+               return a < b;
+       case ARITH_LE:
+               return a <= b;
+       case ARITH_GT:
+               return a > b;
+       case ARITH_GE:
+               return a >= b;
+       case ARITH_EQ:
+               return a == b;
+       case ARITH_NE:
+               return a != b;
+       case ARITH_BAND:
+               return a & b;
+       case ARITH_BXOR:
+               return a ^ b;
+       case ARITH_BOR:
+               return a | b;
+       }
+}
+
+static arith_t assignment(int var, int noeval);
+
+static arith_t primary(int token, union yystype *val, int op, int noeval)
+{
+       arith_t result;
+
+again:
+       switch (token) {
+       case ARITH_LPAREN:
+               result = assignment(op, noeval);
+               if (last_token != ARITH_RPAREN)
+                       yyerror("expecting ')'");
+               last_token = yylex();
+               return result;
+       case ARITH_NUM:
+               last_token = op;
+               return val->val;
+       case ARITH_VAR:
+               last_token = op;
+               return noeval ? val->val : arith_lookupvarint(val->name);
+       case ARITH_ADD:
+               token = op;
+               *val = yylval;
+               op = yylex();
+               goto again;
+       case ARITH_SUB:
+               *val = yylval;
+               return -primary(op, val, yylex(), noeval);
+       case ARITH_NOT:
+               *val = yylval;
+               return !primary(op, val, yylex(), noeval);
+       case ARITH_BNOT:
+               *val = yylval;
+               return ~primary(op, val, yylex(), noeval);
+       default:
+               yyerror("expecting primary");
+       }
+}
+
+static arith_t binop2(arith_t a, int op, int precedence, int noeval)
+{
+       for (;;) {
+               union yystype val;
+               arith_t b;
+               int op2;
+               int token;
+
+               token = yylex();
+               val = yylval;
+
+               b = primary(token, &val, yylex(), noeval);
+
+               op2 = last_token;
+               if (op2 >= ARITH_BINOP_MIN && op2 < ARITH_BINOP_MAX &&
+                   higher_prec(op2, op)) {
+                       b = binop2(b, op2, arith_prec(op), noeval);
+                       op2 = last_token;
+               }
+
+               a = noeval ? b : do_binop(op, a, b);
+
+               if (op2 < ARITH_BINOP_MIN || op2 >= ARITH_BINOP_MAX ||
+                   arith_prec(op2) >= precedence)
+                       return a;
+
+               op = op2;
+       }
+}
+
+static arith_t binop(int token, union yystype *val, int op, int noeval)
+{
+       arith_t a = primary(token, val, op, noeval);
+
+       op = last_token;
+       if (op < ARITH_BINOP_MIN || op >= ARITH_BINOP_MAX)
+               return a;
+
+       return binop2(a, op, ARITH_MAX_PREC, noeval);
+}
+
+static arith_t and(int token, union yystype *val, int op, int noeval)
+{
+       arith_t a = binop(token, val, op, noeval);
+       arith_t b;
+
+       op = last_token;
+       if (op != ARITH_AND)
+               return a;
+
+       token = yylex();
+       *val = yylval;
+
+       b = and(token, val, yylex(), noeval | !a);
+
+       return a && b;
+}
+
+static arith_t or(int token, union yystype *val, int op, int noeval)
+{
+       arith_t a = and(token, val, op, noeval);
+       arith_t b;
+
+       op = last_token;
+       if (op != ARITH_OR)
+               return a;
+
+       token = yylex();
+       *val = yylval;
+
+       b = or(token, val, yylex(), noeval | !!a);
+
+       return a || b;
+}
+
+static arith_t cond(int token, union yystype *val, int op, int noeval)
+{
+       arith_t a = or(token, val, op, noeval);
+       arith_t b;
+       arith_t c;
+
+       if (last_token != ARITH_QMARK)
+               return a;
+
+       b = assignment(yylex(), noeval | !a);
+
+       if (last_token != ARITH_COLON)
+               yyerror("expecting ':'");
+
+       token = yylex();
+       *val = yylval;
+
+       c = cond(token, val, yylex(), noeval | !!a);
+
+       return a ? b : c;
+}
+
+static arith_t assignment(int var, int noeval)
+{
+       union yystype val = yylval;
+       int op = yylex();
+       arith_t result;
+       char sresult[DIGITS(result) + 1];
+
+       if (var != ARITH_VAR)
+               return cond(var, &val, op, noeval);
+
+       if (op != ARITH_ASS && (op < ARITH_ASS_MIN || op >= ARITH_ASS_MAX))
+               return cond(var, &val, op, noeval);
+
+       result = assignment(yylex(), noeval);
+       if (noeval)
+               return result;
+
+       if (op != ARITH_ASS)
+               result = do_binop(op - 11, arith_lookupvarint(val.name), result);
+       snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR, result);
+       setvar(val.name, sresult, 0);
+       return result;
+}
+
+arith_t arith(const char *s)
+{
+       struct stackmark smark;
+       arith_t result;
+
+       setstackmark(&smark);
+
+       arith_buf = arith_startbuf = s;
+
+       result = assignment(yylex(), 0);
+
+       if (last_token)
+               yyerror("expecting EOF");
+
+       popstackmark(&smark);
+
+       return result;
+}
+
+/*
+ *  The exp(1) builtin.
+ */
+int
+letcmd(int argc, char **argv)
+{
+       const char *p;
+       char *concat;
+       char **ap;
+       arith_t i;
+
+       if (argc > 1) {
+               p = argv[1];
+               if (argc > 2) {
+                       /*
+                        * Concatenate arguments.
+                        */
+                       STARTSTACKSTR(concat);
+                       ap = argv + 2;
+                       for (;;) {
+                               while (*p)
+                                       STPUTC(*p++, concat);
+                               if ((p = *ap++) == NULL)
+                                       break;
+                               STPUTC(' ', concat);
+                       }
+                       STPUTC('\0', concat);
+                       p = grabstackstr(concat);
+               }
+       } else
+               p = "";
+
+       i = arith(p);
+
+       out1fmt(ARITH_FORMAT_STR "\n", i);
+       return !i;
+}
diff --git a/sh/arith_yacc.h b/sh/arith_yacc.h
new file mode 100644 (file)
index 0000000..ca92e6f
--- /dev/null
@@ -0,0 +1,93 @@
+/*-
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 2007
+ *     Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#define ARITH_ASS 1
+
+#define ARITH_OR 2
+#define ARITH_AND 3
+#define ARITH_BAD 4
+#define ARITH_NUM 5
+#define ARITH_VAR 6
+#define ARITH_NOT 7
+
+#define ARITH_BINOP_MIN 8
+#define ARITH_LE 8
+#define ARITH_GE 9
+#define ARITH_LT 10
+#define ARITH_GT 11
+#define ARITH_EQ 12
+#define ARITH_REM 13
+#define ARITH_BAND 14
+#define ARITH_LSHIFT 15
+#define ARITH_RSHIFT 16
+#define ARITH_MUL 17
+#define ARITH_ADD 18
+#define ARITH_BOR 19
+#define ARITH_SUB 20
+#define ARITH_BXOR 21
+#define ARITH_DIV 22
+#define ARITH_NE 23
+#define ARITH_BINOP_MAX 24
+
+#define ARITH_ASS_MIN 24
+#define ARITH_REMASS 24
+#define ARITH_BANDASS 25
+#define ARITH_LSHIFTASS 26
+#define ARITH_RSHIFTASS 27
+#define ARITH_MULASS 28
+#define ARITH_ADDASS 29
+#define ARITH_BORASS 30
+#define ARITH_SUBASS 31
+#define ARITH_BXORASS 32
+#define ARITH_DIVASS 33
+#define ARITH_ASS_MAX 34
+
+#define ARITH_LPAREN 34
+#define ARITH_RPAREN 35
+#define ARITH_BNOT 36
+#define ARITH_QMARK 37
+#define ARITH_COLON 38
+
+extern const char *arith_buf;
+
+union yystype {
+       arith_t val;
+       char *name;
+};
+
+extern union yystype yylval;
+
+int yylex(void);
diff --git a/sh/arith_yylex.c b/sh/arith_yylex.c
new file mode 100644 (file)
index 0000000..f7eaf3e
--- /dev/null
@@ -0,0 +1,248 @@
+/*-
+ * Copyright (c) 2002
+ *     Herbert Xu.
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include "shell.h"
+#include "arith_yacc.h"
+#include "expand.h"
+#include "error.h"
+#include "memalloc.h"
+#include "parser.h"
+#include "syntax.h"
+
+#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ
+#error Arithmetic tokens are out of order.
+#endif
+
+int
+yylex(void)
+{
+       int value;
+       const char *buf = arith_buf;
+       char *end;
+       const char *p;
+
+       for (;;) {
+               value = *buf;
+               switch (value) {
+               case ' ':
+               case '\t':
+               case '\n':
+                       buf++;
+                       continue;
+               default:
+                       return ARITH_BAD;
+               case '0':
+               case '1':
+               case '2':
+               case '3':
+               case '4':
+               case '5':
+               case '6':
+               case '7':
+               case '8':
+               case '9':
+                       yylval.val = strtoarith_t(buf, &end, 0);
+                       arith_buf = end;
+                       return ARITH_NUM;
+               case 'A':
+               case 'B':
+               case 'C':
+               case 'D':
+               case 'E':
+               case 'F':
+               case 'G':
+               case 'H':
+               case 'I':
+               case 'J':
+               case 'K':
+               case 'L':
+               case 'M':
+               case 'N':
+               case 'O':
+               case 'P':
+               case 'Q':
+               case 'R':
+               case 'S':
+               case 'T':
+               case 'U':
+               case 'V':
+               case 'W':
+               case 'X':
+               case 'Y':
+               case 'Z':
+               case '_':
+               case 'a':
+               case 'b':
+               case 'c':
+               case 'd':
+               case 'e':
+               case 'f':
+               case 'g':
+               case 'h':
+               case 'i':
+               case 'j':
+               case 'k':
+               case 'l':
+               case 'm':
+               case 'n':
+               case 'o':
+               case 'p':
+               case 'q':
+               case 'r':
+               case 's':
+               case 't':
+               case 'u':
+               case 'v':
+               case 'w':
+               case 'x':
+               case 'y':
+               case 'z':
+                       p = buf;
+                       while (buf++, is_in_name(*buf))
+                               ;
+                       yylval.name = stalloc(buf - p + 1);
+                       memcpy(yylval.name, p, buf - p);
+                       yylval.name[buf - p] = '\0';
+                       value = ARITH_VAR;
+                       goto out;
+               case '=':
+                       value += ARITH_ASS - '=';
+checkeq:
+                       buf++;
+checkeqcur:
+                       if (*buf != '=')
+                               goto out;
+                       value += 11;
+                       break;
+               case '>':
+                       switch (*++buf) {
+                       case '=':
+                               value += ARITH_GE - '>';
+                               break;
+                       case '>':
+                               value += ARITH_RSHIFT - '>';
+                               goto checkeq;
+                       default:
+                               value += ARITH_GT - '>';
+                               goto out;
+                       }
+                       break;
+               case '<':
+                       switch (*++buf) {
+                       case '=':
+                               value += ARITH_LE - '<';
+                               break;
+                       case '<':
+                               value += ARITH_LSHIFT - '<';
+                               goto checkeq;
+                       default:
+                               value += ARITH_LT - '<';
+                               goto out;
+                       }
+                       break;
+               case '|':
+                       if (*++buf != '|') {
+                               value += ARITH_BOR - '|';
+                               goto checkeqcur;
+                       }
+                       value += ARITH_OR - '|';
+                       break;
+               case '&':
+                       if (*++buf != '&') {
+                               value += ARITH_BAND - '&';
+                               goto checkeqcur;
+                       }
+                       value += ARITH_AND - '&';
+                       break;
+               case '!':
+                       if (*++buf != '=') {
+                               value += ARITH_NOT - '!';
+                               goto out;
+                       }
+                       value += ARITH_NE - '!';
+                       break;
+               case 0:
+                       goto out;
+               case '(':
+                       value += ARITH_LPAREN - '(';
+                       break;
+               case ')':
+                       value += ARITH_RPAREN - ')';
+                       break;
+               case '*':
+                       value += ARITH_MUL - '*';
+                       goto checkeq;
+               case '/':
+                       value += ARITH_DIV - '/';
+                       goto checkeq;
+               case '%':
+                       value += ARITH_REM - '%';
+                       goto checkeq;
+               case '+':
+                       if (buf[1] == '+')
+                               return ARITH_BAD;
+                       value += ARITH_ADD - '+';
+                       goto checkeq;
+               case '-':
+                       if (buf[1] == '-')
+                               return ARITH_BAD;
+                       value += ARITH_SUB - '-';
+                       goto checkeq;
+               case '~':
+                       value += ARITH_BNOT - '~';
+                       break;
+               case '^':
+                       value += ARITH_BXOR - '^';
+                       goto checkeq;
+               case '?':
+                       value += ARITH_QMARK - '?';
+                       break;
+               case ':':
+                       value += ARITH_COLON - ':';
+                       break;
+               }
+               break;
+       }
+
+       buf++;
+out:
+       arith_buf = buf;
+       return value;
+}
diff --git a/sh/bltin/bltin.h b/sh/bltin/bltin.h
new file mode 100644 (file)
index 0000000..a530ab7
--- /dev/null
@@ -0,0 +1,79 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)bltin.h     8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+/*
+ * This file is included by programs which are optionally built into the
+ * shell.  If SHELL is defined, we try to map the standard UNIX library
+ * routines to ash routines using defines.
+ */
+
+#include "../shell.h"
+#include "../mystring.h"
+#ifdef SHELL
+#include "../error.h"
+#include "../output.h"
+#include "builtins.h"
+#define FILE struct output
+#undef stdout
+#define stdout out1
+#undef stderr
+#define stderr out2
+#define printf out1fmt
+#undef putc
+#define putc(c, file)  outc(c, file)
+#undef putchar
+#define putchar(c)     out1c(c)
+#define fprintf outfmt
+#define fputs outstr
+#define fwrite(ptr, size, nmemb, file) outbin(ptr, (size) * (nmemb), file)
+#define fflush flushout
+#define INITARGS(argv)
+#define warnx warning
+#define warn(fmt, ...) warning(fmt ": %s", __VA_ARGS__, strerror(errno))
+#define errx(exitstatus, ...) error(__VA_ARGS__)
+
+#else
+#undef NULL
+#include <stdio.h>
+#undef main
+#define INITARGS(argv) if ((commandname = argv[0]) == NULL) {fputs("Argc is zero\n", stderr); exit(2);} else
+#endif
+
+#include <unistd.h>
+
+pointer stalloc(int);
+int killjob(const char *, int);
+
+extern char *commandname;
diff --git a/sh/bltin/echo.c b/sh/bltin/echo.c
new file mode 100644 (file)
index 0000000..457a4c7
--- /dev/null
@@ -0,0 +1,107 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)echo.c      8.2 (Berkeley) 5/4/95
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Echo command.
+ */
+
+#define main echocmd
+
+#include "bltin.h"
+
+/* #define eflag 1 */
+
+int
+main(int argc, char *argv[])
+{
+       char **ap;
+       char *p;
+       char c;
+       int count;
+       int nflag = 0;
+#ifndef eflag
+       int eflag = 0;
+#endif
+
+       ap = argv;
+       if (argc)
+               ap++;
+       if ((p = *ap) != NULL) {
+               if (equal(p, "-n")) {
+                       nflag++;
+                       ap++;
+               } else if (equal(p, "-e")) {
+#ifndef eflag
+                       eflag++;
+#endif
+                       ap++;
+               }
+       }
+       while ((p = *ap++) != NULL) {
+               while ((c = *p++) != '\0') {
+                       if (c == '\\' && eflag) {
+                               switch (*p++) {
+                               case 'a':  c = '\a';  break;
+                               case 'b':  c = '\b';  break;
+                               case 'c':  return 0;            /* exit */
+                               case 'e':  c = '\033';  break;
+                               case 'f':  c = '\f';  break;
+                               case 'n':  c = '\n';  break;
+                               case 'r':  c = '\r';  break;
+                               case 't':  c = '\t';  break;
+                               case 'v':  c = '\v';  break;
+                               case '\\':  break;              /* c = '\\' */
+                               case '0':
+                                       c = 0;
+                                       count = 3;
+                                       while (--count >= 0 && (unsigned)(*p - '0') < 8)
+                                               c = (c << 3) + (*p++ - '0');
+                                       break;
+                               default:
+                                       p--;
+                                       break;
+                               }
+                       }
+                       putchar(c);
+               }
+               if (*ap)
+                       putchar(' ');
+       }
+       if (! nflag)
+               putchar('\n');
+       return 0;
+}
diff --git a/sh/builtins.def b/sh/builtins.def
new file mode 100644 (file)
index 0000000..1cbeea9
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/sh -
+
+#-
+# Copyright (c) 1991, 1993
+#      The Regents of the University of California.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 4. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#      @(#)builtins.def        8.4 (Berkeley) 5/4/95
+# $FreeBSD$
+
+#
+# This file lists all the builtin commands.  The first column is the name
+# of a C routine.
+# The -j flag specifies that this command is to be excluded from systems
+# without job control.
+# The -h flag specifies that this command is to be excluded from systems
+# based on the NO_HISTORY compile-time symbol.
+# The -s flag specifies that this is a POSIX 'special built-in' command.
+# The rest of the line specifies the command name or names used to run the
+# command.  The entry for bltincmd, which is run when the user does not specify
+# a command, must come first.
+#
+# NOTE: bltincmd must come first!
+
+bltincmd       builtin
+aliascmd       alias
+bgcmd -j       bg
+bindcmd                bind
+breakcmd       -s break -s continue
+cdcmd          cd chdir
+commandcmd     command
+dotcmd         -s .
+echocmd                echo
+evalcmd                -s eval
+execcmd                -s exec
+exitcmd                -s exit
+letcmd         let
+exportcmd      -s export -s readonly
+#exprcmd               expr
+falsecmd       false
+fgcmd -j       fg
+getoptscmd     getopts
+hashcmd                hash
+histcmd -h     fc
+jobidcmd       jobid
+jobscmd                jobs
+killcmd                kill
+localcmd       local
+printfcmd      printf
+pwdcmd         pwd
+readcmd                read
+returncmd      -s return
+setcmd         -s set
+setvarcmd      setvar
+shiftcmd       -s shift
+testcmd                test [
+timescmd       -s times
+trapcmd                -s trap
+truecmd                -s : true
+typecmd                type
+ulimitcmd      ulimit
+umaskcmd       umask
+unaliascmd     unalias
+unsetcmd       -s unset
+waitcmd                wait
+wordexpcmd     wordexp
diff --git a/sh/cd.c b/sh/cd.c
new file mode 100644 (file)
index 0000000..88f03f5
--- /dev/null
+++ b/sh/cd.c
@@ -0,0 +1,421 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)cd.c       8.2 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+
+/*
+ * The cd and pwd commands.
+ */
+
+#include "shell.h"
+#include "var.h"
+#include "nodes.h"     /* for jobs.h */
+#include "jobs.h"
+#include "options.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "exec.h"
+#include "redir.h"
+#include "mystring.h"
+#include "show.h"
+#include "cd.h"
+#include "builtins.h"
+
+static int cdlogical(char *);
+static int cdphysical(char *);
+static int docd(char *, int, int);
+static char *getcomponent(void);
+static char *findcwd(char *);
+static void updatepwd(char *);
+static char *getpwd(void);
+static char *getpwd2(void);
+
+static char *curdir = NULL;    /* current working directory */
+static char *prevdir;          /* previous working directory */
+static char *cdcomppath;
+
+int
+cdcmd(int argc __unused, char **argv __unused)
+{
+       const char *dest;
+       const char *path;
+       char *p;
+       struct stat statb;
+       int ch, phys, print = 0, getcwderr = 0;
+       int rc;
+       int errno1 = ENOENT;
+
+       phys = Pflag;
+       while ((ch = nextopt("eLP")) != '\0') {
+               switch (ch) {
+               case 'e':
+                       getcwderr = 1;
+                       break;
+               case 'L':
+                       phys = 0;
+                       break;
+               case 'P':
+                       phys = 1;
+                       break;
+               }
+       }
+
+       if (*argptr != NULL && argptr[1] != NULL)
+               error("too many arguments");
+
+       if ((dest = *argptr) == NULL && (dest = bltinlookup("HOME", 1)) == NULL)
+               error("HOME not set");
+       if (*dest == '\0')
+               dest = ".";
+       if (dest[0] == '-' && dest[1] == '\0') {
+               dest = prevdir ? prevdir : curdir;
+               if (dest)
+                       print = 1;
+               else
+                       dest = ".";
+       }
+       if (dest[0] == '/' ||
+           (dest[0] == '.' && (dest[1] == '/' || dest[1] == '\0')) ||
+           (dest[0] == '.' && dest[1] == '.' && (dest[2] == '/' || dest[2] == '\0')) ||
+           (path = bltinlookup("CDPATH", 1)) == NULL)
+               path = "";
+       while ((p = padvance(&path, dest)) != NULL) {
+               if (stat(p, &statb) < 0) {
+                       if (errno != ENOENT)
+                               errno1 = errno;
+               } else if (!S_ISDIR(statb.st_mode))
+                       errno1 = ENOTDIR;
+               else {
+                       if (!print) {
+                               /*
+                                * XXX - rethink
+                                */
+                               if (p[0] == '.' && p[1] == '/' && p[2] != '\0')
+                                       print = strcmp(p + 2, dest);
+                               else
+                                       print = strcmp(p, dest);
+                       }
+                       rc = docd(p, print, phys);
+                       if (rc >= 0)
+                               return getcwderr ? rc : 0;
+                       if (errno != ENOENT)
+                               errno1 = errno;
+               }
+       }
+       error("%s: %s", dest, strerror(errno1));
+       /*NOTREACHED*/
+       return 0;
+}
+
+
+/*
+ * Actually change the directory.  In an interactive shell, print the
+ * directory name if "print" is nonzero.
+ */
+static int
+docd(char *dest, int print, int phys)
+{
+       int rc;
+
+       TRACE(("docd(\"%s\", %d, %d) called\n", dest, print, phys));
+
+       /* If logical cd fails, fall back to physical. */
+       if ((phys || (rc = cdlogical(dest)) < 0) && (rc = cdphysical(dest)) < 0)
+               return (-1);
+
+       if (print && iflag && curdir)
+               out1fmt("%s\n", curdir);
+
+       return (rc);
+}
+
+static int
+cdlogical(char *dest)
+{
+       char *p;
+       char *q;
+       char *component;
+       struct stat statb;
+       int first;
+       int badstat;
+
+       /*
+        *  Check each component of the path. If we find a symlink or
+        *  something we can't stat, clear curdir to force a getcwd()
+        *  next time we get the value of the current directory.
+        */
+       badstat = 0;
+       cdcomppath = stsavestr(dest);
+       STARTSTACKSTR(p);
+       if (*dest == '/') {
+               STPUTC('/', p);
+               cdcomppath++;
+       }
+       first = 1;
+       while ((q = getcomponent()) != NULL) {
+               if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0'))
+                       continue;
+               if (! first)
+                       STPUTC('/', p);
+               first = 0;
+               component = q;
+               STPUTS(q, p);
+               if (equal(component, ".."))
+                       continue;
+               STACKSTRNUL(p);
+               if (lstat(stackblock(), &statb) < 0) {
+                       badstat = 1;
+                       break;
+               }
+       }
+
+       INTOFF;
+       if ((p = findcwd(badstat ? NULL : dest)) == NULL || chdir(p) < 0) {
+               INTON;
+               return (-1);
+       }
+       updatepwd(p);
+       INTON;
+       return (0);
+}
+
+static int
+cdphysical(char *dest)
+{
+       char *p;
+       int rc = 0;
+
+       INTOFF;
+       if (chdir(dest) < 0) {
+               INTON;
+               return (-1);
+       }
+       p = findcwd(NULL);
+       if (p == NULL) {
+               warning("warning: failed to get name of current directory");
+               rc = 1;
+       }
+       updatepwd(p);
+       INTON;
+       return (rc);
+}
+
+/*
+ * Get the next component of the path name pointed to by cdcomppath.
+ * This routine overwrites the string pointed to by cdcomppath.
+ */
+static char *
+getcomponent(void)
+{
+       char *p;
+       char *start;
+
+       if ((p = cdcomppath) == NULL)
+               return NULL;
+       start = cdcomppath;
+       while (*p != '/' && *p != '\0')
+               p++;
+       if (*p == '\0') {
+               cdcomppath = NULL;
+       } else {
+               *p++ = '\0';
+               cdcomppath = p;
+       }
+       return start;
+}
+
+
+static char *
+findcwd(char *dir)
+{
+       char *new;
+       char *p;
+
+       /*
+        * If our argument is NULL, we don't know the current directory
+        * any more because we traversed a symbolic link or something
+        * we couldn't stat().
+        */
+       if (dir == NULL || curdir == NULL)
+               return getpwd2();
+       cdcomppath = stsavestr(dir);
+       STARTSTACKSTR(new);
+       if (*dir != '/') {
+               STPUTS(curdir, new);
+               if (STTOPC(new) == '/')
+                       STUNPUTC(new);
+       }
+       while ((p = getcomponent()) != NULL) {
+               if (equal(p, "..")) {
+                       while (new > stackblock() && (STUNPUTC(new), *new) != '/');
+               } else if (*p != '\0' && ! equal(p, ".")) {
+                       STPUTC('/', new);
+                       STPUTS(p, new);
+               }
+       }
+       if (new == stackblock())
+               STPUTC('/', new);
+       STACKSTRNUL(new);
+       return stackblock();
+}
+
+/*
+ * Update curdir (the name of the current directory) in response to a
+ * cd command.  We also call hashcd to let the routines in exec.c know
+ * that the current directory has changed.
+ */
+static void
+updatepwd(char *dir)
+{
+       hashcd();                               /* update command hash table */
+
+       if (prevdir)
+               ckfree(prevdir);
+       prevdir = curdir;
+       curdir = dir ? savestr(dir) : NULL;
+       setvar("PWD", curdir, VEXPORT);
+       setvar("OLDPWD", prevdir, VEXPORT);
+}
+
+int
+pwdcmd(int argc __unused, char **argv __unused)
+{
+       char *p;
+       int ch, phys;
+
+       phys = Pflag;
+       while ((ch = nextopt("LP")) != '\0') {
+               switch (ch) {
+               case 'L':
+                       phys = 0;
+                       break;
+               case 'P':
+                       phys = 1;
+                       break;
+               }
+       }
+
+       if (*argptr != NULL)
+               error("too many arguments");
+
+       if (!phys && getpwd()) {
+               out1str(curdir);
+               out1c('\n');
+       } else {
+               if ((p = getpwd2()) == NULL)
+                       error(".: %s", strerror(errno));
+               out1str(p);
+               out1c('\n');
+       }
+
+       return 0;
+}
+
+/*
+ * Get the current directory and cache the result in curdir.
+ */
+static char *
+getpwd(void)
+{
+       char *p;
+
+       if (curdir)
+               return curdir;
+
+       p = getpwd2();
+       if (p != NULL)
+               curdir = savestr(p);
+
+       return curdir;
+}
+
+#define MAXPWD 256
+
+/*
+ * Return the current directory.
+ */
+static char *
+getpwd2(void)
+{
+       char *pwd;
+       int i;
+
+       for (i = MAXPWD;; i *= 2) {
+               pwd = stalloc(i);
+               if (getcwd(pwd, i) != NULL)
+                       return pwd;
+               stunalloc(pwd);
+               if (errno != ERANGE)
+                       break;
+       }
+
+       return NULL;
+}
+
+/*
+ * Initialize PWD in a new shell.
+ * If the shell is interactive, we need to warn if this fails.
+ */
+void
+pwd_init(int warn)
+{
+       char *pwd;
+       struct stat stdot, stpwd;
+
+       pwd = lookupvar("PWD");
+       if (pwd && *pwd == '/' && stat(".", &stdot) != -1 &&
+           stat(pwd, &stpwd) != -1 &&
+           stdot.st_dev == stpwd.st_dev &&
+           stdot.st_ino == stpwd.st_ino) {
+               if (curdir)
+                       ckfree(curdir);
+               curdir = savestr(pwd);
+       }
+       if (getpwd() == NULL && warn)
+               out2fmt_flush("sh: cannot determine working directory\n");
+       setvar("PWD", curdir, VEXPORT);
+}
diff --git a/sh/cd.h b/sh/cd.h
new file mode 100644 (file)
index 0000000..91fbc2b
--- /dev/null
+++ b/sh/cd.h
@@ -0,0 +1,32 @@
+/*-
+ * Copyright (c) 1995
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+void    pwd_init(int);
diff --git a/sh/error.c b/sh/error.c
new file mode 100644 (file)
index 0000000..6057226
--- /dev/null
@@ -0,0 +1,199 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)error.c    8.2 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Errors and exceptions.
+ */
+
+#include "shell.h"
+#include "eval.h"
+#include "main.h"
+#include "options.h"
+#include "output.h"
+#include "error.h"
+#include "nodes.h" /* show.h needs nodes.h */
+#include "show.h"
+#include "trap.h"
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+
+/*
+ * Code to handle exceptions in C.
+ */
+
+struct jmploc *handler;
+volatile sig_atomic_t exception;
+volatile sig_atomic_t suppressint;
+volatile sig_atomic_t intpending;
+
+
+static void exverror(int, const char *, va_list) __printf0like(2, 0) __dead2;
+
+/*
+ * Called to raise an exception.  Since C doesn't include exceptions, we
+ * just do a longjmp to the exception handler.  The type of exception is
+ * stored in the global variable "exception".
+ *
+ * Interrupts are disabled; they should be reenabled when the exception is
+ * caught.
+ */
+
+void
+exraise(int e)
+{
+       INTOFF;
+       if (handler == NULL)
+               abort();
+       exception = e;
+       longjmp(handler->loc, 1);
+}
+
+
+/*
+ * Called from trap.c when a SIGINT is received and not suppressed, or when
+ * an interrupt is pending and interrupts are re-enabled using INTON.
+ * (If the user specifies that SIGINT is to be trapped or ignored using the
+ * trap builtin, then this routine is not called.)  Suppressint is nonzero
+ * when interrupts are held using the INTOFF macro.  If SIGINTs are not
+ * suppressed and the shell is not a root shell, then we want to be
+ * terminated if we get here, as if we were terminated directly by a SIGINT.
+ * Arrange for this here.
+ */
+
+void
+onint(void)
+{
+       sigset_t sigs;
+
+       intpending = 0;
+       sigemptyset(&sigs);
+       sigprocmask(SIG_SETMASK, &sigs, NULL);
+
+       /*
+        * This doesn't seem to be needed, since main() emits a newline.
+        */
+#if 0
+       if (tcgetpgrp(0) == getpid())
+               write(STDERR_FILENO, "\n", 1);
+#endif
+       if (rootshell && iflag)
+               exraise(EXINT);
+       else {
+               signal(SIGINT, SIG_DFL);
+               kill(getpid(), SIGINT);
+               _exit(128 + SIGINT);
+       }
+}
+
+
+static void
+vwarning(const char *msg, va_list ap)
+{
+       if (commandname)
+               outfmt(out2, "%s: ", commandname);
+       else if (arg0)
+               outfmt(out2, "%s: ", arg0);
+       doformat(out2, msg, ap);
+       out2fmt_flush("\n");
+}
+
+
+void
+warning(const char *msg, ...)
+{
+       va_list ap;
+       va_start(ap, msg);
+       vwarning(msg, ap);
+       va_end(ap);
+}
+
+
+/*
+ * Exverror is called to raise the error exception.  If the first argument
+ * is not NULL then error prints an error message using printf style
+ * formatting.  It then raises the error exception.
+ */
+static void
+exverror(int cond, const char *msg, va_list ap)
+{
+       /*
+        * An interrupt trumps an error.  Certain places catch error
+        * exceptions or transform them to a plain nonzero exit code
+        * in child processes, and if an error exception can be handled,
+        * an interrupt can be handled as well.
+        *
+        * exraise() will disable interrupts for the exception handler.
+        */
+       FORCEINTON;
+
+#ifdef DEBUG
+       if (msg)
+               TRACE(("exverror(%d, \"%s\") pid=%d\n", cond, msg, getpid()));
+       else
+               TRACE(("exverror(%d, NULL) pid=%d\n", cond, getpid()));
+#endif
+       if (msg)
+               vwarning(msg, ap);
+       flushall();
+       exraise(cond);
+}
+
+
+void
+error(const char *msg, ...)
+{
+       va_list ap;
+       va_start(ap, msg);
+       exverror(EXERROR, msg, ap);
+       va_end(ap);
+}
+
+
+void
+exerror(int cond, const char *msg, ...)
+{
+       va_list ap;
+       va_start(ap, msg);
+       exverror(cond, msg, ap);
+       va_end(ap);
+}
diff --git a/sh/error.h b/sh/error.h
new file mode 100644 (file)
index 0000000..a60b1fa
--- /dev/null
@@ -0,0 +1,95 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)error.h     8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+/*
+ * We enclose jmp_buf in a structure so that we can declare pointers to
+ * jump locations.  The global variable handler contains the location to
+ * jump to when an exception occurs, and the global variable exception
+ * contains a code identifying the exception.  To implement nested
+ * exception handlers, the user should save the value of handler on entry
+ * to an inner scope, set handler to point to a jmploc structure for the
+ * inner scope, and restore handler on exit from the scope.
+ */
+
+#include <setjmp.h>
+#include <signal.h>
+
+struct jmploc {
+       jmp_buf loc;
+};
+
+extern struct jmploc *handler;
+extern volatile sig_atomic_t exception;
+
+/* exceptions */
+#define EXINT 0                /* SIGINT received */
+#define EXERROR 1      /* a generic error */
+#define EXEXEC 2       /* command execution failed */
+#define EXEXIT 3       /* call exitshell(exitstatus) */
+
+
+/*
+ * These macros allow the user to suspend the handling of interrupt signals
+ * over a period of time.  This is similar to SIGHOLD to or sigblock, but
+ * much more efficient and portable.  (But hacking the kernel is so much
+ * more fun than worrying about efficiency and portability. :-))
+ */
+
+extern volatile sig_atomic_t suppressint;
+extern volatile sig_atomic_t intpending;
+
+#define INTOFF suppressint++
+#define INTON { if (--suppressint == 0 && intpending) onint(); }
+#define is_int_on() suppressint
+#define SETINTON(s) suppressint = (s)
+#define FORCEINTON {suppressint = 0; if (intpending) onint();}
+#define SET_PENDING_INT intpending = 1
+#define CLEAR_PENDING_INT intpending = 0
+#define int_pending() intpending
+
+void exraise(int) __dead2;
+void onint(void) __dead2;
+void warning(const char *, ...) __printflike(1, 2);
+void error(const char *, ...) __printf0like(1, 2) __dead2;
+void exerror(int, const char *, ...) __printf0like(2, 3) __dead2;
+
+
+/*
+ * BSD setjmp saves the signal mask, which violates ANSI C and takes time,
+ * so we use _setjmp instead.
+ */
+
+#define setjmp(jmploc) _setjmp(jmploc)
+#define longjmp(jmploc, val)   _longjmp(jmploc, val)
diff --git a/sh/eval.c b/sh/eval.c
new file mode 100644 (file)
index 0000000..51206d9
--- /dev/null
+++ b/sh/eval.c
@@ -0,0 +1,1382 @@
+/*-
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)eval.c     8.9 (Berkeley) 6/8/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <paths.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/resource.h>
+#include <sys/wait.h> /* For WIFSIGNALED(status) */
+#include <errno.h>
+
+/*
+ * Evaluate a command.
+ */
+
+#include "shell.h"
+#include "nodes.h"
+#include "syntax.h"
+#include "expand.h"
+#include "parser.h"
+#include "jobs.h"
+#include "eval.h"
+#include "builtins.h"
+#include "options.h"
+#include "exec.h"
+#include "redir.h"
+#include "input.h"
+#include "output.h"
+#include "trap.h"
+#include "var.h"
+#include "memalloc.h"
+#include "error.h"
+#include "show.h"
+#include "mystring.h"
+#ifndef NO_HISTORY
+#include "myhistedit.h"
+#endif
+
+
+int evalskip;                  /* set if we are skipping commands */
+int skipcount;                 /* number of levels to skip */
+static int loopnest;           /* current loop nesting level */
+int funcnest;                  /* depth of function calls */
+static int builtin_flags;      /* evalcommand flags for builtins */
+
+
+char *commandname;
+struct strlist *cmdenviron;
+int exitstatus;                        /* exit status of last command */
+int oexitstatus;               /* saved exit status */
+
+
+static void evalloop(union node *, int);
+static void evalfor(union node *, int);
+static union node *evalcase(union node *);
+static void evalsubshell(union node *, int);
+static void evalredir(union node *, int);
+static void exphere(union node *, struct arglist *);
+static void expredir(union node *);
+static void evalpipe(union node *);
+static int is_valid_fast_cmdsubst(union node *n);
+static void evalcommand(union node *, int, struct backcmd *);
+static void prehash(union node *);
+
+
+/*
+ * Called to reset things after an exception.
+ */
+
+void
+reseteval(void)
+{
+       evalskip = 0;
+       loopnest = 0;
+}
+
+
+/*
+ * The eval command.
+ */
+
+int
+evalcmd(int argc, char **argv)
+{
+        char *p;
+        char *concat;
+        char **ap;
+
+        if (argc > 1) {
+                p = argv[1];
+                if (argc > 2) {
+                        STARTSTACKSTR(concat);
+                        ap = argv + 2;
+                        for (;;) {
+                                STPUTS(p, concat);
+                                if ((p = *ap++) == NULL)
+                                        break;
+                                STPUTC(' ', concat);
+                        }
+                        STPUTC('\0', concat);
+                        p = grabstackstr(concat);
+                }
+                evalstring(p, builtin_flags);
+        } else
+                exitstatus = 0;
+        return exitstatus;
+}
+
+
+/*
+ * Execute a command or commands contained in a string.
+ */
+
+void
+evalstring(const char *s, int flags)
+{
+       union node *n;
+       struct stackmark smark;
+       int flags_exit;
+       int any;
+
+       flags_exit = flags & EV_EXIT;
+       flags &= ~EV_EXIT;
+       any = 0;
+       setstackmark(&smark);
+       setinputstring(s, 1);
+       while ((n = parsecmd(0)) != NEOF) {
+               if (n != NULL && !nflag) {
+                       if (flags_exit && preadateof())
+                               evaltree(n, flags | EV_EXIT);
+                       else
+                               evaltree(n, flags);
+                       any = 1;
+                       if (evalskip)
+                               break;
+               }
+               popstackmark(&smark);
+               setstackmark(&smark);
+       }
+       popfile();
+       popstackmark(&smark);
+       if (!any)
+               exitstatus = 0;
+       if (flags_exit)
+               exraise(EXEXIT);
+}
+
+
+/*
+ * Evaluate a parse tree.  The value is left in the global variable
+ * exitstatus.
+ */
+
+void
+evaltree(union node *n, int flags)
+{
+       int do_etest;
+       union node *next;
+       struct stackmark smark;
+
+       setstackmark(&smark);
+       do_etest = 0;
+       if (n == NULL) {
+               TRACE(("evaltree(NULL) called\n"));
+               exitstatus = 0;
+               goto out;
+       }
+       do {
+               next = NULL;
+#ifndef NO_HISTORY
+               displayhist = 1;        /* show history substitutions done with fc */
+#endif
+               TRACE(("evaltree(%p: %d) called\n", (void *)n, n->type));
+               switch (n->type) {
+               case NSEMI:
+                       evaltree(n->nbinary.ch1, flags & ~EV_EXIT);
+                       if (evalskip)
+                               goto out;
+                       next = n->nbinary.ch2;
+                       break;
+               case NAND:
+                       evaltree(n->nbinary.ch1, EV_TESTED);
+                       if (evalskip || exitstatus != 0) {
+                               goto out;
+                       }
+                       next = n->nbinary.ch2;
+                       break;
+               case NOR:
+                       evaltree(n->nbinary.ch1, EV_TESTED);
+                       if (evalskip || exitstatus == 0)
+                               goto out;
+                       next = n->nbinary.ch2;
+                       break;
+               case NREDIR:
+                       evalredir(n, flags);
+                       break;
+               case NSUBSHELL:
+                       evalsubshell(n, flags);
+                       do_etest = !(flags & EV_TESTED);
+                       break;
+               case NBACKGND:
+                       evalsubshell(n, flags);
+                       break;
+               case NIF: {
+                       evaltree(n->nif.test, EV_TESTED);
+                       if (evalskip)
+                               goto out;
+                       if (exitstatus == 0)
+                               next = n->nif.ifpart;
+                       else if (n->nif.elsepart)
+                               next = n->nif.elsepart;
+                       else
+                               exitstatus = 0;
+                       break;
+               }
+               case NWHILE:
+               case NUNTIL:
+                       evalloop(n, flags & ~EV_EXIT);
+                       break;
+               case NFOR:
+                       evalfor(n, flags & ~EV_EXIT);
+                       break;
+               case NCASE:
+                       next = evalcase(n);
+                       break;
+               case NCLIST:
+                       next = n->nclist.body;
+                       break;
+               case NCLISTFALLTHRU:
+                       if (n->nclist.body) {
+                               evaltree(n->nclist.body, flags & ~EV_EXIT);
+                               if (evalskip)
+                                       goto out;
+                       }
+                       next = n->nclist.next;
+                       break;
+               case NDEFUN:
+                       defun(n->narg.text, n->narg.next);
+                       exitstatus = 0;
+                       break;
+               case NNOT:
+                       evaltree(n->nnot.com, EV_TESTED);
+                       if (evalskip)
+                               goto out;
+                       exitstatus = !exitstatus;
+                       break;
+
+               case NPIPE:
+                       evalpipe(n);
+                       do_etest = !(flags & EV_TESTED);
+                       break;
+               case NCMD:
+                       evalcommand(n, flags, (struct backcmd *)NULL);
+                       do_etest = !(flags & EV_TESTED);
+                       break;
+               default:
+                       out1fmt("Node type = %d\n", n->type);
+                       flushout(&output);
+                       break;
+               }
+               n = next;
+               popstackmark(&smark);
+               setstackmark(&smark);
+       } while (n != NULL);
+out:
+       popstackmark(&smark);
+       if (pendingsig)
+               dotrap();
+       if (eflag && exitstatus != 0 && do_etest)
+               exitshell(exitstatus);
+       if (flags & EV_EXIT)
+               exraise(EXEXIT);
+}
+
+
+static void
+evalloop(union node *n, int flags)
+{
+       int status;
+
+       loopnest++;
+       status = 0;
+       for (;;) {
+               if (!evalskip)
+                       evaltree(n->nbinary.ch1, EV_TESTED);
+               if (evalskip) {
+                       if (evalskip == SKIPCONT && --skipcount <= 0) {
+                               evalskip = 0;
+                               continue;
+                       }
+                       if (evalskip == SKIPBREAK && --skipcount <= 0)
+                               evalskip = 0;
+                       if (evalskip == SKIPRETURN)
+                               status = exitstatus;
+                       break;
+               }
+               if (n->type == NWHILE) {
+                       if (exitstatus != 0)
+                               break;
+               } else {
+                       if (exitstatus == 0)
+                               break;
+               }
+               evaltree(n->nbinary.ch2, flags);
+               status = exitstatus;
+       }
+       loopnest--;
+       exitstatus = status;
+}
+
+
+
+static void
+evalfor(union node *n, int flags)
+{
+       struct arglist arglist;
+       union node *argp;
+       struct strlist *sp;
+       int status;
+
+       arglist.lastp = &arglist.list;
+       for (argp = n->nfor.args ; argp ; argp = argp->narg.next) {
+               oexitstatus = exitstatus;
+               expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+       }
+       *arglist.lastp = NULL;
+
+       loopnest++;
+       status = 0;
+       for (sp = arglist.list ; sp ; sp = sp->next) {
+               setvar(n->nfor.var, sp->text, 0);
+               evaltree(n->nfor.body, flags);
+               status = exitstatus;
+               if (evalskip) {
+                       if (evalskip == SKIPCONT && --skipcount <= 0) {
+                               evalskip = 0;
+                               continue;
+                       }
+                       if (evalskip == SKIPBREAK && --skipcount <= 0)
+                               evalskip = 0;
+                       break;
+               }
+       }
+       loopnest--;
+       exitstatus = status;
+}
+
+
+/*
+ * Evaluate a case statement, returning the selected tree.
+ *
+ * The exit status needs care to get right.
+ */
+
+static union node *
+evalcase(union node *n)
+{
+       union node *cp;
+       union node *patp;
+       struct arglist arglist;
+
+       arglist.lastp = &arglist.list;
+       oexitstatus = exitstatus;
+       expandarg(n->ncase.expr, &arglist, EXP_TILDE);
+       for (cp = n->ncase.cases ; cp ; cp = cp->nclist.next) {
+               for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) {
+                       if (casematch(patp, arglist.list->text)) {
+                               while (cp->nclist.next &&
+                                   cp->type == NCLISTFALLTHRU &&
+                                   cp->nclist.body == NULL)
+                                       cp = cp->nclist.next;
+                               if (cp->nclist.next &&
+                                   cp->type == NCLISTFALLTHRU)
+                                       return (cp);
+                               if (cp->nclist.body == NULL)
+                                       exitstatus = 0;
+                               return (cp->nclist.body);
+                       }
+               }
+       }
+       exitstatus = 0;
+       return (NULL);
+}
+
+
+
+/*
+ * Kick off a subshell to evaluate a tree.
+ */
+
+static void
+evalsubshell(union node *n, int flags)
+{
+       struct job *jp;
+       int backgnd = (n->type == NBACKGND);
+
+       oexitstatus = exitstatus;
+       expredir(n->nredir.redirect);
+       if ((!backgnd && flags & EV_EXIT && !have_traps()) ||
+                       forkshell(jp = makejob(n, 1), n, backgnd) == 0) {
+               if (backgnd)
+                       flags &=~ EV_TESTED;
+               redirect(n->nredir.redirect, 0);
+               evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */
+       } else if (! backgnd) {
+               INTOFF;
+               exitstatus = waitforjob(jp, (int *)NULL);
+               INTON;
+       } else
+               exitstatus = 0;
+}
+
+
+/*
+ * Evaluate a redirected compound command.
+ */
+
+static void
+evalredir(union node *n, int flags)
+{
+       struct jmploc jmploc;
+       struct jmploc *savehandler;
+       volatile int in_redirect = 1;
+
+       oexitstatus = exitstatus;
+       expredir(n->nredir.redirect);
+       savehandler = handler;
+       if (setjmp(jmploc.loc)) {
+               int e;
+
+               handler = savehandler;
+               e = exception;
+               popredir();
+               if (e == EXERROR || e == EXEXEC) {
+                       if (in_redirect) {
+                               exitstatus = 2;
+                               return;
+                       }
+               }
+               longjmp(handler->loc, 1);
+       } else {
+               INTOFF;
+               handler = &jmploc;
+               redirect(n->nredir.redirect, REDIR_PUSH);
+               in_redirect = 0;
+               INTON;
+               evaltree(n->nredir.n, flags);
+       }
+       INTOFF;
+       handler = savehandler;
+       popredir();
+       INTON;
+}
+
+
+static void
+exphere(union node *redir, struct arglist *fn)
+{
+       struct jmploc jmploc;
+       struct jmploc *savehandler;
+       struct localvar *savelocalvars;
+       int need_longjmp = 0;
+
+       redir->nhere.expdoc = "";
+       savelocalvars = localvars;
+       localvars = NULL;
+       forcelocal++;
+       savehandler = handler;
+       if (setjmp(jmploc.loc))
+               need_longjmp = exception != EXERROR && exception != EXEXEC;
+       else {
+               handler = &jmploc;
+               expandarg(redir->nhere.doc, fn, 0);
+               redir->nhere.expdoc = fn->list->text;
+               INTOFF;
+       }
+       handler = savehandler;
+       forcelocal--;
+       poplocalvars();
+       localvars = savelocalvars;
+       if (need_longjmp)
+               longjmp(handler->loc, 1);
+       INTON;
+}
+
+
+/*
+ * Compute the names of the files in a redirection list.
+ */
+
+static void
+expredir(union node *n)
+{
+       union node *redir;
+
+       for (redir = n ; redir ; redir = redir->nfile.next) {
+               struct arglist fn;
+               fn.lastp = &fn.list;
+               switch (redir->type) {
+               case NFROM:
+               case NTO:
+               case NFROMTO:
+               case NAPPEND:
+               case NCLOBBER:
+                       expandarg(redir->nfile.fname, &fn, EXP_TILDE);
+                       redir->nfile.expfname = fn.list->text;
+                       break;
+               case NFROMFD:
+               case NTOFD:
+                       if (redir->ndup.vname) {
+                               expandarg(redir->ndup.vname, &fn, EXP_TILDE);
+                               fixredir(redir, fn.list->text, 1);
+                       }
+                       break;
+               case NXHERE:
+                       exphere(redir, &fn);
+                       break;
+               }
+       }
+}
+
+
+
+/*
+ * Evaluate a pipeline.  All the processes in the pipeline are children
+ * of the process creating the pipeline.  (This differs from some versions
+ * of the shell, which make the last process in a pipeline the parent
+ * of all the rest.)
+ */
+
+static void
+evalpipe(union node *n)
+{
+       struct job *jp;
+       struct nodelist *lp;
+       int pipelen;
+       int prevfd;
+       int pip[2];
+
+       TRACE(("evalpipe(%p) called\n", (void *)n));
+       pipelen = 0;
+       for (lp = n->npipe.cmdlist ; lp ; lp = lp->next)
+               pipelen++;
+       INTOFF;
+       jp = makejob(n, pipelen);
+       prevfd = -1;
+       for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+               prehash(lp->n);
+               pip[1] = -1;
+               if (lp->next) {
+                       if (pipe(pip) < 0) {
+                               if (prevfd >= 0)
+                                       close(prevfd);
+                               error("Pipe call failed: %s", strerror(errno));
+                       }
+               }
+               if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) {
+                       INTON;
+                       if (prevfd > 0) {
+                               dup2(prevfd, 0);
+                               close(prevfd);
+                       }
+                       if (pip[1] >= 0) {
+                               if (!(prevfd >= 0 && pip[0] == 0))
+                                       close(pip[0]);
+                               if (pip[1] != 1) {
+                                       dup2(pip[1], 1);
+                                       close(pip[1]);
+                               }
+                       }
+                       evaltree(lp->n, EV_EXIT);
+               }
+               if (prevfd >= 0)
+                       close(prevfd);
+               prevfd = pip[0];
+               if (pip[1] != -1)
+                       close(pip[1]);
+       }
+       INTON;
+       if (n->npipe.backgnd == 0) {
+               INTOFF;
+               exitstatus = waitforjob(jp, (int *)NULL);
+               TRACE(("evalpipe:  job done exit status %d\n", exitstatus));
+               INTON;
+       } else
+               exitstatus = 0;
+}
+
+
+
+static int
+is_valid_fast_cmdsubst(union node *n)
+{
+
+       return (n->type == NCMD);
+}
+
+/*
+ * Execute a command inside back quotes.  If it's a builtin command, we
+ * want to save its output in a block obtained from malloc.  Otherwise
+ * we fork off a subprocess and get the output of the command via a pipe.
+ * Should be called with interrupts off.
+ */
+
+void
+evalbackcmd(union node *n, struct backcmd *result)
+{
+       int pip[2];
+       struct job *jp;
+       struct stackmark smark;
+       struct jmploc jmploc;
+       struct jmploc *savehandler;
+       struct localvar *savelocalvars;
+
+       result->fd = -1;
+       result->buf = NULL;
+       result->nleft = 0;
+       result->jp = NULL;
+       if (n == NULL) {
+               exitstatus = 0;
+               return;
+       }
+       setstackmark(&smark);
+       exitstatus = oexitstatus;
+       if (is_valid_fast_cmdsubst(n)) {
+               savelocalvars = localvars;
+               localvars = NULL;
+               forcelocal++;
+               savehandler = handler;
+               if (setjmp(jmploc.loc)) {
+                       if (exception == EXERROR || exception == EXEXEC)
+                               exitstatus = 2;
+                       else if (exception != 0) {
+                               handler = savehandler;
+                               forcelocal--;
+                               poplocalvars();
+                               localvars = savelocalvars;
+                               longjmp(handler->loc, 1);
+                       }
+               } else {
+                       handler = &jmploc;
+                       evalcommand(n, EV_BACKCMD, result);
+               }
+               handler = savehandler;
+               forcelocal--;
+               poplocalvars();
+               localvars = savelocalvars;
+       } else {
+               if (pipe(pip) < 0)
+                       error("Pipe call failed: %s", strerror(errno));
+               jp = makejob(n, 1);
+               if (forkshell(jp, n, FORK_NOJOB) == 0) {
+                       FORCEINTON;
+                       close(pip[0]);
+                       if (pip[1] != 1) {
+                               dup2(pip[1], 1);
+                               close(pip[1]);
+                       }
+                       evaltree(n, EV_EXIT);
+               }
+               close(pip[1]);
+               result->fd = pip[0];
+               result->jp = jp;
+       }
+       popstackmark(&smark);
+       TRACE(("evalbackcmd done: fd=%d buf=%p nleft=%d jp=%p\n",
+               result->fd, result->buf, result->nleft, result->jp));
+}
+
+static int
+mustexpandto(const char *argtext, const char *mask)
+{
+       for (;;) {
+               if (*argtext == CTLQUOTEMARK || *argtext == CTLQUOTEEND) {
+                       argtext++;
+                       continue;
+               }
+               if (*argtext == CTLESC)
+                       argtext++;
+               else if (BASESYNTAX[(int)*argtext] == CCTL)
+                       return (0);
+               if (*argtext != *mask)
+                       return (0);
+               if (*argtext == '\0')
+                       return (1);
+               argtext++;
+               mask++;
+       }
+}
+
+static int
+isdeclarationcmd(struct narg *arg)
+{
+       int have_command = 0;
+
+       if (arg == NULL)
+               return (0);
+       while (mustexpandto(arg->text, "command")) {
+               have_command = 1;
+               arg = &arg->next->narg;
+               if (arg == NULL)
+                       return (0);
+               /*
+                * To also allow "command -p" and "command --" as part of
+                * a declaration command, add code here.
+                * We do not do this, as ksh does not do it either and it
+                * is not required by POSIX.
+                */
+       }
+       return (mustexpandto(arg->text, "export") ||
+           mustexpandto(arg->text, "readonly") ||
+           (mustexpandto(arg->text, "local") &&
+               (have_command || !isfunc("local"))));
+}
+
+static void
+xtracecommand(struct arglist *varlist, struct arglist *arglist)
+{
+       struct strlist *sp;
+       char sep = 0;
+       const char *p, *ps4;
+
+       ps4 = expandstr(ps4val());
+       out2str(ps4 != NULL ? ps4 : ps4val());
+       for (sp = varlist->list ; sp ; sp = sp->next) {
+               if (sep != 0)
+                       out2c(' ');
+               p = strchr(sp->text, '=');
+               if (p != NULL) {
+                       p++;
+                       outbin(sp->text, p - sp->text, out2);
+                       out2qstr(p);
+               } else
+                       out2qstr(sp->text);
+               sep = ' ';
+       }
+       for (sp = arglist->list ; sp ; sp = sp->next) {
+               if (sep != 0)
+                       out2c(' ');
+               out2qstr(sp->text);
+               sep = ' ';
+       }
+       out2c('\n');
+       flushout(&errout);
+}
+
+/*
+ * Check if a builtin can safely be executed in the same process,
+ * even though it should be in a subshell (command substitution).
+ * Note that jobid, jobs, times and trap can show information not
+ * available in a child process; this is deliberate.
+ * The arguments should already have been expanded.
+ */
+static int
+safe_builtin(int idx, int argc, char **argv)
+{
+       if (idx == BLTINCMD || idx == COMMANDCMD || idx == ECHOCMD ||
+           idx == FALSECMD || idx == JOBIDCMD || idx == JOBSCMD ||
+           idx == KILLCMD || idx == PRINTFCMD || idx == PWDCMD ||
+           idx == TESTCMD || idx == TIMESCMD || idx == TRUECMD ||
+           idx == TYPECMD)
+               return (1);
+       if (idx == EXPORTCMD || idx == TRAPCMD || idx == ULIMITCMD ||
+           idx == UMASKCMD)
+               return (argc <= 1 || (argc == 2 && argv[1][0] == '-'));
+       if (idx == SETCMD)
+               return (argc <= 1 || (argc == 2 && (argv[1][0] == '-' ||
+                   argv[1][0] == '+') && argv[1][1] == 'o' &&
+                   argv[1][2] == '\0'));
+       return (0);
+}
+
+/*
+ * Execute a simple command.
+ * Note: This may or may not return if (flags & EV_EXIT).
+ */
+
+static void
+evalcommand(union node *cmd, int flags, struct backcmd *backcmd)
+{
+       union node *argp;
+       struct arglist arglist;
+       struct arglist varlist;
+       char **argv;
+       int argc;
+       char **envp;
+       int varflag;
+       struct strlist *sp;
+       int mode;
+       int pip[2];
+       struct cmdentry cmdentry;
+       struct job *jp;
+       struct jmploc jmploc;
+       struct jmploc *savehandler;
+       char *savecmdname;
+       struct shparam saveparam;
+       struct localvar *savelocalvars;
+       struct parsefile *savetopfile;
+       volatile int e;
+       char *lastarg;
+       int realstatus;
+       int do_clearcmdentry;
+       const char *path = pathval();
+
+       /* First expand the arguments. */
+       TRACE(("evalcommand(%p, %d) called\n", (void *)cmd, flags));
+       arglist.lastp = &arglist.list;
+       varlist.lastp = &varlist.list;
+       varflag = 1;
+       jp = NULL;
+       do_clearcmdentry = 0;
+       oexitstatus = exitstatus;
+       exitstatus = 0;
+       for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) {
+               if (varflag && isassignment(argp->narg.text)) {
+                       expandarg(argp, varflag == 1 ? &varlist : &arglist,
+                           EXP_VARTILDE);
+                       continue;
+               } else if (varflag == 1)
+                       varflag = isdeclarationcmd(&argp->narg) ? 2 : 0;
+               expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+       }
+       *arglist.lastp = NULL;
+       *varlist.lastp = NULL;
+       expredir(cmd->ncmd.redirect);
+       argc = 0;
+       for (sp = arglist.list ; sp ; sp = sp->next)
+               argc++;
+       /* Add one slot at the beginning for tryexec(). */
+       argv = stalloc(sizeof (char *) * (argc + 2));
+       argv++;
+
+       for (sp = arglist.list ; sp ; sp = sp->next) {
+               TRACE(("evalcommand arg: %s\n", sp->text));
+               *argv++ = sp->text;
+       }
+       *argv = NULL;
+       lastarg = NULL;
+       if (iflag && funcnest == 0 && argc > 0)
+               lastarg = argv[-1];
+       argv -= argc;
+
+       /* Print the command if xflag is set. */
+       if (xflag)
+               xtracecommand(&varlist, &arglist);
+
+       /* Now locate the command. */
+       if (argc == 0) {
+               /* Variable assignment(s) without command */
+               cmdentry.cmdtype = CMDBUILTIN;
+               cmdentry.u.index = BLTINCMD;
+               cmdentry.special = 0;
+       } else {
+               static const char PATH[] = "PATH=";
+               int cmd_flags = 0, bltinonly = 0;
+
+               /*
+                * Modify the command lookup path, if a PATH= assignment
+                * is present
+                */
+               for (sp = varlist.list ; sp ; sp = sp->next)
+                       if (strncmp(sp->text, PATH, sizeof(PATH) - 1) == 0) {
+                               path = sp->text + sizeof(PATH) - 1;
+                               /*
+                                * On `PATH=... command`, we need to make
+                                * sure that the command isn't using the
+                                * non-updated hash table of the outer PATH
+                                * setting and we need to make sure that
+                                * the hash table isn't filled with items
+                                * from the temporary setting.
+                                *
+                                * It would be better to forbit using and
+                                * updating the table while this command
+                                * runs, by the command finding mechanism
+                                * is heavily integrated with hash handling,
+                                * so we just delete the hash before and after
+                                * the command runs. Partly deleting like
+                                * changepatch() does doesn't seem worth the
+                                * bookinging effort, since most such runs add
+                                * directories in front of the new PATH.
+                                */
+                               clearcmdentry();
+                               do_clearcmdentry = 1;
+                       }
+
+               for (;;) {
+                       if (bltinonly) {
+                               cmdentry.u.index = find_builtin(*argv, &cmdentry.special);
+                               if (cmdentry.u.index < 0) {
+                                       cmdentry.u.index = BLTINCMD;
+                                       argv--;
+                                       argc++;
+                                       break;
+                               }
+                       } else
+                               find_command(argv[0], &cmdentry, cmd_flags, path);
+                       /* implement the bltin and command builtins here */
+                       if (cmdentry.cmdtype != CMDBUILTIN)
+                               break;
+                       if (cmdentry.u.index == BLTINCMD) {
+                               if (argc == 1)
+                                       break;
+                               argv++;
+                               argc--;
+                               bltinonly = 1;
+                       } else if (cmdentry.u.index == COMMANDCMD) {
+                               if (argc == 1)
+                                       break;
+                               if (!strcmp(argv[1], "-p")) {
+                                       if (argc == 2)
+                                               break;
+                                       if (argv[2][0] == '-') {
+                                               if (strcmp(argv[2], "--"))
+                                                       break;
+                                               if (argc == 3)
+                                                       break;
+                                               argv += 3;
+                                               argc -= 3;
+                                       } else {
+                                               argv += 2;
+                                               argc -= 2;
+                                       }
+                                       path = _PATH_STDPATH;
+                                       clearcmdentry();
+                                       do_clearcmdentry = 1;
+                               } else if (!strcmp(argv[1], "--")) {
+                                       if (argc == 2)
+                                               break;
+                                       argv += 2;
+                                       argc -= 2;
+                               } else if (argv[1][0] == '-')
+                                       break;
+                               else {
+                                       argv++;
+                                       argc--;
+                               }
+                               cmd_flags |= DO_NOFUNC;
+                               bltinonly = 0;
+                       } else
+                               break;
+               }
+               /*
+                * Special builtins lose their special properties when
+                * called via 'command'.
+                */
+               if (cmd_flags & DO_NOFUNC)
+                       cmdentry.special = 0;
+       }
+
+       /* Fork off a child process if necessary. */
+       if (((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN)
+           && ((flags & EV_EXIT) == 0 || have_traps()))
+        || ((flags & EV_BACKCMD) != 0
+           && (cmdentry.cmdtype != CMDBUILTIN ||
+                !safe_builtin(cmdentry.u.index, argc, argv)))) {
+               jp = makejob(cmd, 1);
+               mode = FORK_FG;
+               if (flags & EV_BACKCMD) {
+                       mode = FORK_NOJOB;
+                       if (pipe(pip) < 0)
+                               error("Pipe call failed: %s", strerror(errno));
+               }
+               if (cmdentry.cmdtype == CMDNORMAL &&
+                   cmd->ncmd.redirect == NULL &&
+                   varlist.list == NULL &&
+                   (mode == FORK_FG || mode == FORK_NOJOB) &&
+                   !disvforkset() && !iflag && !mflag) {
+                       vforkexecshell(jp, argv, environment(), path,
+                           cmdentry.u.index, flags & EV_BACKCMD ? pip : NULL);
+                       goto parent;
+               }
+               if (forkshell(jp, cmd, mode) != 0)
+                       goto parent;    /* at end of routine */
+               if (flags & EV_BACKCMD) {
+                       FORCEINTON;
+                       close(pip[0]);
+                       if (pip[1] != 1) {
+                               dup2(pip[1], 1);
+                               close(pip[1]);
+                       }
+                       flags &= ~EV_BACKCMD;
+               }
+               flags |= EV_EXIT;
+       }
+
+       /* This is the child process if a fork occurred. */
+       /* Execute the command. */
+       if (cmdentry.cmdtype == CMDFUNCTION) {
+#ifdef DEBUG
+               trputs("Shell function:  ");  trargs(argv);
+#endif
+               saveparam = shellparam;
+               shellparam.malloc = 0;
+               shellparam.reset = 1;
+               shellparam.nparam = argc - 1;
+               shellparam.p = argv + 1;
+               shellparam.optp = NULL;
+               shellparam.optnext = NULL;
+               INTOFF;
+               savelocalvars = localvars;
+               localvars = NULL;
+               reffunc(cmdentry.u.func);
+               savehandler = handler;
+               if (setjmp(jmploc.loc)) {
+                       freeparam(&shellparam);
+                       shellparam = saveparam;
+                       popredir();
+                       unreffunc(cmdentry.u.func);
+                       poplocalvars();
+                       localvars = savelocalvars;
+                       funcnest--;
+                       handler = savehandler;
+                       longjmp(handler->loc, 1);
+               }
+               handler = &jmploc;
+               funcnest++;
+               redirect(cmd->ncmd.redirect, REDIR_PUSH);
+               INTON;
+               for (sp = varlist.list ; sp ; sp = sp->next)
+                       mklocal(sp->text);
+               exitstatus = oexitstatus;
+               evaltree(getfuncnode(cmdentry.u.func),
+                   flags & (EV_TESTED | EV_EXIT));
+               INTOFF;
+               unreffunc(cmdentry.u.func);
+               poplocalvars();
+               localvars = savelocalvars;
+               freeparam(&shellparam);
+               shellparam = saveparam;
+               handler = savehandler;
+               funcnest--;
+               popredir();
+               INTON;
+               if (evalskip == SKIPRETURN) {
+                       evalskip = 0;
+                       skipcount = 0;
+               }
+               if (jp)
+                       exitshell(exitstatus);
+       } else if (cmdentry.cmdtype == CMDBUILTIN) {
+#ifdef DEBUG
+               trputs("builtin command:  ");  trargs(argv);
+#endif
+               mode = (cmdentry.u.index == EXECCMD)? 0 : REDIR_PUSH;
+               if (flags == EV_BACKCMD) {
+                       memout.nleft = 0;
+                       memout.nextc = memout.buf;
+                       memout.bufsize = 64;
+                       mode |= REDIR_BACKQ;
+               }
+               savecmdname = commandname;
+               savetopfile = getcurrentfile();
+               cmdenviron = varlist.list;
+               e = -1;
+               savehandler = handler;
+               if (setjmp(jmploc.loc)) {
+                       e = exception;
+                       if (e == EXINT)
+                               exitstatus = SIGINT+128;
+                       else if (e != EXEXIT)
+                               exitstatus = 2;
+                       goto cmddone;
+               }
+               handler = &jmploc;
+               redirect(cmd->ncmd.redirect, mode);
+               outclearerror(out1);
+               /*
+                * If there is no command word, redirection errors should
+                * not be fatal but assignment errors should.
+                */
+               if (argc == 0)
+                       cmdentry.special = 1;
+               listsetvar(cmdenviron, cmdentry.special ? 0 : VNOSET);
+               if (argc > 0)
+                       bltinsetlocale();
+               commandname = argv[0];
+               argptr = argv + 1;
+               nextopt_optptr = NULL;          /* initialize nextopt */
+               builtin_flags = flags;
+               exitstatus = (*builtinfunc[cmdentry.u.index])(argc, argv);
+               flushall();
+               if (outiserror(out1)) {
+                       warning("write error on stdout");
+                       if (exitstatus == 0 || exitstatus == 1)
+                               exitstatus = 2;
+               }
+cmddone:
+               if (argc > 0)
+                       bltinunsetlocale();
+               cmdenviron = NULL;
+               out1 = &output;
+               out2 = &errout;
+               freestdout();
+               handler = savehandler;
+               commandname = savecmdname;
+               if (jp)
+                       exitshell(exitstatus);
+               if (flags == EV_BACKCMD) {
+                       backcmd->buf = memout.buf;
+                       backcmd->nleft = memout.nextc - memout.buf;
+                       memout.buf = NULL;
+               }
+               if (cmdentry.u.index != EXECCMD)
+                       popredir();
+               if (e != -1) {
+                       if ((e != EXERROR && e != EXEXEC)
+                           || cmdentry.special)
+                               exraise(e);
+                       popfilesupto(savetopfile);
+                       if (flags != EV_BACKCMD)
+                               FORCEINTON;
+               }
+       } else {
+#ifdef DEBUG
+               trputs("normal command:  ");  trargs(argv);
+#endif
+               redirect(cmd->ncmd.redirect, 0);
+               for (sp = varlist.list ; sp ; sp = sp->next)
+                       setvareq(sp->text, VEXPORT|VSTACK);
+               envp = environment();
+               shellexec(argv, envp, path, cmdentry.u.index);
+               /*NOTREACHED*/
+       }
+       goto out;
+
+parent:        /* parent process gets here (if we forked) */
+       if (mode == FORK_FG) {  /* argument to fork */
+               INTOFF;
+               exitstatus = waitforjob(jp, &realstatus);
+               INTON;
+               if (iflag && loopnest > 0 && WIFSIGNALED(realstatus)) {
+                       evalskip = SKIPBREAK;
+                       skipcount = loopnest;
+               }
+       } else if (mode == FORK_NOJOB) {
+               backcmd->fd = pip[0];
+               close(pip[1]);
+               backcmd->jp = jp;
+       }
+
+out:
+       if (lastarg)
+               setvar("_", lastarg, 0);
+       if (do_clearcmdentry)
+               clearcmdentry();
+}
+
+
+
+/*
+ * Search for a command.  This is called before we fork so that the
+ * location of the command will be available in the parent as well as
+ * the child.  The check for "goodname" is an overly conservative
+ * check that the name will not be subject to expansion.
+ */
+
+static void
+prehash(union node *n)
+{
+       struct cmdentry entry;
+
+       if (n && n->type == NCMD && n->ncmd.args)
+               if (goodname(n->ncmd.args->narg.text))
+                       find_command(n->ncmd.args->narg.text, &entry, 0,
+                                    pathval());
+}
+
+
+
+/*
+ * Builtin commands.  Builtin commands whose functions are closely
+ * tied to evaluation are implemented here.
+ */
+
+/*
+ * No command given, a bltin command with no arguments, or a bltin command
+ * with an invalid name.
+ */
+
+int
+bltincmd(int argc, char **argv)
+{
+       if (argc > 1) {
+               out2fmt_flush("%s: not found\n", argv[1]);
+               return 127;
+       }
+       /*
+        * Preserve exitstatus of a previous possible redirection
+        * as POSIX mandates
+        */
+       return exitstatus;
+}
+
+
+/*
+ * Handle break and continue commands.  Break, continue, and return are
+ * all handled by setting the evalskip flag.  The evaluation routines
+ * above all check this flag, and if it is set they start skipping
+ * commands rather than executing them.  The variable skipcount is
+ * the number of loops to break/continue, or the number of function
+ * levels to return.  (The latter is always 1.)  It should probably
+ * be an error to break out of more loops than exist, but it isn't
+ * in the standard shell so we don't make it one here.
+ */
+
+int
+breakcmd(int argc, char **argv)
+{
+       long n;
+       char *end;
+
+       if (argc > 1) {
+               /* Allow arbitrarily large numbers. */
+               n = strtol(argv[1], &end, 10);
+               if (!is_digit(argv[1][0]) || *end != '\0')
+                       error("Illegal number: %s", argv[1]);
+       } else
+               n = 1;
+       if (n > loopnest)
+               n = loopnest;
+       if (n > 0) {
+               evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
+               skipcount = n;
+       }
+       return 0;
+}
+
+/*
+ * The `command' command.
+ */
+int
+commandcmd(int argc __unused, char **argv __unused)
+{
+       const char *path;
+       int ch;
+       int cmd = -1;
+
+       path = bltinlookup("PATH", 1);
+
+       while ((ch = nextopt("pvV")) != '\0') {
+               switch (ch) {
+               case 'p':
+                       path = _PATH_STDPATH;
+                       break;
+               case 'v':
+                       cmd = TYPECMD_SMALLV;
+                       break;
+               case 'V':
+                       cmd = TYPECMD_BIGV;
+                       break;
+               }
+       }
+
+       if (cmd != -1) {
+               if (*argptr == NULL || argptr[1] != NULL)
+                       error("wrong number of arguments");
+               return typecmd_impl(2, argptr - 1, cmd, path);
+       }
+       if (*argptr != NULL)
+               error("commandcmd bad call");
+
+       /*
+        * Do nothing successfully if no command was specified;
+        * ksh also does this.
+        */
+       return 0;
+}
+
+
+/*
+ * The return command.
+ */
+
+int
+returncmd(int argc, char **argv)
+{
+       int ret = argc > 1 ? number(argv[1]) : oexitstatus;
+
+       evalskip = SKIPRETURN;
+       skipcount = 1;
+       return ret;
+}
+
+
+int
+falsecmd(int argc __unused, char **argv __unused)
+{
+       return 1;
+}
+
+
+int
+truecmd(int argc __unused, char **argv __unused)
+{
+       return 0;
+}
+
+
+int
+execcmd(int argc, char **argv)
+{
+       /*
+        * Because we have historically not supported any options,
+        * only treat "--" specially.
+        */
+       if (argc > 1 && strcmp(argv[1], "--") == 0)
+               argc--, argv++;
+       if (argc > 1) {
+               struct strlist *sp;
+
+               iflag = 0;              /* exit on error */
+               mflag = 0;
+               optschanged();
+               for (sp = cmdenviron; sp ; sp = sp->next)
+                       setvareq(sp->text, VEXPORT|VSTACK);
+               shellexec(argv + 1, environment(), pathval(), 0);
+
+       }
+       return 0;
+}
+
+
+int
+timescmd(int argc __unused, char **argv __unused)
+{
+       struct rusage ru;
+       long shumins, shsmins, chumins, chsmins;
+       double shusecs, shssecs, chusecs, chssecs;
+
+       if (getrusage(RUSAGE_SELF, &ru) < 0)
+               return 1;
+       shumins = ru.ru_utime.tv_sec / 60;
+       shusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.;
+       shsmins = ru.ru_stime.tv_sec / 60;
+       shssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.;
+       if (getrusage(RUSAGE_CHILDREN, &ru) < 0)
+               return 1;
+       chumins = ru.ru_utime.tv_sec / 60;
+       chusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.;
+       chsmins = ru.ru_stime.tv_sec / 60;
+       chssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.;
+       out1fmt("%ldm%.3fs %ldm%.3fs\n%ldm%.3fs %ldm%.3fs\n", shumins,
+           shusecs, shsmins, shssecs, chumins, chusecs, chsmins, chssecs);
+       return 0;
+}
diff --git a/sh/eval.h b/sh/eval.h
new file mode 100644 (file)
index 0000000..d4092be
--- /dev/null
+++ b/sh/eval.h
@@ -0,0 +1,70 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)eval.h      8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+extern char *commandname;      /* currently executing command */
+extern int exitstatus;         /* exit status of last command */
+extern int oexitstatus;                /* saved exit status */
+extern struct strlist *cmdenviron;  /* environment for builtin command */
+
+
+struct backcmd {               /* result of evalbackcmd */
+       int fd;                 /* file descriptor to read from */
+       char *buf;              /* buffer */
+       int nleft;              /* number of chars in buffer */
+       struct job *jp;         /* job structure for command */
+};
+
+void reseteval(void);
+
+/* flags in argument to evaltree/evalstring */
+#define EV_EXIT 01             /* exit after evaluating tree */
+#define EV_TESTED 02           /* exit status is checked; ignore -e flag */
+#define EV_BACKCMD 04          /* command executing within back quotes */
+
+void evalstring(const char *, int);
+union node;    /* BLETCH for ansi C */
+void evaltree(union node *, int);
+void evalbackcmd(union node *, struct backcmd *);
+
+/* in_function returns nonzero if we are currently evaluating a function */
+#define in_function()  funcnest
+extern int funcnest;
+extern int evalskip;
+extern int skipcount;
+
+/* reasons for skipping commands (see comment on breakcmd routine) */
+#define SKIPBREAK      1
+#define SKIPCONT       2
+#define SKIPRETURN     3
diff --git a/sh/exec.c b/sh/exec.c
new file mode 100644 (file)
index 0000000..b6ffe5c
--- /dev/null
+++ b/sh/exec.c
@@ -0,0 +1,773 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)exec.c     8.4 (Berkeley) 6/8/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <paths.h>
+#include <stdlib.h>
+
+/*
+ * When commands are first encountered, they are entered in a hash table.
+ * This ensures that a full path search will not have to be done for them
+ * on each invocation.
+ *
+ * We should investigate converting to a linear search, even though that
+ * would make the command name "hash" a misnomer.
+ */
+
+#include "shell.h"
+#include "main.h"
+#include "nodes.h"
+#include "parser.h"
+#include "redir.h"
+#include "eval.h"
+#include "exec.h"
+#include "builtins.h"
+#include "var.h"
+#include "options.h"
+#include "input.h"
+#include "output.h"
+#include "syntax.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "show.h"
+#include "jobs.h"
+#include "alias.h"
+
+#ifdef __APPLE__
+#define eaccess(path, mode) faccessat(AT_FDCWD, path, mode, AT_EACCESS)
+#endif /* __APPLE__ */
+
+#define CMDTABLESIZE 31                /* should be prime */
+
+
+
+struct tblentry {
+       struct tblentry *next;  /* next entry in hash chain */
+       union param param;      /* definition of builtin function */
+       int special;            /* flag for special builtin commands */
+       signed char cmdtype;    /* index identifying command */
+       char cmdname[];         /* name of command */
+};
+
+
+static struct tblentry *cmdtable[CMDTABLESIZE];
+static int cmdtable_cd = 0;    /* cmdtable contains cd-dependent entries */
+int exerrno = 0;                       /* Last exec error */
+
+
+static void tryexec(char *, char **, char **);
+static void printentry(struct tblentry *, int);
+static struct tblentry *cmdlookup(const char *, int);
+static void delete_cmd_entry(void);
+static void addcmdentry(const char *, struct cmdentry *);
+
+
+
+/*
+ * Exec a program.  Never returns.  If you change this routine, you may
+ * have to change the find_command routine as well.
+ *
+ * The argv array may be changed and element argv[-1] should be writable.
+ */
+
+void
+shellexec(char **argv, char **envp, const char *path, int idx)
+{
+       char *cmdname;
+       int e;
+
+       if (strchr(argv[0], '/') != NULL) {
+               tryexec(argv[0], argv, envp);
+               e = errno;
+       } else {
+               e = ENOENT;
+               while ((cmdname = padvance(&path, argv[0])) != NULL) {
+                       if (--idx < 0 && pathopt == NULL) {
+                               tryexec(cmdname, argv, envp);
+                               if (errno != ENOENT && errno != ENOTDIR)
+                                       e = errno;
+                               if (e == ENOEXEC)
+                                       break;
+                       }
+                       stunalloc(cmdname);
+               }
+       }
+
+       /* Map to POSIX errors */
+       if (e == ENOENT || e == ENOTDIR) {
+               exerrno = 127;
+               exerror(EXEXEC, "%s: not found", argv[0]);
+       } else {
+               exerrno = 126;
+               exerror(EXEXEC, "%s: %s", argv[0], strerror(e));
+       }
+}
+
+
+static void
+tryexec(char *cmd, char **argv, char **envp)
+{
+       int e, in;
+       ssize_t n;
+       char buf[256];
+
+       execve(cmd, argv, envp);
+       e = errno;
+       if (e == ENOEXEC) {
+               INTOFF;
+               in = open(cmd, O_RDONLY | O_NONBLOCK);
+               if (in != -1) {
+                       n = pread(in, buf, sizeof buf, 0);
+                       close(in);
+                       if (n > 0 && memchr(buf, '\0', n) != NULL) {
+                               errno = ENOEXEC;
+                               return;
+                       }
+               }
+               *argv = cmd;
+               *--argv = __DECONST(char *, _PATH_BSHELL);
+               execve(_PATH_BSHELL, argv, envp);
+       }
+       errno = e;
+}
+
+/*
+ * Do a path search.  The variable path (passed by reference) should be
+ * set to the start of the path before the first call; padvance will update
+ * this value as it proceeds.  Successive calls to padvance will return
+ * the possible path expansions in sequence.  If an option (indicated by
+ * a percent sign) appears in the path entry then the global variable
+ * pathopt will be set to point to it; otherwise pathopt will be set to
+ * NULL.
+ */
+
+const char *pathopt;
+
+char *
+padvance(const char **path, const char *name)
+{
+       const char *p, *start;
+       char *q;
+       size_t len, namelen;
+
+       if (*path == NULL)
+               return NULL;
+       start = *path;
+       for (p = start; *p && *p != ':' && *p != '%'; p++)
+               ; /* nothing */
+       namelen = strlen(name);
+       len = p - start + namelen + 2;  /* "2" is for '/' and '\0' */
+       STARTSTACKSTR(q);
+       CHECKSTRSPACE(len, q);
+       if (p != start) {
+               memcpy(q, start, p - start);
+               q += p - start;
+               *q++ = '/';
+       }
+       memcpy(q, name, namelen + 1);
+       pathopt = NULL;
+       if (*p == '%') {
+               pathopt = ++p;
+               while (*p && *p != ':')  p++;
+       }
+       if (*p == ':')
+               *path = p + 1;
+       else
+               *path = NULL;
+       return stalloc(len);
+}
+
+
+
+/*** Command hashing code ***/
+
+
+int
+hashcmd(int argc __unused, char **argv __unused)
+{
+       struct tblentry **pp;
+       struct tblentry *cmdp;
+       int c;
+       int verbose;
+       struct cmdentry entry;
+       char *name;
+       int errors;
+
+       errors = 0;
+       verbose = 0;
+       while ((c = nextopt("rv")) != '\0') {
+               if (c == 'r') {
+                       clearcmdentry();
+               } else if (c == 'v') {
+                       verbose++;
+               }
+       }
+       if (*argptr == NULL) {
+               for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
+                       for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+                               if (cmdp->cmdtype == CMDNORMAL)
+                                       printentry(cmdp, verbose);
+                       }
+               }
+               return 0;
+       }
+       while ((name = *argptr) != NULL) {
+               if ((cmdp = cmdlookup(name, 0)) != NULL
+                && cmdp->cmdtype == CMDNORMAL)
+                       delete_cmd_entry();
+               find_command(name, &entry, DO_ERR, pathval());
+               if (entry.cmdtype == CMDUNKNOWN)
+                       errors = 1;
+               else if (verbose) {
+                       cmdp = cmdlookup(name, 0);
+                       if (cmdp != NULL)
+                               printentry(cmdp, verbose);
+                       else {
+                               outfmt(out2, "%s: not found\n", name);
+                               errors = 1;
+                       }
+                       flushall();
+               }
+               argptr++;
+       }
+       return errors;
+}
+
+
+static void
+printentry(struct tblentry *cmdp, int verbose)
+{
+       int idx;
+       const char *path;
+       char *name;
+
+       if (cmdp->cmdtype == CMDNORMAL) {
+               idx = cmdp->param.index;
+               path = pathval();
+               do {
+                       name = padvance(&path, cmdp->cmdname);
+                       stunalloc(name);
+               } while (--idx >= 0);
+               out1str(name);
+       } else if (cmdp->cmdtype == CMDBUILTIN) {
+               out1fmt("builtin %s", cmdp->cmdname);
+       } else if (cmdp->cmdtype == CMDFUNCTION) {
+               out1fmt("function %s", cmdp->cmdname);
+               if (verbose) {
+                       INTOFF;
+                       name = commandtext(getfuncnode(cmdp->param.func));
+                       out1c(' ');
+                       out1str(name);
+                       ckfree(name);
+                       INTON;
+               }
+#ifdef DEBUG
+       } else {
+               error("internal error: cmdtype %d", cmdp->cmdtype);
+#endif
+       }
+       out1c('\n');
+}
+
+
+
+/*
+ * Resolve a command name.  If you change this routine, you may have to
+ * change the shellexec routine as well.
+ */
+
+void
+find_command(const char *name, struct cmdentry *entry, int act,
+    const char *path)
+{
+       struct tblentry *cmdp, loc_cmd;
+       int idx;
+       char *fullname;
+       struct stat statb;
+       int e;
+       int i;
+       int spec;
+       int cd;
+
+       /* If name contains a slash, don't use the hash table */
+       if (strchr(name, '/') != NULL) {
+               entry->cmdtype = CMDNORMAL;
+               entry->u.index = 0;
+               return;
+       }
+
+       cd = 0;
+
+       /* If name is in the table, and not invalidated by cd, we're done */
+       if ((cmdp = cmdlookup(name, 0)) != NULL) {
+               if (cmdp->cmdtype == CMDFUNCTION && act & DO_NOFUNC)
+                       cmdp = NULL;
+               else
+                       goto success;
+       }
+
+       /* Check for builtin next */
+       if ((i = find_builtin(name, &spec)) >= 0) {
+               INTOFF;
+               cmdp = cmdlookup(name, 1);
+               if (cmdp->cmdtype == CMDFUNCTION)
+                       cmdp = &loc_cmd;
+               cmdp->cmdtype = CMDBUILTIN;
+               cmdp->param.index = i;
+               cmdp->special = spec;
+               INTON;
+               goto success;
+       }
+
+       /* We have to search path. */
+
+       e = ENOENT;
+       idx = -1;
+       for (;(fullname = padvance(&path, name)) != NULL; stunalloc(fullname)) {
+               idx++;
+               if (pathopt) {
+                       if (strncmp(pathopt, "func", 4) == 0) {
+                               /* handled below */
+                       } else {
+                               continue; /* ignore unimplemented options */
+                       }
+               }
+               if (fullname[0] != '/')
+                       cd = 1;
+               if (stat(fullname, &statb) < 0) {
+                       if (errno != ENOENT && errno != ENOTDIR)
+                               e = errno;
+                       continue;
+               }
+               e = EACCES;     /* if we fail, this will be the error */
+               if (!S_ISREG(statb.st_mode))
+                       continue;
+               if (pathopt) {          /* this is a %func directory */
+                       readcmdfile(fullname);
+                       if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION)
+                               error("%s not defined in %s", name, fullname);
+                       stunalloc(fullname);
+                       goto success;
+               }
+#ifdef notdef
+               if (statb.st_uid == geteuid()) {
+                       if ((statb.st_mode & 0100) == 0)
+                               goto loop;
+               } else if (statb.st_gid == getegid()) {
+                       if ((statb.st_mode & 010) == 0)
+                               goto loop;
+               } else {
+                       if ((statb.st_mode & 01) == 0)
+                               goto loop;
+               }
+#endif
+               TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
+               INTOFF;
+               stunalloc(fullname);
+               cmdp = cmdlookup(name, 1);
+               if (cmdp->cmdtype == CMDFUNCTION)
+                       cmdp = &loc_cmd;
+               cmdp->cmdtype = CMDNORMAL;
+               cmdp->param.index = idx;
+               INTON;
+               goto success;
+       }
+
+       if (act & DO_ERR) {
+               if (e == ENOENT || e == ENOTDIR)
+                       outfmt(out2, "%s: not found\n", name);
+               else
+                       outfmt(out2, "%s: %s\n", name, strerror(e));
+       }
+       entry->cmdtype = CMDUNKNOWN;
+       entry->u.index = 0;
+       return;
+
+success:
+       if (cd)
+               cmdtable_cd = 1;
+       entry->cmdtype = cmdp->cmdtype;
+       entry->u = cmdp->param;
+       entry->special = cmdp->special;
+}
+
+
+
+/*
+ * Search the table of builtin commands.
+ */
+
+int
+find_builtin(const char *name, int *special)
+{
+       const struct builtincmd *bp;
+
+       for (bp = builtincmd ; bp->name ; bp++) {
+               if (*bp->name == *name && equal(bp->name, name)) {
+                       *special = bp->special;
+                       return bp->code;
+               }
+       }
+       return -1;
+}
+
+
+
+/*
+ * Called when a cd is done.  If any entry in cmdtable depends on the current
+ * directory, simply clear cmdtable completely.
+ */
+
+void
+hashcd(void)
+{
+       if (cmdtable_cd)
+               clearcmdentry();
+}
+
+
+
+/*
+ * Called before PATH is changed.  The argument is the new value of PATH;
+ * pathval() still returns the old value at this point.  Called with
+ * interrupts off.
+ */
+
+void
+changepath(const char *newval __unused)
+{
+       clearcmdentry();
+}
+
+
+/*
+ * Clear out command entries.  The argument specifies the first entry in
+ * PATH which has changed.
+ */
+
+void
+clearcmdentry(void)
+{
+       struct tblentry **tblp;
+       struct tblentry **pp;
+       struct tblentry *cmdp;
+
+       INTOFF;
+       for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
+               pp = tblp;
+               while ((cmdp = *pp) != NULL) {
+                       if (cmdp->cmdtype == CMDNORMAL) {
+                               *pp = cmdp->next;
+                               ckfree(cmdp);
+                       } else {
+                               pp = &cmdp->next;
+                       }
+               }
+       }
+       cmdtable_cd = 0;
+       INTON;
+}
+
+
+/*
+ * Locate a command in the command hash table.  If "add" is nonzero,
+ * add the command to the table if it is not already present.  The
+ * variable "lastcmdentry" is set to point to the address of the link
+ * pointing to the entry, so that delete_cmd_entry can delete the
+ * entry.
+ */
+
+static struct tblentry **lastcmdentry;
+
+
+static struct tblentry *
+cmdlookup(const char *name, int add)
+{
+       unsigned int hashval;
+       const char *p;
+       struct tblentry *cmdp;
+       struct tblentry **pp;
+       size_t len;
+
+       p = name;
+       hashval = (unsigned char)*p << 4;
+       while (*p)
+               hashval += *p++;
+       pp = &cmdtable[hashval % CMDTABLESIZE];
+       for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+               if (equal(cmdp->cmdname, name))
+                       break;
+               pp = &cmdp->next;
+       }
+       if (add && cmdp == NULL) {
+               INTOFF;
+               len = strlen(name);
+               cmdp = *pp = ckmalloc(sizeof (struct tblentry) + len + 1);
+               cmdp->next = NULL;
+               cmdp->cmdtype = CMDUNKNOWN;
+               memcpy(cmdp->cmdname, name, len + 1);
+               INTON;
+       }
+       lastcmdentry = pp;
+       return cmdp;
+}
+
+/*
+ * Delete the command entry returned on the last lookup.
+ */
+
+static void
+delete_cmd_entry(void)
+{
+       struct tblentry *cmdp;
+
+       INTOFF;
+       cmdp = *lastcmdentry;
+       *lastcmdentry = cmdp->next;
+       ckfree(cmdp);
+       INTON;
+}
+
+
+
+/*
+ * Add a new command entry, replacing any existing command entry for
+ * the same name.
+ */
+
+static void
+addcmdentry(const char *name, struct cmdentry *entry)
+{
+       struct tblentry *cmdp;
+
+       INTOFF;
+       cmdp = cmdlookup(name, 1);
+       if (cmdp->cmdtype == CMDFUNCTION) {
+               unreffunc(cmdp->param.func);
+       }
+       cmdp->cmdtype = entry->cmdtype;
+       cmdp->param = entry->u;
+       INTON;
+}
+
+
+/*
+ * Define a shell function.
+ */
+
+void
+defun(const char *name, union node *func)
+{
+       struct cmdentry entry;
+
+       INTOFF;
+       entry.cmdtype = CMDFUNCTION;
+       entry.u.func = copyfunc(func);
+       addcmdentry(name, &entry);
+       INTON;
+}
+
+
+/*
+ * Delete a function if it exists.
+ * Called with interrupts off.
+ */
+
+int
+unsetfunc(const char *name)
+{
+       struct tblentry *cmdp;
+
+       if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) {
+               unreffunc(cmdp->param.func);
+               delete_cmd_entry();
+               return (0);
+       }
+       return (0);
+}
+
+
+/*
+ * Check if a function by a certain name exists.
+ */
+int
+isfunc(const char *name)
+{
+       struct tblentry *cmdp;
+       cmdp = cmdlookup(name, 0);
+       return (cmdp != NULL && cmdp->cmdtype == CMDFUNCTION);
+}
+
+
+/*
+ * Shared code for the following builtin commands:
+ *    type, command -v, command -V
+ */
+
+int
+typecmd_impl(int argc, char **argv, int cmd, const char *path)
+{
+       struct cmdentry entry;
+       struct tblentry *cmdp;
+       const char *const *pp;
+       struct alias *ap;
+       int i;
+       int error1 = 0;
+
+       if (path != pathval())
+               clearcmdentry();
+
+       for (i = 1; i < argc; i++) {
+               /* First look at the keywords */
+               for (pp = parsekwd; *pp; pp++)
+                       if (**pp == *argv[i] && equal(*pp, argv[i]))
+                               break;
+
+               if (*pp) {
+                       if (cmd == TYPECMD_SMALLV)
+                               out1fmt("%s\n", argv[i]);
+                       else
+                               out1fmt("%s is a shell keyword\n", argv[i]);
+                       continue;
+               }
+
+               /* Then look at the aliases */
+               if ((ap = lookupalias(argv[i], 1)) != NULL) {
+                       if (cmd == TYPECMD_SMALLV) {
+                               out1fmt("alias %s=", argv[i]);
+                               out1qstr(ap->val);
+                               outcslow('\n', out1);
+                       } else
+                               out1fmt("%s is an alias for %s\n", argv[i],
+                                   ap->val);
+                       continue;
+               }
+
+               /* Then check if it is a tracked alias */
+               if ((cmdp = cmdlookup(argv[i], 0)) != NULL) {
+                       entry.cmdtype = cmdp->cmdtype;
+                       entry.u = cmdp->param;
+                       entry.special = cmdp->special;
+               }
+               else {
+                       /* Finally use brute force */
+                       find_command(argv[i], &entry, 0, path);
+               }
+
+               switch (entry.cmdtype) {
+               case CMDNORMAL: {
+                       if (strchr(argv[i], '/') == NULL) {
+                               const char *path2 = path;
+                               char *name;
+                               int j = entry.u.index;
+                               do {
+                                       name = padvance(&path2, argv[i]);
+                                       stunalloc(name);
+                               } while (--j >= 0);
+                               if (cmd == TYPECMD_SMALLV)
+                                       out1fmt("%s\n", name);
+                               else
+                                       out1fmt("%s is%s %s\n", argv[i],
+                                           (cmdp && cmd == TYPECMD_TYPE) ?
+                                               " a tracked alias for" : "",
+                                           name);
+                       } else {
+                               if (eaccess(argv[i], X_OK) == 0) {
+                                       if (cmd == TYPECMD_SMALLV)
+                                               out1fmt("%s\n", argv[i]);
+                                       else
+                                               out1fmt("%s is %s\n", argv[i],
+                                                   argv[i]);
+                               } else {
+                                       if (cmd != TYPECMD_SMALLV)
+                                               outfmt(out2, "%s: %s\n",
+                                                   argv[i], strerror(errno));
+                                       error1 |= 127;
+                               }
+                       }
+                       break;
+               }
+               case CMDFUNCTION:
+                       if (cmd == TYPECMD_SMALLV)
+                               out1fmt("%s\n", argv[i]);
+                       else
+                               out1fmt("%s is a shell function\n", argv[i]);
+                       break;
+
+               case CMDBUILTIN:
+                       if (cmd == TYPECMD_SMALLV)
+                               out1fmt("%s\n", argv[i]);
+                       else if (entry.special)
+                               out1fmt("%s is a special shell builtin\n",
+                                   argv[i]);
+                       else
+                               out1fmt("%s is a shell builtin\n", argv[i]);
+                       break;
+
+               default:
+                       if (cmd != TYPECMD_SMALLV)
+                               outfmt(out2, "%s: not found\n", argv[i]);
+                       error1 |= 127;
+                       break;
+               }
+       }
+
+       if (path != pathval())
+               clearcmdentry();
+
+       return error1;
+}
+
+/*
+ * Locate and print what a word is...
+ */
+
+int
+typecmd(int argc, char **argv)
+{
+       if (argc > 2 && strcmp(argv[1], "--") == 0)
+               argc--, argv++;
+       return typecmd_impl(argc, argv, TYPECMD_TYPE, bltinlookup("PATH", 1));
+}
diff --git a/sh/exec.h b/sh/exec.h
new file mode 100644 (file)
index 0000000..b57b2d5
--- /dev/null
+++ b/sh/exec.h
@@ -0,0 +1,77 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)exec.h      8.3 (Berkeley) 6/8/95
+ * $FreeBSD$
+ */
+
+/* values of cmdtype */
+#define CMDUNKNOWN -1          /* no entry in table for command */
+#define CMDNORMAL 0            /* command is an executable program */
+#define CMDBUILTIN 1           /* command is a shell builtin */
+#define CMDFUNCTION 2          /* command is a shell function */
+
+/* values for typecmd_impl's third parameter */
+enum {
+       TYPECMD_SMALLV,         /* command -v */
+       TYPECMD_BIGV,           /* command -V */
+       TYPECMD_TYPE            /* type */
+};
+
+union node;
+struct cmdentry {
+       int cmdtype;
+       union param {
+               int index;
+               struct funcdef *func;
+       } u;
+       int special;
+};
+
+
+/* action to find_command() */
+#define DO_ERR         0x01    /* prints errors */
+#define DO_NOFUNC      0x02    /* don't return shell functions, for command */
+
+extern const char *pathopt;    /* set by padvance */
+extern int exerrno;            /* last exec error */
+
+void shellexec(char **, char **, const char *, int) __dead2;
+char *padvance(const char **, const char *);
+void find_command(const char *, struct cmdentry *, int, const char *);
+int find_builtin(const char *, int *);
+void hashcd(void);
+void changepath(const char *);
+void defun(const char *, union node *);
+int unsetfunc(const char *);
+int isfunc(const char *);
+int typecmd_impl(int, char **, int, const char *);
+void clearcmdentry(void);
diff --git a/sh/expand.c b/sh/expand.c
new file mode 100644 (file)
index 0000000..193d651
--- /dev/null
@@ -0,0 +1,1663 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *     Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)expand.c   8.5 (Berkeley) 5/15/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <wchar.h>
+#include <wctype.h>
+
+/*
+ * Routines to expand arguments to commands.  We have to deal with
+ * backquotes, shell variables, and file metacharacters.
+ */
+
+#include "shell.h"
+#include "main.h"
+#include "nodes.h"
+#include "eval.h"
+#include "expand.h"
+#include "syntax.h"
+#include "parser.h"
+#include "jobs.h"
+#include "options.h"
+#include "var.h"
+#include "input.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "arith.h"
+#include "show.h"
+#include "builtins.h"
+
+/*
+ * Structure specifying which parts of the string should be searched
+ * for IFS characters.
+ */
+
+struct ifsregion {
+       struct ifsregion *next; /* next region in list */
+       int begoff;             /* offset of start of region */
+       int endoff;             /* offset of end of region */
+       int inquotes;           /* search for nul bytes only */
+};
+
+
+static char *expdest;                  /* output of current string */
+static struct nodelist *argbackq;      /* list of back quote expressions */
+static struct ifsregion ifsfirst;      /* first struct in list of ifs regions */
+static struct ifsregion *ifslastp;     /* last struct in list */
+static struct arglist exparg;          /* holds expanded arg list */
+
+static char *argstr(char *, int);
+static char *exptilde(char *, int);
+static char *expari(char *);
+static void expbackq(union node *, int, int);
+static int subevalvar(char *, char *, int, int, int, int, int);
+static char *evalvar(char *, int);
+static int varisset(const char *, int);
+static void strtodest(const char *, int, int, int);
+static void varvalue(const char *, int, int, int);
+static void recordregion(int, int, int);
+static void removerecordregions(int);
+static void ifsbreakup(char *, struct arglist *);
+static void expandmeta(struct strlist *);
+static void expmeta(char *, char *);
+static void addfname(char *);
+static struct strlist *expsort(struct strlist *);
+static struct strlist *msort(struct strlist *, int);
+static int patmatch(const char *, const char *, int);
+static char *cvtnum(int, char *);
+static int collate_range_cmp(wchar_t, wchar_t);
+
+static int
+collate_range_cmp(wchar_t c1, wchar_t c2)
+{
+       static wchar_t s1[2], s2[2];
+
+       s1[0] = c1;
+       s2[0] = c2;
+       return (wcscoll(s1, s2));
+}
+
+static char *
+stputs_quotes(const char *data, const char *syntax, char *p)
+{
+       while (*data) {
+               CHECKSTRSPACE(2, p);
+               if (syntax[(int)*data] == CCTL)
+                       USTPUTC(CTLESC, p);
+               USTPUTC(*data++, p);
+       }
+       return (p);
+}
+#define STPUTS_QUOTES(data, syntax, p) p = stputs_quotes((data), syntax, p)
+
+/*
+ * Perform expansions on an argument, placing the resulting list of arguments
+ * in arglist.  Parameter expansion, command substitution and arithmetic
+ * expansion are always performed; additional expansions can be requested
+ * via flag (EXP_*).
+ * The result is left in the stack string.
+ * When arglist is NULL, perform here document expansion.
+ *
+ * Caution: this function uses global state and is not reentrant.
+ * However, a new invocation after an interrupted invocation is safe
+ * and will reset the global state for the new call.
+ */
+void
+expandarg(union node *arg, struct arglist *arglist, int flag)
+{
+       struct strlist *sp;
+       char *p;
+
+       argbackq = arg->narg.backquote;
+       STARTSTACKSTR(expdest);
+       ifsfirst.next = NULL;
+       ifslastp = NULL;
+       argstr(arg->narg.text, flag);
+       if (arglist == NULL) {
+               STACKSTRNUL(expdest);
+               return;                 /* here document expanded */
+       }
+       STPUTC('\0', expdest);
+       p = grabstackstr(expdest);
+       exparg.lastp = &exparg.list;
+       if (flag & EXP_FULL) {
+               ifsbreakup(p, &exparg);
+               *exparg.lastp = NULL;
+               exparg.lastp = &exparg.list;
+               expandmeta(exparg.list);
+       } else {
+               sp = (struct strlist *)stalloc(sizeof (struct strlist));
+               sp->text = p;
+               *exparg.lastp = sp;
+               exparg.lastp = &sp->next;
+       }
+       while (ifsfirst.next != NULL) {
+               struct ifsregion *ifsp;
+               INTOFF;
+               ifsp = ifsfirst.next->next;
+               ckfree(ifsfirst.next);
+               ifsfirst.next = ifsp;
+               INTON;
+       }
+       *exparg.lastp = NULL;
+       if (exparg.list) {
+               *arglist->lastp = exparg.list;
+               arglist->lastp = exparg.lastp;
+       }
+}
+
+
+
+/*
+ * Perform parameter expansion, command substitution and arithmetic
+ * expansion, and tilde expansion if requested via EXP_TILDE/EXP_VARTILDE.
+ * Processing ends at a CTLENDVAR or CTLENDARI character as well as '\0'.
+ * This is used to expand word in ${var+word} etc.
+ * If EXP_FULL or EXP_CASE are set, keep and/or generate CTLESC
+ * characters to allow for further processing.
+ * If EXP_FULL is set, also preserve CTLQUOTEMARK characters.
+ */
+static char *
+argstr(char *p, int flag)
+{
+       char c;
+       int quotes = flag & (EXP_FULL | EXP_CASE);      /* do CTLESC */
+       int firsteq = 1;
+       int split_lit;
+       int lit_quoted;
+
+       split_lit = flag & EXP_SPLIT_LIT;
+       lit_quoted = flag & EXP_LIT_QUOTED;
+       flag &= ~(EXP_SPLIT_LIT | EXP_LIT_QUOTED);
+       if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE)))
+               p = exptilde(p, flag);
+       for (;;) {
+               CHECKSTRSPACE(2, expdest);
+               switch (c = *p++) {
+               case '\0':
+                       return (p - 1);
+               case CTLENDVAR:
+               case CTLENDARI:
+                       return (p);
+               case CTLQUOTEMARK:
+                       lit_quoted = 1;
+                       /* "$@" syntax adherence hack */
+                       if (p[0] == CTLVAR && p[2] == '@' && p[3] == '=')
+                               break;
+                       if ((flag & EXP_FULL) != 0)
+                               USTPUTC(c, expdest);
+                       break;
+               case CTLQUOTEEND:
+                       lit_quoted = 0;
+                       break;
+               case CTLESC:
+                       if (quotes)
+                               USTPUTC(c, expdest);
+                       c = *p++;
+                       USTPUTC(c, expdest);
+                       if (split_lit && !lit_quoted)
+                               recordregion(expdest - stackblock() -
+                                   (quotes ? 2 : 1),
+                                   expdest - stackblock(), 0);
+                       break;
+               case CTLVAR:
+                       p = evalvar(p, flag);
+                       break;
+               case CTLBACKQ:
+               case CTLBACKQ|CTLQUOTE:
+                       expbackq(argbackq->n, c & CTLQUOTE, flag);
+                       argbackq = argbackq->next;
+                       break;
+               case CTLARI:
+                       p = expari(p);
+                       break;
+               case ':':
+               case '=':
+                       /*
+                        * sort of a hack - expand tildes in variable
+                        * assignments (after the first '=' and after ':'s).
+                        */
+                       USTPUTC(c, expdest);
+                       if (split_lit && !lit_quoted)
+                               recordregion(expdest - stackblock() - 1,
+                                   expdest - stackblock(), 0);
+                       if (flag & EXP_VARTILDE && *p == '~' &&
+                           (c != '=' || firsteq)) {
+                               if (c == '=')
+                                       firsteq = 0;
+                               p = exptilde(p, flag);
+                       }
+                       break;
+               default:
+                       USTPUTC(c, expdest);
+                       if (split_lit && !lit_quoted)
+                               recordregion(expdest - stackblock() - 1,
+                                   expdest - stackblock(), 0);
+               }
+       }
+}
+
+/*
+ * Perform tilde expansion, placing the result in the stack string and
+ * returning the next position in the input string to process.
+ */
+static char *
+exptilde(char *p, int flag)
+{
+       char c, *startp = p;
+       struct passwd *pw;
+       char *home;
+
+       for (;;) {
+               c = *p;
+               switch(c) {
+               case CTLESC: /* This means CTL* are always considered quoted. */
+               case CTLVAR:
+               case CTLBACKQ:
+               case CTLBACKQ | CTLQUOTE:
+               case CTLARI:
+               case CTLENDARI:
+               case CTLQUOTEMARK:
+                       return (startp);
+               case ':':
+                       if ((flag & EXP_VARTILDE) == 0)
+                               break;
+                       /* FALLTHROUGH */
+               case '\0':
+               case '/':
+               case CTLENDVAR:
+                       *p = '\0';
+                       if (*(startp+1) == '\0') {
+                               home = lookupvar("HOME");
+                       } else {
+                               pw = getpwnam(startp+1);
+                               home = pw != NULL ? pw->pw_dir : NULL;
+                       }
+                       *p = c;
+                       if (home == NULL || *home == '\0')
+                               return (startp);
+                       strtodest(home, flag, VSNORMAL, 1);
+                       return (p);
+               }
+               p++;
+       }
+}
+
+
+static void
+removerecordregions(int endoff)
+{
+       if (ifslastp == NULL)
+               return;
+
+       if (ifsfirst.endoff > endoff) {
+               while (ifsfirst.next != NULL) {
+                       struct ifsregion *ifsp;
+                       INTOFF;
+                       ifsp = ifsfirst.next->next;
+                       ckfree(ifsfirst.next);
+                       ifsfirst.next = ifsp;
+                       INTON;
+               }
+               if (ifsfirst.begoff > endoff)
+                       ifslastp = NULL;
+               else {
+                       ifslastp = &ifsfirst;
+                       ifsfirst.endoff = endoff;
+               }
+               return;
+       }
+
+       ifslastp = &ifsfirst;
+       while (ifslastp->next && ifslastp->next->begoff < endoff)
+               ifslastp=ifslastp->next;
+       while (ifslastp->next != NULL) {
+               struct ifsregion *ifsp;
+               INTOFF;
+               ifsp = ifslastp->next->next;
+               ckfree(ifslastp->next);
+               ifslastp->next = ifsp;
+               INTON;
+       }
+       if (ifslastp->endoff > endoff)
+               ifslastp->endoff = endoff;
+}
+
+/*
+ * Expand arithmetic expression.
+ * Note that flag is not required as digits never require CTLESC characters.
+ */
+static char *
+expari(char *p)
+{
+       char *q, *start;
+       arith_t result;
+       int begoff;
+       int quoted;
+       int adj;
+
+       quoted = *p++ == '"';
+       begoff = expdest - stackblock();
+       p = argstr(p, 0);
+       removerecordregions(begoff);
+       STPUTC('\0', expdest);
+       start = stackblock() + begoff;
+
+       q = grabstackstr(expdest);
+       result = arith(start);
+       ungrabstackstr(q, expdest);
+
+       start = stackblock() + begoff;
+       adj = start - expdest;
+       STADJUST(adj, expdest);
+
+       CHECKSTRSPACE((int)(DIGITS(result) + 1), expdest);
+       fmtstr(expdest, DIGITS(result), ARITH_FORMAT_STR, result);
+       adj = strlen(expdest);
+       STADJUST(adj, expdest);
+       if (!quoted)
+               recordregion(begoff, expdest - stackblock(), 0);
+       return p;
+}
+
+
+/*
+ * Perform command substitution.
+ */
+static void
+expbackq(union node *cmd, int quoted, int flag)
+{
+       struct backcmd in;
+       int i;
+       char buf[128];
+       char *p;
+       char *dest = expdest;
+       struct ifsregion saveifs, *savelastp;
+       struct nodelist *saveargbackq;
+       char lastc;
+       int startloc = dest - stackblock();
+       char const *syntax = quoted? DQSYNTAX : BASESYNTAX;
+       int quotes = flag & (EXP_FULL | EXP_CASE);
+       size_t nnl;
+
+       INTOFF;
+       saveifs = ifsfirst;
+       savelastp = ifslastp;
+       saveargbackq = argbackq;
+       p = grabstackstr(dest);
+       evalbackcmd(cmd, &in);
+       ungrabstackstr(p, dest);
+       ifsfirst = saveifs;
+       ifslastp = savelastp;
+       argbackq = saveargbackq;
+
+       p = in.buf;
+       lastc = '\0';
+       nnl = 0;
+       /* Don't copy trailing newlines */
+       for (;;) {
+               if (--in.nleft < 0) {
+                       if (in.fd < 0)
+                               break;
+                       while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR);
+                       TRACE(("expbackq: read returns %d\n", i));
+                       if (i <= 0)
+                               break;
+                       p = buf;
+                       in.nleft = i - 1;
+               }
+               lastc = *p++;
+               if (lastc != '\0') {
+                       if (lastc == '\n') {
+                               nnl++;
+                       } else {
+                               CHECKSTRSPACE(nnl + 2, dest);
+                               while (nnl > 0) {
+                                       nnl--;
+                                       USTPUTC('\n', dest);
+                               }
+                               if (quotes && syntax[(int)lastc] == CCTL)
+                                       USTPUTC(CTLESC, dest);
+                               USTPUTC(lastc, dest);
+                       }
+               }
+       }
+
+       if (in.fd >= 0)
+               close(in.fd);
+       if (in.buf)
+               ckfree(in.buf);
+       if (in.jp)
+               exitstatus = waitforjob(in.jp, (int *)NULL);
+       if (quoted == 0)
+               recordregion(startloc, dest - stackblock(), 0);
+       TRACE(("expbackq: size=%td: \"%.*s\"\n",
+               ((dest - stackblock()) - startloc),
+               (int)((dest - stackblock()) - startloc),
+               stackblock() + startloc));
+       expdest = dest;
+       INTON;
+}
+
+
+
+static void
+recordleft(const char *str, const char *loc, char *startp)
+{
+       int amount;
+
+       amount = ((str - 1) - (loc - startp)) - expdest;
+       STADJUST(amount, expdest);
+       while (loc != str - 1)
+               *startp++ = *loc++;
+}
+
+static int
+subevalvar(char *p, char *str, int strloc, int subtype, int startloc,
+  int varflags, int quotes)
+{
+       char *startp;
+       char *loc = NULL;
+       char *q;
+       int c = 0;
+       struct nodelist *saveargbackq = argbackq;
+       int amount;
+
+       argstr(p, (subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX ||
+           subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX ?
+           EXP_CASE : 0) | EXP_TILDE);
+       STACKSTRNUL(expdest);
+       argbackq = saveargbackq;
+       startp = stackblock() + startloc;
+       if (str == NULL)
+           str = stackblock() + strloc;
+
+       switch (subtype) {
+       case VSASSIGN:
+               setvar(str, startp, 0);
+               amount = startp - expdest;
+               STADJUST(amount, expdest);
+               varflags &= ~VSNUL;
+               return 1;
+
+       case VSQUESTION:
+               if (*p != CTLENDVAR) {
+                       outfmt(out2, "%s\n", startp);
+                       error((char *)NULL);
+               }
+               error("%.*s: parameter %snot set", (int)(p - str - 1),
+                     str, (varflags & VSNUL) ? "null or " : "");
+               return 0;
+
+       case VSTRIMLEFT:
+               for (loc = startp; loc < str; loc++) {
+                       c = *loc;
+                       *loc = '\0';
+                       if (patmatch(str, startp, quotes)) {
+                               *loc = c;
+                               recordleft(str, loc, startp);
+                               return 1;
+                       }
+                       *loc = c;
+                       if (quotes && *loc == CTLESC)
+                               loc++;
+               }
+               return 0;
+
+       case VSTRIMLEFTMAX:
+               for (loc = str - 1; loc >= startp;) {
+                       c = *loc;
+                       *loc = '\0';
+                       if (patmatch(str, startp, quotes)) {
+                               *loc = c;
+                               recordleft(str, loc, startp);
+                               return 1;
+                       }
+                       *loc = c;
+                       loc--;
+                       if (quotes && loc > startp && *(loc - 1) == CTLESC) {
+                               for (q = startp; q < loc; q++)
+                                       if (*q == CTLESC)
+                                               q++;
+                               if (q > loc)
+                                       loc--;
+                       }
+               }
+               return 0;
+
+       case VSTRIMRIGHT:
+               for (loc = str - 1; loc >= startp;) {
+                       if (patmatch(str, loc, quotes)) {
+                               amount = loc - expdest;
+                               STADJUST(amount, expdest);
+                               return 1;
+                       }
+                       loc--;
+                       if (quotes && loc > startp && *(loc - 1) == CTLESC) {
+                               for (q = startp; q < loc; q++)
+                                       if (*q == CTLESC)
+                                               q++;
+                               if (q > loc)
+                                       loc--;
+                       }
+               }
+               return 0;
+
+       case VSTRIMRIGHTMAX:
+               for (loc = startp; loc < str - 1; loc++) {
+                       if (patmatch(str, loc, quotes)) {
+                               amount = loc - expdest;
+                               STADJUST(amount, expdest);
+                               return 1;
+                       }
+                       if (quotes && *loc == CTLESC)
+                               loc++;
+               }
+               return 0;
+
+
+       default:
+               abort();
+       }
+}
+
+
+/*
+ * Expand a variable, and return a pointer to the next character in the
+ * input string.
+ */
+
+static char *
+evalvar(char *p, int flag)
+{
+       int subtype;
+       int varflags;
+       char *var;
+       const char *val;
+       int patloc;
+       int c;
+       int set;
+       int special;
+       int startloc;
+       int varlen;
+       int varlenb;
+       int easy;
+       int quotes = flag & (EXP_FULL | EXP_CASE);
+       int record = 0;
+
+       varflags = (unsigned char)*p++;
+       subtype = varflags & VSTYPE;
+       var = p;
+       special = 0;
+       if (! is_name(*p))
+               special = 1;
+       p = strchr(p, '=') + 1;
+again: /* jump here after setting a variable with ${var=text} */
+       if (varflags & VSLINENO) {
+               set = 1;
+               special = 1;
+               val = NULL;
+       } else if (special) {
+               set = varisset(var, varflags & VSNUL);
+               val = NULL;
+       } else {
+               val = bltinlookup(var, 1);
+               if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) {
+                       val = NULL;
+                       set = 0;
+               } else
+                       set = 1;
+       }
+       varlen = 0;
+       startloc = expdest - stackblock();
+       if (!set && uflag && *var != '@' && *var != '*') {
+               switch (subtype) {
+               case VSNORMAL:
+               case VSTRIMLEFT:
+               case VSTRIMLEFTMAX:
+               case VSTRIMRIGHT:
+               case VSTRIMRIGHTMAX:
+               case VSLENGTH:
+                       error("%.*s: parameter not set", (int)(p - var - 1),
+                           var);
+               }
+       }
+       if (set && subtype != VSPLUS) {
+               /* insert the value of the variable */
+               if (special) {
+                       if (varflags & VSLINENO)
+                               STPUTBIN(var, p - var - 1, expdest);
+                       else
+                               varvalue(var, varflags & VSQUOTE, subtype, flag);
+                       if (subtype == VSLENGTH) {
+                               varlenb = expdest - stackblock() - startloc;
+                               varlen = varlenb;
+                               if (localeisutf8) {
+                                       val = stackblock() + startloc;
+                                       for (;val != expdest; val++)
+                                               if ((*val & 0xC0) == 0x80)
+                                                       varlen--;
+                               }
+                               STADJUST(-varlenb, expdest);
+                       }
+               } else {
+                       if (subtype == VSLENGTH) {
+                               for (;*val; val++)
+                                       if (!localeisutf8 ||
+                                           (*val & 0xC0) != 0x80)
+                                               varlen++;
+                       }
+                       else
+                               strtodest(val, flag, subtype,
+                                   varflags & VSQUOTE);
+               }
+       }
+
+       if (subtype == VSPLUS)
+               set = ! set;
+
+       easy = ((varflags & VSQUOTE) == 0 ||
+               (*var == '@' && shellparam.nparam != 1));
+
+
+       switch (subtype) {
+       case VSLENGTH:
+               expdest = cvtnum(varlen, expdest);
+               record = 1;
+               break;
+
+       case VSNORMAL:
+               record = easy;
+               break;
+
+       case VSPLUS:
+       case VSMINUS:
+               if (!set) {
+                       argstr(p, flag | (flag & EXP_FULL ? EXP_SPLIT_LIT : 0) |
+                           (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0));
+                       break;
+               }
+               record = easy;
+               break;
+
+       case VSTRIMLEFT:
+       case VSTRIMLEFTMAX:
+       case VSTRIMRIGHT:
+       case VSTRIMRIGHTMAX:
+               if (!set)
+                       break;
+               /*
+                * Terminate the string and start recording the pattern
+                * right after it
+                */
+               STPUTC('\0', expdest);
+               patloc = expdest - stackblock();
+               if (subevalvar(p, NULL, patloc, subtype,
+                   startloc, varflags, quotes) == 0) {
+                       int amount = (expdest - stackblock() - patloc) + 1;
+                       STADJUST(-amount, expdest);
+               }
+               /* Remove any recorded regions beyond start of variable */
+               removerecordregions(startloc);
+               record = 1;
+               break;
+
+       case VSASSIGN:
+       case VSQUESTION:
+               if (!set) {
+                       if (subevalvar(p, var, 0, subtype, startloc, varflags,
+                           quotes)) {
+                               varflags &= ~VSNUL;
+                               /*
+                                * Remove any recorded regions beyond
+                                * start of variable
+                                */
+                               removerecordregions(startloc);
+                               goto again;
+                       }
+                       break;
+               }
+               record = easy;
+               break;
+
+       case VSERROR:
+               c = p - var - 1;
+               error("${%.*s%s}: Bad substitution", c, var,
+                   (c > 0 && *p != CTLENDVAR) ? "..." : "");
+
+       default:
+               abort();
+       }
+
+       if (record)
+               recordregion(startloc, expdest - stackblock(),
+                   varflags & VSQUOTE || (ifsset() && ifsval()[0] == '\0' &&
+                   (*var == '@' || *var == '*')));
+
+       if (subtype != VSNORMAL) {      /* skip to end of alternative */
+               int nesting = 1;
+               for (;;) {
+                       if ((c = *p++) == CTLESC)
+                               p++;
+                       else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) {
+                               if (set)
+                                       argbackq = argbackq->next;
+                       } else if (c == CTLVAR) {
+                               if ((*p++ & VSTYPE) != VSNORMAL)
+                                       nesting++;
+                       } else if (c == CTLENDVAR) {
+                               if (--nesting == 0)
+                                       break;
+                       }
+               }
+       }
+       return p;
+}
+
+
+
+/*
+ * Test whether a specialized variable is set.
+ */
+
+static int
+varisset(const char *name, int nulok)
+{
+
+       if (*name == '!')
+               return backgndpidset();
+       else if (*name == '@' || *name == '*') {
+               if (*shellparam.p == NULL)
+                       return 0;
+
+               if (nulok) {
+                       char **av;
+
+                       for (av = shellparam.p; *av; av++)
+                               if (**av != '\0')
+                                       return 1;
+                       return 0;
+               }
+       } else if (is_digit(*name)) {
+               char *ap;
+               long num;
+
+               errno = 0;
+               num = strtol(name, NULL, 10);
+               if (errno != 0 || num > shellparam.nparam)
+                       return 0;
+
+               if (num == 0)
+                       ap = arg0;
+               else
+                       ap = shellparam.p[num - 1];
+
+               if (nulok && (ap == NULL || *ap == '\0'))
+                       return 0;
+       }
+       return 1;
+}
+
+static void
+strtodest(const char *p, int flag, int subtype, int quoted)
+{
+       if (flag & (EXP_FULL | EXP_CASE) && subtype != VSLENGTH)
+               STPUTS_QUOTES(p, quoted ? DQSYNTAX : BASESYNTAX, expdest);
+       else
+               STPUTS(p, expdest);
+}
+
+/*
+ * Add the value of a specialized variable to the stack string.
+ */
+
+static void
+varvalue(const char *name, int quoted, int subtype, int flag)
+{
+       int num;
+       char *p;
+       int i;
+       char sep[2];
+       char **ap;
+
+       switch (*name) {
+       case '$':
+               num = rootpid;
+               break;
+       case '?':
+               num = oexitstatus;
+               break;
+       case '#':
+               num = shellparam.nparam;
+               break;
+       case '!':
+               num = backgndpidval();
+               break;
+       case '-':
+               for (i = 0 ; i < NOPTS ; i++) {
+                       if (optlist[i].val)
+                               STPUTC(optlist[i].letter, expdest);
+               }
+               return;
+       case '@':
+               if (flag & EXP_FULL && quoted) {
+                       for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
+                               strtodest(p, flag, subtype, quoted);
+                               if (*ap)
+                                       STPUTC('\0', expdest);
+                       }
+                       return;
+               }
+               /* FALLTHROUGH */
+       case '*':
+               if (ifsset())
+                       sep[0] = ifsval()[0];
+               else
+                       sep[0] = ' ';
+               sep[1] = '\0';
+               for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
+                       strtodest(p, flag, subtype, quoted);
+                       if (!*ap)
+                               break;
+                       if (sep[0])
+                               strtodest(sep, flag, subtype, quoted);
+                       else if (flag & EXP_FULL && !quoted && **ap != '\0')
+                               STPUTC('\0', expdest);
+               }
+               return;
+       default:
+               if (is_digit(*name)) {
+                       num = atoi(name);
+                       if (num == 0)
+                               p = arg0;
+                       else if (num > 0 && num <= shellparam.nparam)
+                               p = shellparam.p[num - 1];
+                       else
+                               return;
+                       strtodest(p, flag, subtype, quoted);
+               }
+               return;
+       }
+       expdest = cvtnum(num, expdest);
+}
+
+
+
+/*
+ * Record the fact that we have to scan this region of the
+ * string for IFS characters.
+ */
+
+static void
+recordregion(int start, int end, int inquotes)
+{
+       struct ifsregion *ifsp;
+
+       INTOFF;
+       if (ifslastp == NULL) {
+               ifsp = &ifsfirst;
+       } else {
+               if (ifslastp->endoff == start
+                   && ifslastp->inquotes == inquotes) {
+                       /* extend previous area */
+                       ifslastp->endoff = end;
+                       INTON;
+                       return;
+               }
+               ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion));
+               ifslastp->next = ifsp;
+       }
+       ifslastp = ifsp;
+       ifslastp->next = NULL;
+       ifslastp->begoff = start;
+       ifslastp->endoff = end;
+       ifslastp->inquotes = inquotes;
+       INTON;
+}
+
+
+
+/*
+ * Break the argument string into pieces based upon IFS and add the
+ * strings to the argument list.  The regions of the string to be
+ * searched for IFS characters have been stored by recordregion.
+ * CTLESC characters are preserved but have little effect in this pass
+ * other than escaping CTL* characters.  In particular, they do not escape
+ * IFS characters: that should be done with the ifsregion mechanism.
+ * CTLQUOTEMARK characters are used to preserve empty quoted strings.
+ * This pass treats them as a regular character, making the string non-empty.
+ * Later, they are removed along with the other CTL* characters.
+ */
+static void
+ifsbreakup(char *string, struct arglist *arglist)
+{
+       struct ifsregion *ifsp;
+       struct strlist *sp;
+       char *start;
+       char *p;
+       char *q;
+       const char *ifs;
+       const char *ifsspc;
+       int had_param_ch = 0;
+
+       start = string;
+
+       if (ifslastp == NULL) {
+               /* Return entire argument, IFS doesn't apply to any of it */
+               sp = (struct strlist *)stalloc(sizeof *sp);
+               sp->text = start;
+               *arglist->lastp = sp;
+               arglist->lastp = &sp->next;
+               return;
+       }
+
+       ifs = ifsset() ? ifsval() : " \t\n";
+
+       for (ifsp = &ifsfirst; ifsp != NULL; ifsp = ifsp->next) {
+               p = string + ifsp->begoff;
+               while (p < string + ifsp->endoff) {
+                       q = p;
+                       if (*p == CTLESC)
+                               p++;
+                       if (ifsp->inquotes) {
+                               /* Only NULs (should be from "$@") end args */
+                               had_param_ch = 1;
+                               if (*p != 0) {
+                                       p++;
+                                       continue;
+                               }
+                               ifsspc = NULL;
+                       } else {
+                               if (!strchr(ifs, *p)) {
+                                       had_param_ch = 1;
+                                       p++;
+                                       continue;
+                               }
+                               ifsspc = strchr(" \t\n", *p);
+
+                               /* Ignore IFS whitespace at start */
+                               if (q == start && ifsspc != NULL) {
+                                       p++;
+                                       start = p;
+                                       continue;
+                               }
+                               had_param_ch = 0;
+                       }
+
+                       /* Save this argument... */
+                       *q = '\0';
+                       sp = (struct strlist *)stalloc(sizeof *sp);
+                       sp->text = start;
+                       *arglist->lastp = sp;
+                       arglist->lastp = &sp->next;
+                       p++;
+
+                       if (ifsspc != NULL) {
+                               /* Ignore further trailing IFS whitespace */
+                               for (; p < string + ifsp->endoff; p++) {
+                                       q = p;
+                                       if (*p == CTLESC)
+                                               p++;
+                                       if (strchr(ifs, *p) == NULL) {
+                                               p = q;
+                                               break;
+                                       }
+                                       if (strchr(" \t\n", *p) == NULL) {
+                                               p++;
+                                               break;
+                                       }
+                               }
+                       }
+                       start = p;
+               }
+       }
+
+       /*
+        * Save anything left as an argument.
+        * Traditionally we have treated 'IFS=':'; set -- x$IFS' as
+        * generating 2 arguments, the second of which is empty.
+        * Some recent clarification of the Posix spec say that it
+        * should only generate one....
+        */
+       if (had_param_ch || *start != 0) {
+               sp = (struct strlist *)stalloc(sizeof *sp);
+               sp->text = start;
+               *arglist->lastp = sp;
+               arglist->lastp = &sp->next;
+       }
+}
+
+
+static char expdir[PATH_MAX];
+#define expdir_end (expdir + sizeof(expdir))
+
+/*
+ * Perform pathname generation and remove control characters.
+ * At this point, the only control characters should be CTLESC and CTLQUOTEMARK.
+ * The results are stored in the list exparg.
+ */
+static void
+expandmeta(struct strlist *str)
+{
+       char *p;
+       struct strlist **savelastp;
+       struct strlist *sp;
+       char c;
+
+       while (str) {
+               savelastp = exparg.lastp;
+               if (!fflag) {
+                       p = str->text;
+                       for (; (c = *p) != '\0'; p++) {
+                               /* fast check for meta chars */
+                               if (c == '*' || c == '?' || c == '[') {
+                                       INTOFF;
+                                       expmeta(expdir, str->text);
+                                       INTON;
+                                       break;
+                               }
+                       }
+               }
+               if (exparg.lastp == savelastp) {
+                       /*
+                        * no matches
+                        */
+                       *exparg.lastp = str;
+                       rmescapes(str->text);
+                       exparg.lastp = &str->next;
+               } else {
+                       *exparg.lastp = NULL;
+                       *savelastp = sp = expsort(*savelastp);
+                       while (sp->next != NULL)
+                               sp = sp->next;
+                       exparg.lastp = &sp->next;
+               }
+               str = str->next;
+       }
+}
+
+
+/*
+ * Do metacharacter (i.e. *, ?, [...]) expansion.
+ */
+
+static void
+expmeta(char *enddir, char *name)
+{
+       const char *p;
+       const char *q;
+       const char *start;
+       char *endname;
+       int metaflag;
+       struct stat statb;
+       DIR *dirp;
+       struct dirent *dp;
+       int atend;
+       int matchdot;
+       int esc;
+       int namlen;
+
+       metaflag = 0;
+       start = name;
+       for (p = name; esc = 0, *p; p += esc + 1) {
+               if (*p == '*' || *p == '?')
+                       metaflag = 1;
+               else if (*p == '[') {
+                       q = p + 1;
+                       if (*q == '!' || *q == '^')
+                               q++;
+                       for (;;) {
+                               while (*q == CTLQUOTEMARK)
+                                       q++;
+                               if (*q == CTLESC)
+                                       q++;
+                               if (*q == '/' || *q == '\0')
+                                       break;
+                               if (*++q == ']') {
+                                       metaflag = 1;
+                                       break;
+                               }
+                       }
+               } else if (*p == '\0')
+                       break;
+               else if (*p == CTLQUOTEMARK)
+                       continue;
+               else {
+                       if (*p == CTLESC)
+                               esc++;
+                       if (p[esc] == '/') {
+                               if (metaflag)
+                                       break;
+                               start = p + esc + 1;
+                       }
+               }
+       }
+       if (metaflag == 0) {    /* we've reached the end of the file name */
+               if (enddir != expdir)
+                       metaflag++;
+               for (p = name ; ; p++) {
+                       if (*p == CTLQUOTEMARK)
+                               continue;
+                       if (*p == CTLESC)
+                               p++;
+                       *enddir++ = *p;
+                       if (*p == '\0')
+                               break;
+                       if (enddir == expdir_end)
+                               return;
+               }
+               if (metaflag == 0 || lstat(expdir, &statb) >= 0)
+                       addfname(expdir);
+               return;
+       }
+       endname = name + (p - name);
+       if (start != name) {
+               p = name;
+               while (p < start) {
+                       while (*p == CTLQUOTEMARK)
+                               p++;
+                       if (*p == CTLESC)
+                               p++;
+                       *enddir++ = *p++;
+                       if (enddir == expdir_end)
+                               return;
+               }
+       }
+       if (enddir == expdir) {
+               p = ".";
+       } else if (enddir == expdir + 1 && *expdir == '/') {
+               p = "/";
+       } else {
+               p = expdir;
+               enddir[-1] = '\0';
+       }
+       if ((dirp = opendir(p)) == NULL)
+               return;
+       if (enddir != expdir)
+               enddir[-1] = '/';
+       if (*endname == 0) {
+               atend = 1;
+       } else {
+               atend = 0;
+               *endname = '\0';
+               endname += esc + 1;
+       }
+       matchdot = 0;
+       p = start;
+       while (*p == CTLQUOTEMARK)
+               p++;
+       if (*p == CTLESC)
+               p++;
+       if (*p == '.')
+               matchdot++;
+       while (! int_pending() && (dp = readdir(dirp)) != NULL) {
+               if (dp->d_name[0] == '.' && ! matchdot)
+                       continue;
+               if (patmatch(start, dp->d_name, 0)) {
+                       namlen = dp->d_namlen;
+                       if (enddir + namlen + 1 > expdir_end)
+                               continue;
+                       memcpy(enddir, dp->d_name, namlen + 1);
+                       if (atend)
+                               addfname(expdir);
+                       else {
+                               if (dp->d_type != DT_UNKNOWN &&
+                                   dp->d_type != DT_DIR &&
+                                   dp->d_type != DT_LNK)
+                                       continue;
+                               if (enddir + namlen + 2 > expdir_end)
+                                       continue;
+                               enddir[namlen] = '/';
+                               enddir[namlen + 1] = '\0';
+                               expmeta(enddir + namlen + 1, endname);
+                       }
+               }
+       }
+       closedir(dirp);
+       if (! atend)
+               endname[-esc - 1] = esc ? CTLESC : '/';
+}
+
+
+/*
+ * Add a file name to the list.
+ */
+
+static void
+addfname(char *name)
+{
+       char *p;
+       struct strlist *sp;
+
+       p = stsavestr(name);
+       sp = (struct strlist *)stalloc(sizeof *sp);
+       sp->text = p;
+       *exparg.lastp = sp;
+       exparg.lastp = &sp->next;
+}
+
+
+/*
+ * Sort the results of file name expansion.  It calculates the number of
+ * strings to sort and then calls msort (short for merge sort) to do the
+ * work.
+ */
+
+static struct strlist *
+expsort(struct strlist *str)
+{
+       int len;
+       struct strlist *sp;
+
+       len = 0;
+       for (sp = str ; sp ; sp = sp->next)
+               len++;
+       return msort(str, len);
+}
+
+
+static struct strlist *
+msort(struct strlist *list, int len)
+{
+       struct strlist *p, *q = NULL;
+       struct strlist **lpp;
+       int half;
+       int n;
+
+       if (len <= 1)
+               return list;
+       half = len >> 1;
+       p = list;
+       for (n = half ; --n >= 0 ; ) {
+               q = p;
+               p = p->next;
+       }
+       q->next = NULL;                 /* terminate first half of list */
+       q = msort(list, half);          /* sort first half of list */
+       p = msort(p, len - half);               /* sort second half */
+       lpp = &list;
+       for (;;) {
+               if (strcmp(p->text, q->text) < 0) {
+                       *lpp = p;
+                       lpp = &p->next;
+                       if ((p = *lpp) == NULL) {
+                               *lpp = q;
+                               break;
+                       }
+               } else {
+                       *lpp = q;
+                       lpp = &q->next;
+                       if ((q = *lpp) == NULL) {
+                               *lpp = p;
+                               break;
+                       }
+               }
+       }
+       return list;
+}
+
+
+
+static wchar_t
+get_wc(const char **p)
+{
+       wchar_t c;
+       int chrlen;
+
+       chrlen = mbtowc(&c, *p, 4);
+       if (chrlen == 0)
+               return 0;
+       else if (chrlen == -1)
+               c = 0;
+       else
+               *p += chrlen;
+       return c;
+}
+
+
+/*
+ * See if a character matches a character class, starting at the first colon
+ * of "[:class:]".
+ * If a valid character class is recognized, a pointer to the next character
+ * after the final closing bracket is stored into *end, otherwise a null
+ * pointer is stored into *end.
+ */
+static int
+match_charclass(const char *p, wchar_t chr, const char **end)
+{
+       char name[20];
+       const char *nameend;
+       wctype_t cclass;
+
+       *end = NULL;
+       p++;
+       nameend = strstr(p, ":]");
+       if (nameend == NULL || (size_t)(nameend - p) >= sizeof(name) ||
+           nameend == p)
+               return 0;
+       memcpy(name, p, nameend - p);
+       name[nameend - p] = '\0';
+       *end = nameend + 2;
+       cclass = wctype(name);
+       /* An unknown class matches nothing but is valid nevertheless. */
+       if (cclass == 0)
+               return 0;
+       return iswctype(chr, cclass);
+}
+
+
+/*
+ * Returns true if the pattern matches the string.
+ */
+
+static int
+patmatch(const char *pattern, const char *string, int squoted)
+{
+       const char *p, *q, *end;
+       const char *bt_p, *bt_q;
+       char c;
+       wchar_t wc, wc2;
+
+       p = pattern;
+       q = string;
+       bt_p = NULL;
+       bt_q = NULL;
+       for (;;) {
+               switch (c = *p++) {
+               case '\0':
+                       if (*q != '\0')
+                               goto backtrack;
+                       return 1;
+               case CTLESC:
+                       if (squoted && *q == CTLESC)
+                               q++;
+                       if (*q++ != *p++)
+                               goto backtrack;
+                       break;
+               case CTLQUOTEMARK:
+                       continue;
+               case '?':
+                       if (squoted && *q == CTLESC)
+                               q++;
+                       if (*q == '\0')
+                               return 0;
+                       if (localeisutf8) {
+                               wc = get_wc(&q);
+                               /*
+                                * A '?' does not match invalid UTF-8 but a
+                                * '*' does, so backtrack.
+                                */
+                               if (wc == 0)
+                                       goto backtrack;
+                       } else
+                               wc = (unsigned char)*q++;
+                       break;
+               case '*':
+                       c = *p;
+                       while (c == CTLQUOTEMARK || c == '*')
+                               c = *++p;
+                       /*
+                        * If the pattern ends here, we know the string
+                        * matches without needing to look at the rest of it.
+                        */
+                       if (c == '\0')
+                               return 1;
+                       /*
+                        * First try the shortest match for the '*' that
+                        * could work. We can forget any earlier '*' since
+                        * there is no way having it match more characters
+                        * can help us, given that we are already here.
+                        */
+                       bt_p = p;
+                       bt_q = q;
+                       break;
+               case '[': {
+                       const char *endp;
+                       int invert, found;
+                       wchar_t chr;
+
+                       endp = p;
+                       if (*endp == '!' || *endp == '^')
+                               endp++;
+                       do {
+                               while (*endp == CTLQUOTEMARK)
+                                       endp++;
+                               if (*endp == 0)
+                                       goto dft;               /* no matching ] */
+                               if (*endp == CTLESC)
+                                       endp++;
+                       } while (*++endp != ']');
+                       invert = 0;
+                       if (*p == '!' || *p == '^') {
+                               invert++;
+                               p++;
+                       }
+                       found = 0;
+                       if (squoted && *q == CTLESC)
+                               q++;
+                       if (*q == '\0')
+                               return 0;
+                       if (localeisutf8) {
+                               chr = get_wc(&q);
+                               if (chr == 0)
+                                       goto backtrack;
+                       } else
+                               chr = (unsigned char)*q++;
+                       c = *p++;
+                       do {
+                               if (c == CTLQUOTEMARK)
+                                       continue;
+                               if (c == '[' && *p == ':') {
+                                       found |= match_charclass(p, chr, &end);
+                                       if (end != NULL)
+                                               p = end;
+                               }
+                               if (c == CTLESC)
+                                       c = *p++;
+                               if (localeisutf8 && c & 0x80) {
+                                       p--;
+                                       wc = get_wc(&p);
+                                       if (wc == 0) /* bad utf-8 */
+                                               return 0;
+                               } else
+                                       wc = (unsigned char)c;
+                               if (*p == '-' && p[1] != ']') {
+                                       p++;
+                                       while (*p == CTLQUOTEMARK)
+                                               p++;
+                                       if (*p == CTLESC)
+                                               p++;
+                                       if (localeisutf8) {
+                                               wc2 = get_wc(&p);
+                                               if (wc2 == 0) /* bad utf-8 */
+                                                       return 0;
+                                       } else
+                                               wc2 = (unsigned char)*p++;
+                                       if (   collate_range_cmp(chr, wc) >= 0
+                                           && collate_range_cmp(chr, wc2) <= 0
+                                          )
+                                               found = 1;
+                               } else {
+                                       if (chr == wc)
+                                               found = 1;
+                               }
+                       } while ((c = *p++) != ']');
+                       if (found == invert)
+                               goto backtrack;
+                       break;
+               }
+dft:           default:
+                       if (squoted && *q == CTLESC)
+                               q++;
+                       if (*q == '\0')
+                               return 0;
+                       if (*q++ == c)
+                               break;
+backtrack:
+                       /*
+                        * If we have a mismatch (other than hitting the end
+                        * of the string), go back to the last '*' seen and
+                        * have it match one additional character.
+                        */
+                       if (bt_p == NULL)
+                               return 0;
+                       if (squoted && *bt_q == CTLESC)
+                               bt_q++;
+                       if (*bt_q == '\0')
+                               return 0;
+                       bt_q++;
+                       p = bt_p;
+                       q = bt_q;
+                       break;
+               }
+       }
+}
+
+
+
+/*
+ * Remove any CTLESC and CTLQUOTEMARK characters from a string.
+ */
+
+void
+rmescapes(char *str)
+{
+       char *p, *q;
+
+       p = str;
+       while (*p != CTLESC && *p != CTLQUOTEMARK && *p != CTLQUOTEEND) {
+               if (*p++ == '\0')
+                       return;
+       }
+       q = p;
+       while (*p) {
+               if (*p == CTLQUOTEMARK || *p == CTLQUOTEEND) {
+                       p++;
+                       continue;
+               }
+               if (*p == CTLESC)
+                       p++;
+               *q++ = *p++;
+       }
+       *q = '\0';
+}
+
+
+
+/*
+ * See if a pattern matches in a case statement.
+ */
+
+int
+casematch(union node *pattern, const char *val)
+{
+       struct stackmark smark;
+       int result;
+       char *p;
+
+       setstackmark(&smark);
+       argbackq = pattern->narg.backquote;
+       STARTSTACKSTR(expdest);
+       ifslastp = NULL;
+       argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
+       STPUTC('\0', expdest);
+       p = grabstackstr(expdest);
+       result = patmatch(p, val, 0);
+       popstackmark(&smark);
+       return result;
+}
+
+/*
+ * Our own itoa().
+ */
+
+static char *
+cvtnum(int num, char *buf)
+{
+       char temp[32];
+       int neg = num < 0;
+       char *p = temp + 31;
+
+       temp[31] = '\0';
+
+       do {
+               *--p = num % 10 + '0';
+       } while ((num /= 10) != 0);
+
+       if (neg)
+               *--p = '-';
+
+       STPUTS(p, buf);
+       return buf;
+}
+
+/*
+ * Do most of the work for wordexp(3).
+ */
+
+int
+wordexpcmd(int argc, char **argv)
+{
+       size_t len;
+       int i;
+
+       out1fmt("%08x", argc - 1);
+       for (i = 1, len = 0; i < argc; i++)
+               len += strlen(argv[i]);
+       out1fmt("%08x", (int)len);
+       for (i = 1; i < argc; i++)
+               outbin(argv[i], strlen(argv[i]) + 1, out1);
+        return (0);
+}
diff --git a/sh/expand.h b/sh/expand.h
new file mode 100644 (file)
index 0000000..93c80f3
--- /dev/null
@@ -0,0 +1,61 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)expand.h    8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+struct strlist {
+       struct strlist *next;
+       char *text;
+};
+
+
+struct arglist {
+       struct strlist *list;
+       struct strlist **lastp;
+};
+
+/*
+ * expandarg() flags
+ */
+#define EXP_FULL       0x1     /* perform word splitting & file globbing */
+#define EXP_TILDE      0x2     /* do normal tilde expansion */
+#define        EXP_VARTILDE    0x4     /* expand tildes in an assignment */
+#define EXP_CASE       0x10    /* keeps quotes around for CASE pattern */
+#define EXP_SPLIT_LIT  0x20    /* IFS split literal text ${v+-a b c} */
+#define EXP_LIT_QUOTED 0x40    /* for EXP_SPLIT_LIT, start off quoted */
+
+
+union node;
+void expandarg(union node *, struct arglist *, int);
+void rmescapes(char *);
+int casematch(union node *, const char *);
diff --git a/sh/funcs/cmv b/sh/funcs/cmv
new file mode 100644 (file)
index 0000000..2b92c60
--- /dev/null
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+#-
+# Copyright (c) 1991, 1993
+#      The Regents of the University of California.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 4. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#      @(#)cmv 8.2 (Berkeley) 5/4/95
+# $FreeBSD$
+
+# Conditional move--don't replace an existing file.
+
+cmv() {
+       if test $# != 2
+       then    echo "cmv: arg count"
+               return 2
+       fi
+       if test -f "$2" -o -w "$2"
+       then    echo "$2 exists"
+               return 2
+       fi
+       /bin/mv "$1" "$2"
+}
diff --git a/sh/funcs/dirs b/sh/funcs/dirs
new file mode 100644 (file)
index 0000000..95f6857
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+#-
+# Copyright (c) 1991, 1993
+#      The Regents of the University of California.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 4. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#      @(#)dirs        8.2 (Berkeley) 5/4/95
+# $FreeBSD$
+
+# pushd, popd, and dirs --- written by Chris Bertin
+# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris
+# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW
+
+pushd () {
+       SAVE=`pwd`
+       if [ "$1" = "" ] 
+       then    if [ "$DSTACK" = "" ]
+               then    echo "pushd: directory stack empty."
+                       return 1
+               fi
+               set $DSTACK
+               cd $1 || return
+               shift 1
+               DSTACK="$*"
+       else    cd $1 > /dev/null || return
+       fi
+       DSTACK="$SAVE $DSTACK"
+       dirs
+}
+
+popd () {
+       if [ "$DSTACK" = "" ] 
+       then    echo "popd: directory stack empty."
+               return 1
+       fi
+       set $DSTACK
+       cd $1
+       shift
+       DSTACK=$*
+       dirs
+}
+
+dirs () {
+       echo "`pwd` $DSTACK"
+       return 0
+}
diff --git a/sh/funcs/login b/sh/funcs/login
new file mode 100644 (file)
index 0000000..68802a0
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+#-
+# Copyright (c) 1991, 1993
+#      The Regents of the University of California.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 4. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#      @(#)login       8.2 (Berkeley) 5/4/95
+# $FreeBSD$
+
+# replaces the login builtin in the BSD shell
+login () exec login "$@"
diff --git a/sh/funcs/newgrp b/sh/funcs/newgrp
new file mode 100644 (file)
index 0000000..57ac1ce
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+#-
+# Copyright (c) 1991, 1993
+#      The Regents of the University of California.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 4. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#      @(#)newgrp      8.2 (Berkeley) 5/4/95
+# $FreeBSD$
+
+newgrp() exec newgrp "$@"
diff --git a/sh/funcs/popd b/sh/funcs/popd
new file mode 100644 (file)
index 0000000..bc1cd5c
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+#-
+# Copyright (c) 1991, 1993
+#      The Regents of the University of California.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 4. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#      @(#)popd        8.2 (Berkeley) 5/4/95
+# $FreeBSD$
+
+# pushd, popd, and dirs --- written by Chris Bertin
+# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris
+# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW
+
+pushd () {
+       SAVE=`pwd`
+       if [ "$1" = "" ] 
+       then    if [ "$DSTACK" = "" ]
+               then    echo "pushd: directory stack empty."
+                       return 1
+               fi
+               set $DSTACK
+               cd $1 || return
+               shift 1
+               DSTACK="$*"
+       else    cd $1 > /dev/null || return
+       fi
+       DSTACK="$SAVE $DSTACK"
+       dirs
+}
+
+popd () {
+       if [ "$DSTACK" = "" ] 
+       then    echo "popd: directory stack empty."
+               return 1
+       fi
+       set $DSTACK
+       cd $1
+       shift
+       DSTACK=$*
+       dirs
+}
+
+dirs () {
+       echo "`pwd` $DSTACK"
+       return 0
+}
diff --git a/sh/funcs/pushd b/sh/funcs/pushd
new file mode 100644 (file)
index 0000000..9e31f2a
--- /dev/null
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+#-
+# Copyright (c) 1991, 1993
+#      The Regents of the University of California.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 4. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#      @(#)pushd       8.2 (Berkeley) 5/4/95
+# $FreeBSD$
+
+# pushd, popd, and dirs --- written by Chris Bertin
+# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris
+# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW
+
+pushd () {
+       SAVE=`pwd`
+       if [ "$1" = "" ] 
+       then    if [ "$DSTACK" = "" ]
+               then    echo "pushd: directory stack empty."
+                       return 1
+               fi
+               set $DSTACK
+               cd $1 || return
+               shift 1
+               DSTACK="$*"
+       else    cd $1 > /dev/null || return
+       fi
+       DSTACK="$SAVE $DSTACK"
+       dirs
+}
+
+popd () {
+       if [ "$DSTACK" = "" ] 
+       then    echo "popd: directory stack empty."
+               return 1
+       fi
+       set $DSTACK
+       cd $1
+       shift
+       DSTACK=$*
+       dirs
+}
+
+dirs () {
+       echo "`pwd` $DSTACK"
+       return 0
+}
diff --git a/sh/funcs/suspend b/sh/funcs/suspend
new file mode 100644 (file)
index 0000000..1749298
--- /dev/null
@@ -0,0 +1,39 @@
+#-
+# Copyright (c) 1991, 1993
+#      The Regents of the University of California.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 4. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#      @(#)suspend     8.2 (Berkeley) 5/4/95
+# $FreeBSD$
+
+suspend() {
+       local -
+       set +m
+       kill -TSTP 0
+}
diff --git a/sh/histedit.c b/sh/histedit.c
new file mode 100644 (file)
index 0000000..7295c3f
--- /dev/null
@@ -0,0 +1,502 @@
+/*-
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+/*
+ * Editline and history functions (and glue).
+ */
+#include "shell.h"
+#include "parser.h"
+#include "var.h"
+#include "options.h"
+#include "main.h"
+#include "output.h"
+#include "mystring.h"
+#ifndef NO_HISTORY
+#include "myhistedit.h"
+#include "error.h"
+#include "eval.h"
+#include "memalloc.h"
+#include "builtins.h"
+
+#define MAXHISTLOOPS   4       /* max recursions through fc */
+#define DEFEDITOR      "ed"    /* default editor *should* be $EDITOR */
+
+History *hist; /* history cookie */
+EditLine *el;  /* editline cookie */
+int displayhist;
+static FILE *el_in, *el_out, *el_err;
+
+static char *fc_replace(const char *, char *, char *);
+static int not_fcnumber(const char *);
+static int str_to_event(const char *, int);
+
+/*
+ * Set history and editing status.  Called whenever the status may
+ * have changed (figures out what to do).
+ */
+void
+histedit(void)
+{
+
+#define editing (Eflag || Vflag)
+
+       if (iflag) {
+               if (!hist) {
+                       /*
+                        * turn history on
+                        */
+                       INTOFF;
+                       hist = history_init();
+                       INTON;
+
+                       if (hist != NULL)
+                               sethistsize(histsizeval());
+                       else
+                               out2fmt_flush("sh: can't initialize history\n");
+               }
+               if (editing && !el && isatty(0)) { /* && isatty(2) ??? */
+                       /*
+                        * turn editing on
+                        */
+                       char *term;
+
+                       INTOFF;
+                       if (el_in == NULL)
+                               el_in = fdopen(0, "r");
+                       if (el_err == NULL)
+                               el_err = fdopen(1, "w");
+                       if (el_out == NULL)
+                               el_out = fdopen(2, "w");
+                       if (el_in == NULL || el_err == NULL || el_out == NULL)
+                               goto bad;
+                       term = lookupvar("TERM");
+                       if (term)
+                               setenv("TERM", term, 1);
+                       else
+                               unsetenv("TERM");
+                       el = el_init(arg0, el_in, el_out, el_err);
+                       if (el != NULL) {
+                               if (hist)
+                                       el_set(el, EL_HIST, history, hist);
+                               el_set(el, EL_PROMPT, getprompt);
+                               el_set(el, EL_ADDFN, "sh-complete",
+                                   "Filename completion",
+                                   _el_fn_sh_complete);
+                       } else {
+bad:
+                               out2fmt_flush("sh: can't initialize editing\n");
+                       }
+                       INTON;
+               } else if (!editing && el) {
+                       INTOFF;
+                       el_end(el);
+                       el = NULL;
+                       INTON;
+               }
+               if (el) {
+                       if (Vflag)
+                               el_set(el, EL_EDITOR, "vi");
+                       else if (Eflag)
+                               el_set(el, EL_EDITOR, "emacs");
+                       el_set(el, EL_BIND, "^I", "sh-complete", NULL);
+                       el_source(el, NULL);
+               }
+       } else {
+               INTOFF;
+               if (el) {       /* no editing if not interactive */
+                       el_end(el);
+                       el = NULL;
+               }
+               if (hist) {
+                       history_end(hist);
+                       hist = NULL;
+               }
+               INTON;
+       }
+}
+
+
+void
+sethistsize(const char *hs)
+{
+       int histsize;
+       HistEvent he;
+
+       if (hist != NULL) {
+               if (hs == NULL || !is_number(hs))
+                       histsize = 100;
+               else
+                       histsize = atoi(hs);
+               history(hist, &he, H_SETSIZE, histsize);
+               history(hist, &he, H_SETUNIQUE, 1);
+       }
+}
+
+void
+setterm(const char *term)
+{
+       if (rootshell && el != NULL && term != NULL)
+               el_set(el, EL_TERMINAL, term);
+}
+
+int
+histcmd(int argc, char **argv __unused)
+{
+       int ch;
+       const char *editor = NULL;
+       HistEvent he;
+       int lflg = 0, nflg = 0, rflg = 0, sflg = 0;
+       int i, retval;
+       const char *firststr, *laststr;
+       int first, last, direction;
+       char *pat = NULL, *repl = NULL;
+       static int active = 0;
+       struct jmploc jmploc;
+       struct jmploc *savehandler;
+       char editfilestr[PATH_MAX];
+       char *volatile editfile;
+       FILE *efp = NULL;
+       int oldhistnum;
+
+       if (hist == NULL)
+               error("history not active");
+
+       if (argc == 1)
+               error("missing history argument");
+
+       while (not_fcnumber(*argptr) && (ch = nextopt("e:lnrs")) != '\0')
+               switch ((char)ch) {
+               case 'e':
+                       editor = shoptarg;
+                       break;
+               case 'l':
+                       lflg = 1;
+                       break;
+               case 'n':
+                       nflg = 1;
+                       break;
+               case 'r':
+                       rflg = 1;
+                       break;
+               case 's':
+                       sflg = 1;
+                       break;
+               }
+
+       savehandler = handler;
+       /*
+        * If executing...
+        */
+       if (lflg == 0 || editor || sflg) {
+               lflg = 0;       /* ignore */
+               editfile = NULL;
+               /*
+                * Catch interrupts to reset active counter and
+                * cleanup temp files.
+                */
+               if (setjmp(jmploc.loc)) {
+                       active = 0;
+                       if (editfile)
+                               unlink(editfile);
+                       handler = savehandler;
+                       longjmp(handler->loc, 1);
+               }
+               handler = &jmploc;
+               if (++active > MAXHISTLOOPS) {
+                       active = 0;
+                       displayhist = 0;
+                       error("called recursively too many times");
+               }
+               /*
+                * Set editor.
+                */
+               if (sflg == 0) {
+                       if (editor == NULL &&
+                           (editor = bltinlookup("FCEDIT", 1)) == NULL &&
+                           (editor = bltinlookup("EDITOR", 1)) == NULL)
+                               editor = DEFEDITOR;
+                       if (editor[0] == '-' && editor[1] == '\0') {
+                               sflg = 1;       /* no edit */
+                               editor = NULL;
+                       }
+               }
+       }
+
+       /*
+        * If executing, parse [old=new] now
+        */
+       if (lflg == 0 && *argptr != NULL &&
+            ((repl = strchr(*argptr, '=')) != NULL)) {
+               pat = *argptr;
+               *repl++ = '\0';
+               argptr++;
+       }
+       /*
+        * determine [first] and [last]
+        */
+       if (*argptr == NULL) {
+               firststr = lflg ? "-16" : "-1";
+               laststr = "-1";
+       } else if (argptr[1] == NULL) {
+               firststr = argptr[0];
+               laststr = lflg ? "-1" : argptr[0];
+       } else if (argptr[2] == NULL) {
+               firststr = argptr[0];
+               laststr = argptr[1];
+       } else
+               error("too many arguments");
+       /*
+        * Turn into event numbers.
+        */
+       first = str_to_event(firststr, 0);
+       last = str_to_event(laststr, 1);
+
+       if (rflg) {
+               i = last;
+               last = first;
+               first = i;
+       }
+       /*
+        * XXX - this should not depend on the event numbers
+        * always increasing.  Add sequence numbers or offset
+        * to the history element in next (diskbased) release.
+        */
+       direction = first < last ? H_PREV : H_NEXT;
+
+       /*
+        * If editing, grab a temp file.
+        */
+       if (editor) {
+               int fd;
+               INTOFF;         /* easier */
+               sprintf(editfilestr, "%s/_shXXXXXX", _PATH_TMP);
+               if ((fd = mkstemp(editfilestr)) < 0)
+                       error("can't create temporary file %s", editfile);
+               editfile = editfilestr;
+               if ((efp = fdopen(fd, "w")) == NULL) {
+                       close(fd);
+                       error("Out of space");
+               }
+       }
+
+       /*
+        * Loop through selected history events.  If listing or executing,
+        * do it now.  Otherwise, put into temp file and call the editor
+        * after.
+        *
+        * The history interface needs rethinking, as the following
+        * convolutions will demonstrate.
+        */
+       history(hist, &he, H_FIRST);
+       retval = history(hist, &he, H_NEXT_EVENT, first);
+       for (;retval != -1; retval = history(hist, &he, direction)) {
+               if (lflg) {
+                       if (!nflg)
+                               out1fmt("%5d ", he.num);
+                       out1str(he.str);
+               } else {
+                       const char *s = pat ?
+                          fc_replace(he.str, pat, repl) : he.str;
+
+                       if (sflg) {
+                               if (displayhist) {
+                                       out2str(s);
+                                       flushout(out2);
+                               }
+                               evalstring(s, 0);
+                               if (displayhist && hist) {
+                                       /*
+                                        *  XXX what about recursive and
+                                        *  relative histnums.
+                                        */
+                                       oldhistnum = he.num;
+                                       history(hist, &he, H_ENTER, s);
+                                       /*
+                                        * XXX H_ENTER moves the internal
+                                        * cursor, set it back to the current
+                                        * entry.
+                                        */
+                                       retval = history(hist, &he,
+                                           H_NEXT_EVENT, oldhistnum);
+                               }
+                       } else
+                               fputs(s, efp);
+               }
+               /*
+                * At end?  (if we were to lose last, we'd sure be
+                * messed up).
+                */
+               if (he.num == last)
+                       break;
+       }
+       if (editor) {
+               char *editcmd;
+
+               fclose(efp);
+               editcmd = stalloc(strlen(editor) + strlen(editfile) + 2);
+               sprintf(editcmd, "%s %s", editor, editfile);
+               evalstring(editcmd, 0); /* XXX - should use no JC command */
+               INTON;
+               readcmdfile(editfile);  /* XXX - should read back - quick tst */
+               unlink(editfile);
+       }
+
+       if (lflg == 0 && active > 0)
+               --active;
+       if (displayhist)
+               displayhist = 0;
+       handler = savehandler;
+       return 0;
+}
+
+static char *
+fc_replace(const char *s, char *p, char *r)
+{
+       char *dest;
+       int plen = strlen(p);
+
+       STARTSTACKSTR(dest);
+       while (*s) {
+               if (*s == *p && strncmp(s, p, plen) == 0) {
+                       STPUTS(r, dest);
+                       s += plen;
+                       *p = '\0';      /* so no more matches */
+               } else
+                       STPUTC(*s++, dest);
+       }
+       STPUTC('\0', dest);
+       dest = grabstackstr(dest);
+
+       return (dest);
+}
+
+static int
+not_fcnumber(const char *s)
+{
+       if (s == NULL)
+               return (0);
+       if (*s == '-')
+               s++;
+       return (!is_number(s));
+}
+
+static int
+str_to_event(const char *str, int last)
+{
+       HistEvent he;
+       const char *s = str;
+       int relative = 0;
+       int i, retval;
+
+       retval = history(hist, &he, H_FIRST);
+       switch (*s) {
+       case '-':
+               relative = 1;
+               /*FALLTHROUGH*/
+       case '+':
+               s++;
+       }
+       if (is_number(s)) {
+               i = atoi(s);
+               if (relative) {
+                       while (retval != -1 && i--) {
+                               retval = history(hist, &he, H_NEXT);
+                       }
+                       if (retval == -1)
+                               retval = history(hist, &he, H_LAST);
+               } else {
+                       retval = history(hist, &he, H_NEXT_EVENT, i);
+                       if (retval == -1) {
+                               /*
+                                * the notion of first and last is
+                                * backwards to that of the history package
+                                */
+                               retval = history(hist, &he, last ? H_FIRST : H_LAST);
+                       }
+               }
+               if (retval == -1)
+                       error("history number %s not found (internal error)",
+                              str);
+       } else {
+               /*
+                * pattern
+                */
+               retval = history(hist, &he, H_PREV_STR, str);
+               if (retval == -1)
+                       error("history pattern not found: %s", str);
+       }
+       return (he.num);
+}
+
+int
+bindcmd(int argc, char **argv)
+{
+
+       if (el == NULL)
+               error("line editing is disabled");
+       return (el_parse(el, argc, __DECONST(const char **, argv)));
+}
+
+#else
+#include "error.h"
+
+int
+histcmd(int argc, char **argv)
+{
+
+       error("not compiled with history support");
+       /*NOTREACHED*/
+       return (0);
+}
+
+int
+bindcmd(int argc, char **argv)
+{
+
+       error("not compiled with line editing support");
+       return (0);
+}
+#endif
diff --git a/sh/input.c b/sh/input.c
new file mode 100644 (file)
index 0000000..a0fd01d
--- /dev/null
@@ -0,0 +1,525 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)input.c    8.3 (Berkeley) 6/9/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdio.h>     /* defines BUFSIZ */
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * This file implements the input routines used by the parser.
+ */
+
+#include "shell.h"
+#include "redir.h"
+#include "syntax.h"
+#include "input.h"
+#include "output.h"
+#include "options.h"
+#include "memalloc.h"
+#include "error.h"
+#include "alias.h"
+#include "parser.h"
+#include "myhistedit.h"
+#include "trap.h"
+
+#define EOF_NLEFT -99          /* value of parsenleft when EOF pushed back */
+
+struct strpush {
+       struct strpush *prev;   /* preceding string on stack */
+       const char *prevstring;
+       int prevnleft;
+       int prevlleft;
+       struct alias *ap;       /* if push was associated with an alias */
+};
+
+/*
+ * The parsefile structure pointed to by the global variable parsefile
+ * contains information about the current file being read.
+ */
+
+struct parsefile {
+       struct parsefile *prev; /* preceding file on stack */
+       int linno;              /* current line */
+       int fd;                 /* file descriptor (or -1 if string) */
+       int nleft;              /* number of chars left in this line */
+       int lleft;              /* number of lines left in this buffer */
+       const char *nextc;      /* next char in buffer */
+       char *buf;              /* input buffer */
+       struct strpush *strpush; /* for pushing strings at this level */
+       struct strpush basestrpush; /* so pushing one is fast */
+};
+
+
+int plinno = 1;                        /* input line number */
+int parsenleft;                        /* copy of parsefile->nleft */
+static int parselleft;         /* copy of parsefile->lleft */
+const char *parsenextc;                /* copy of parsefile->nextc */
+static char basebuf[BUFSIZ + 1];/* buffer for top level input file */
+static struct parsefile basepf = {     /* top level input file */
+       .nextc = basebuf,
+       .buf = basebuf
+};
+static struct parsefile *parsefile = &basepf;  /* current input file */
+int whichprompt;               /* 1 == PS1, 2 == PS2 */
+
+#ifndef __APPLE__
+EditLine *el;                  /* cookie for editline package */
+#endif /* !__APPLE__ */
+
+static void pushfile(void);
+static int preadfd(void);
+static void popstring(void);
+
+void
+resetinput(void)
+{
+       popallfiles();
+       parselleft = parsenleft = 0;    /* clear input buffer */
+}
+
+
+
+/*
+ * Read a character from the script, returning PEOF on end of file.
+ * Nul characters in the input are silently discarded.
+ */
+
+int
+pgetc(void)
+{
+       return pgetc_macro();
+}
+
+
+static int
+preadfd(void)
+{
+       int nr;
+       parsenextc = parsefile->buf;
+
+retry:
+#ifndef NO_HISTORY
+       if (parsefile->fd == 0 && el) {
+               static const char *rl_cp;
+               static int el_len;
+
+               if (rl_cp == NULL) {
+                       el_resize(el);
+                       rl_cp = el_gets(el, &el_len);
+               }
+               if (rl_cp == NULL)
+                       nr = el_len == 0 ? 0 : -1;
+               else {
+                       nr = el_len;
+                       if (nr > BUFSIZ)
+                               nr = BUFSIZ;
+                       memcpy(parsefile->buf, rl_cp, nr);
+                       if (nr != el_len) {
+                               el_len -= nr;
+                               rl_cp += nr;
+                       } else
+                               rl_cp = NULL;
+               }
+       } else
+#endif
+               nr = read(parsefile->fd, parsefile->buf, BUFSIZ);
+
+       if (nr <= 0) {
+                if (nr < 0) {
+                        if (errno == EINTR)
+                                goto retry;
+                        if (parsefile->fd == 0 && errno == EWOULDBLOCK) {
+                                int flags = fcntl(0, F_GETFL, 0);
+                                if (flags >= 0 && flags & O_NONBLOCK) {
+                                        flags &=~ O_NONBLOCK;
+                                        if (fcntl(0, F_SETFL, flags) >= 0) {
+                                               out2fmt_flush("sh: turning off NDELAY mode\n");
+                                                goto retry;
+                                        }
+                                }
+                        }
+                }
+                nr = -1;
+       }
+       return nr;
+}
+
+/*
+ * Refill the input buffer and return the next input character:
+ *
+ * 1) If a string was pushed back on the input, pop it;
+ * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading
+ *    from a string so we can't refill the buffer, return EOF.
+ * 3) If there is more in this buffer, use it else call read to fill it.
+ * 4) Process input up to the next newline, deleting nul characters.
+ */
+
+int
+preadbuffer(void)
+{
+       char *p, *q;
+       int more;
+       char savec;
+
+       while (parsefile->strpush) {
+               /*
+                * Add a space to the end of an alias to ensure that the
+                * alias remains in use while parsing its last word.
+                * This avoids alias recursions.
+                */
+               if (parsenleft == -1 && parsefile->strpush->ap != NULL)
+                       return ' ';
+               popstring();
+               if (--parsenleft >= 0)
+                       return (*parsenextc++);
+       }
+       if (parsenleft == EOF_NLEFT || parsefile->buf == NULL)
+               return PEOF;
+       flushout(&output);
+       flushout(&errout);
+
+again:
+       if (parselleft <= 0) {
+               if ((parselleft = preadfd()) == -1) {
+                       parselleft = parsenleft = EOF_NLEFT;
+                       return PEOF;
+               }
+       }
+
+       q = p = parsefile->buf + (parsenextc - parsefile->buf);
+
+       /* delete nul characters */
+       for (more = 1; more;) {
+               switch (*p) {
+               case '\0':
+                       p++;    /* Skip nul */
+                       goto check;
+
+               case '\n':
+                       parsenleft = q - parsenextc;
+                       more = 0; /* Stop processing here */
+                       break;
+
+               default:
+                       break;
+               }
+
+               *q++ = *p++;
+check:
+               if (--parselleft <= 0) {
+                       parsenleft = q - parsenextc - 1;
+                       if (parsenleft < 0)
+                               goto again;
+                       *q = '\0';
+                       more = 0;
+               }
+       }
+
+       savec = *q;
+       *q = '\0';
+
+#ifndef NO_HISTORY
+       if (parsefile->fd == 0 && hist &&
+           parsenextc[strspn(parsenextc, " \t\n")] != '\0') {
+               HistEvent he;
+               INTOFF;
+               history(hist, &he, whichprompt == 1 ? H_ENTER : H_ADD,
+                   parsenextc);
+               INTON;
+       }
+#endif
+
+       if (vflag) {
+               out2str(parsenextc);
+               flushout(out2);
+       }
+
+       *q = savec;
+
+       return *parsenextc++;
+}
+
+/*
+ * Returns if we are certain we are at EOF. Does not cause any more input
+ * to be read from the outside world.
+ */
+
+int
+preadateof(void)
+{
+       if (parsenleft > 0)
+               return 0;
+       if (parsefile->strpush)
+               return 0;
+       if (parsenleft == EOF_NLEFT || parsefile->buf == NULL)
+               return 1;
+       return 0;
+}
+
+/*
+ * Undo the last call to pgetc.  Only one character may be pushed back.
+ * PEOF may be pushed back.
+ */
+
+void
+pungetc(void)
+{
+       parsenleft++;
+       parsenextc--;
+}
+
+/*
+ * Push a string back onto the input at this current parsefile level.
+ * We handle aliases this way.
+ */
+void
+pushstring(const char *s, int len, struct alias *ap)
+{
+       struct strpush *sp;
+
+       INTOFF;
+/*out2fmt_flush("*** calling pushstring: %s, %d\n", s, len);*/
+       if (parsefile->strpush) {
+               sp = ckmalloc(sizeof (struct strpush));
+               sp->prev = parsefile->strpush;
+               parsefile->strpush = sp;
+       } else
+               sp = parsefile->strpush = &(parsefile->basestrpush);
+       sp->prevstring = parsenextc;
+       sp->prevnleft = parsenleft;
+       sp->prevlleft = parselleft;
+       sp->ap = ap;
+       if (ap)
+               ap->flag |= ALIASINUSE;
+       parsenextc = s;
+       parsenleft = len;
+       INTON;
+}
+
+static void
+popstring(void)
+{
+       struct strpush *sp = parsefile->strpush;
+
+       INTOFF;
+       if (sp->ap) {
+               if (parsenextc != sp->ap->val &&
+                   (parsenextc[-1] == ' ' || parsenextc[-1] == '\t'))
+                       forcealias();
+               sp->ap->flag &= ~ALIASINUSE;
+       }
+       parsenextc = sp->prevstring;
+       parsenleft = sp->prevnleft;
+       parselleft = sp->prevlleft;
+/*out2fmt_flush("*** calling popstring: restoring to '%s'\n", parsenextc);*/
+       parsefile->strpush = sp->prev;
+       if (sp != &(parsefile->basestrpush))
+               ckfree(sp);
+       INTON;
+}
+
+/*
+ * Set the input to take input from a file.  If push is set, push the
+ * old input onto the stack first.
+ */
+
+void
+setinputfile(const char *fname, int push)
+{
+       int fd;
+       int fd2;
+
+       INTOFF;
+       if ((fd = open(fname, O_RDONLY | O_CLOEXEC)) < 0)
+               error("cannot open %s: %s", fname, strerror(errno));
+       if (fd < 10) {
+               fd2 = fcntl(fd, F_DUPFD_CLOEXEC, 10);
+               close(fd);
+               if (fd2 < 0)
+                       error("Out of file descriptors");
+               fd = fd2;
+       }
+       setinputfd(fd, push);
+       INTON;
+}
+
+
+/*
+ * Like setinputfile, but takes an open file descriptor (which should have
+ * its FD_CLOEXEC flag already set).  Call this with interrupts off.
+ */
+
+void
+setinputfd(int fd, int push)
+{
+       if (push) {
+               pushfile();
+               parsefile->buf = ckmalloc(BUFSIZ + 1);
+       }
+       if (parsefile->fd > 0)
+               close(parsefile->fd);
+       parsefile->fd = fd;
+       if (parsefile->buf == NULL)
+               parsefile->buf = ckmalloc(BUFSIZ + 1);
+       parselleft = parsenleft = 0;
+       plinno = 1;
+}
+
+
+/*
+ * Like setinputfile, but takes input from a string.
+ */
+
+void
+setinputstring(const char *string, int push)
+{
+       INTOFF;
+       if (push)
+               pushfile();
+       parsenextc = string;
+       parselleft = parsenleft = strlen(string);
+       parsefile->buf = NULL;
+       plinno = 1;
+       INTON;
+}
+
+
+
+/*
+ * To handle the "." command, a stack of input files is used.  Pushfile
+ * adds a new entry to the stack and popfile restores the previous level.
+ */
+
+static void
+pushfile(void)
+{
+       struct parsefile *pf;
+
+       parsefile->nleft = parsenleft;
+       parsefile->lleft = parselleft;
+       parsefile->nextc = parsenextc;
+       parsefile->linno = plinno;
+       pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile));
+       pf->prev = parsefile;
+       pf->fd = -1;
+       pf->strpush = NULL;
+       pf->basestrpush.prev = NULL;
+       parsefile = pf;
+}
+
+
+void
+popfile(void)
+{
+       struct parsefile *pf = parsefile;
+
+       INTOFF;
+       if (pf->fd >= 0)
+               close(pf->fd);
+       if (pf->buf)
+               ckfree(pf->buf);
+       while (pf->strpush)
+               popstring();
+       parsefile = pf->prev;
+       ckfree(pf);
+       parsenleft = parsefile->nleft;
+       parselleft = parsefile->lleft;
+       parsenextc = parsefile->nextc;
+       plinno = parsefile->linno;
+       INTON;
+}
+
+
+/*
+ * Return current file (to go back to it later using popfilesupto()).
+ */
+
+struct parsefile *
+getcurrentfile(void)
+{
+       return parsefile;
+}
+
+
+/*
+ * Pop files until the given file is on top again. Useful for regular
+ * builtins that read shell commands from files or strings.
+ * If the given file is not an active file, an error is raised.
+ */
+
+void
+popfilesupto(struct parsefile *file)
+{
+       while (parsefile != file && parsefile != &basepf)
+               popfile();
+       if (parsefile != file)
+               error("popfilesupto() misused");
+}
+
+/*
+ * Return to top level.
+ */
+
+void
+popallfiles(void)
+{
+       while (parsefile != &basepf)
+               popfile();
+}
+
+
+
+/*
+ * Close the file(s) that the shell is reading commands from.  Called
+ * after a fork is done.
+ */
+
+void
+closescript(void)
+{
+       popallfiles();
+       if (parsefile->fd > 0) {
+               close(parsefile->fd);
+               parsefile->fd = 0;
+       }
+}
diff --git a/sh/input.h b/sh/input.h
new file mode 100644 (file)
index 0000000..cb0af77
--- /dev/null
@@ -0,0 +1,65 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)input.h     8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+/* PEOF (the end of file marker) is defined in syntax.h */
+
+/*
+ * The input line number.  Input.c just defines this variable, and saves
+ * and restores it when files are pushed and popped.  The user of this
+ * package must set its value.
+ */
+extern int plinno;
+extern int parsenleft;         /* number of characters left in input buffer */
+extern const char *parsenextc; /* next character in input buffer */
+
+struct alias;
+struct parsefile;
+
+void resetinput(void);
+int pgetc(void);
+int preadbuffer(void);
+int preadateof(void);
+void pungetc(void);
+void pushstring(const char *, int, struct alias *);
+void setinputfile(const char *, int);
+void setinputfd(int, int);
+void setinputstring(const char *, int);
+void popfile(void);
+struct parsefile *getcurrentfile(void);
+void popfilesupto(struct parsefile *);
+void popallfiles(void);
+void closescript(void);
+
+#define pgetc_macro()  (--parsenleft >= 0? *parsenextc++ : preadbuffer())
diff --git a/sh/jobs.c b/sh/jobs.c
new file mode 100644 (file)
index 0000000..b531231
--- /dev/null
+++ b/sh/jobs.c
@@ -0,0 +1,1515 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)jobs.c     8.5 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "shell.h"
+#if JOBS
+#include <termios.h>
+#undef CEOF                    /* syntax.h redefines this */
+#endif
+#include "redir.h"
+#include "exec.h"
+#include "show.h"
+#include "main.h"
+#include "parser.h"
+#include "nodes.h"
+#include "jobs.h"
+#include "options.h"
+#include "trap.h"
+#include "syntax.h"
+#include "input.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "var.h"
+#include "builtins.h"
+
+
+static struct job *jobtab;     /* array of jobs */
+static int njobs;              /* size of array */
+static pid_t backgndpid = -1;  /* pid of last background process */
+static struct job *bgjob = NULL; /* last background process */
+#if JOBS
+static struct job *jobmru;     /* most recently used job list */
+static pid_t initialpgrp;      /* pgrp of shell on invocation */
+#endif
+static int ttyfd = -1;
+
+/* mode flags for dowait */
+#define DOWAIT_BLOCK   0x1 /* wait until a child exits */
+#define DOWAIT_SIG     0x2 /* if DOWAIT_BLOCK, abort on signal */
+#define DOWAIT_SIG_TRAP        0x4 /* if DOWAIT_SIG, abort on trapped signal only */
+
+#if JOBS
+static void restartjob(struct job *);
+#endif
+static void freejob(struct job *);
+static int waitcmdloop(struct job *);
+static struct job *getjob_nonotfound(const char *);
+static struct job *getjob(const char *);
+pid_t killjob(const char *, int);
+static pid_t dowait(int, struct job *);
+static void checkzombies(void);
+static void cmdtxt(union node *);
+static void cmdputs(const char *);
+#if JOBS
+static void setcurjob(struct job *);
+static void deljob(struct job *);
+static struct job *getcurjob(struct job *);
+#endif
+static void printjobcmd(struct job *);
+static void showjob(struct job *, int);
+
+
+/*
+ * Turn job control on and off.
+ */
+
+static int jobctl;
+
+#if JOBS
+static void
+jobctl_notty(void)
+{
+       if (ttyfd >= 0) {
+               close(ttyfd);
+               ttyfd = -1;
+       }
+       if (!iflag) {
+               setsignal(SIGTSTP);
+               setsignal(SIGTTOU);
+               setsignal(SIGTTIN);
+               jobctl = 1;
+               return;
+       }
+       out2fmt_flush("sh: can't access tty; job control turned off\n");
+       mflag = 0;
+}
+
+void
+setjobctl(int on)
+{
+       int i;
+
+       if (on == jobctl || rootshell == 0)
+               return;
+       if (on) {
+               if (ttyfd != -1)
+                       close(ttyfd);
+               if ((ttyfd = open(_PATH_TTY, O_RDWR | O_CLOEXEC)) < 0) {
+                       i = 0;
+                       while (i <= 2 && !isatty(i))
+                               i++;
+                       if (i > 2 ||
+                           (ttyfd = fcntl(i, F_DUPFD_CLOEXEC, 10)) < 0) {
+                               jobctl_notty();
+                               return;
+                       }
+               }
+               if (ttyfd < 10) {
+                       /*
+                        * Keep our TTY file descriptor out of the way of
+                        * the user's redirections.
+                        */
+                       if ((i = fcntl(ttyfd, F_DUPFD_CLOEXEC, 10)) < 0) {
+                               jobctl_notty();
+                               return;
+                       }
+                       close(ttyfd);
+                       ttyfd = i;
+               }
+               do { /* while we are in the background */
+                       initialpgrp = tcgetpgrp(ttyfd);
+                       if (initialpgrp < 0) {
+                               jobctl_notty();
+                               return;
+                       }
+                       if (initialpgrp != getpgrp()) {
+                               if (!iflag) {
+                                       initialpgrp = -1;
+                                       jobctl_notty();
+                                       return;
+                               }
+                               kill(0, SIGTTIN);
+                               continue;
+                       }
+               } while (0);
+               setsignal(SIGTSTP);
+               setsignal(SIGTTOU);
+               setsignal(SIGTTIN);
+               setpgid(0, rootpid);
+               tcsetpgrp(ttyfd, rootpid);
+       } else { /* turning job control off */
+               setpgid(0, initialpgrp);
+               if (ttyfd >= 0) {
+                       tcsetpgrp(ttyfd, initialpgrp);
+                       close(ttyfd);
+                       ttyfd = -1;
+               }
+               setsignal(SIGTSTP);
+               setsignal(SIGTTOU);
+               setsignal(SIGTTIN);
+       }
+       jobctl = on;
+}
+#endif
+
+
+#if JOBS
+int
+fgcmd(int argc __unused, char **argv __unused)
+{
+       struct job *jp;
+       pid_t pgrp;
+       int status;
+
+       nextopt("");
+       jp = getjob(*argptr);
+       if (jp->jobctl == 0)
+               error("job not created under job control");
+       printjobcmd(jp);
+       flushout(&output);
+       pgrp = jp->ps[0].pid;
+       if (ttyfd >= 0)
+               tcsetpgrp(ttyfd, pgrp);
+       restartjob(jp);
+       jp->foreground = 1;
+       INTOFF;
+       status = waitforjob(jp, (int *)NULL);
+       INTON;
+       return status;
+}
+
+
+int
+bgcmd(int argc __unused, char **argv __unused)
+{
+       struct job *jp;
+
+       nextopt("");
+       do {
+               jp = getjob(*argptr);
+               if (jp->jobctl == 0)
+                       error("job not created under job control");
+               if (jp->state == JOBDONE)
+                       continue;
+               restartjob(jp);
+               jp->foreground = 0;
+               out1fmt("[%td] ", jp - jobtab + 1);
+               printjobcmd(jp);
+       } while (*argptr != NULL && *++argptr != NULL);
+       return 0;
+}
+
+
+static void
+restartjob(struct job *jp)
+{
+       struct procstat *ps;
+       int i;
+
+       if (jp->state == JOBDONE)
+               return;
+       setcurjob(jp);
+       INTOFF;
+       kill(-jp->ps[0].pid, SIGCONT);
+       for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) {
+               if (WIFSTOPPED(ps->status)) {
+                       ps->status = -1;
+                       jp->state = 0;
+               }
+       }
+       INTON;
+}
+#endif
+
+
+int
+jobscmd(int argc __unused, char *argv[] __unused)
+{
+       char *id;
+       int ch, mode;
+
+       mode = SHOWJOBS_DEFAULT;
+       while ((ch = nextopt("lps")) != '\0') {
+               switch (ch) {
+               case 'l':
+                       mode = SHOWJOBS_VERBOSE;
+                       break;
+               case 'p':
+                       mode = SHOWJOBS_PGIDS;
+                       break;
+               case 's':
+                       mode = SHOWJOBS_PIDS;
+                       break;
+               }
+       }
+
+       if (*argptr == NULL)
+               showjobs(0, mode);
+       else
+               while ((id = *argptr++) != NULL)
+                       showjob(getjob(id), mode);
+
+       return (0);
+}
+
+static void
+printjobcmd(struct job *jp)
+{
+       struct procstat *ps;
+       int i;
+
+       for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) {
+               out1str(ps->cmd);
+               if (i > 0)
+                       out1str(" | ");
+       }
+       out1c('\n');
+}
+
+static void
+showjob(struct job *jp, int mode)
+{
+       char s[64];
+       char statestr[64];
+       const char *sigstr;
+       struct procstat *ps;
+       struct job *j;
+       int col, curr, i, jobno, prev, procno;
+       char c;
+
+       procno = (mode == SHOWJOBS_PGIDS) ? 1 : jp->nprocs;
+       jobno = jp - jobtab + 1;
+       curr = prev = 0;
+#if JOBS
+       if ((j = getcurjob(NULL)) != NULL) {
+               curr = j - jobtab + 1;
+               if ((j = getcurjob(j)) != NULL)
+                       prev = j - jobtab + 1;
+       }
+#endif
+       ps = jp->ps + jp->nprocs - 1;
+       if (jp->state == 0) {
+               strcpy(statestr, "Running");
+#if JOBS
+       } else if (jp->state == JOBSTOPPED) {
+               while (!WIFSTOPPED(ps->status) && ps > jp->ps)
+                       ps--;
+               if (WIFSTOPPED(ps->status))
+                       i = WSTOPSIG(ps->status);
+               else
+                       i = -1;
+               sigstr = strsignal(i);
+               if (sigstr != NULL)
+                       strcpy(statestr, sigstr);
+               else
+                       strcpy(statestr, "Suspended");
+#endif
+       } else if (WIFEXITED(ps->status)) {
+               if (WEXITSTATUS(ps->status) == 0)
+                       strcpy(statestr, "Done");
+               else
+                       fmtstr(statestr, 64, "Done(%d)",
+                           WEXITSTATUS(ps->status));
+       } else {
+               i = WTERMSIG(ps->status);
+               sigstr = strsignal(i);
+               if (sigstr != NULL)
+                       strcpy(statestr, sigstr);
+               else
+                       strcpy(statestr, "Unknown signal");
+               if (WCOREDUMP(ps->status))
+                       strcat(statestr, " (core dumped)");
+       }
+
+       for (ps = jp->ps ; procno > 0 ; ps++, procno--) { /* for each process */
+               if (mode == SHOWJOBS_PIDS || mode == SHOWJOBS_PGIDS) {
+                       out1fmt("%d\n", (int)ps->pid);
+                       continue;
+               }
+               if (mode != SHOWJOBS_VERBOSE && ps != jp->ps)
+                       continue;
+               if (jobno == curr && ps == jp->ps)
+                       c = '+';
+               else if (jobno == prev && ps == jp->ps)
+                       c = '-';
+               else
+                       c = ' ';
+               if (ps == jp->ps)
+                       fmtstr(s, 64, "[%d] %c ", jobno, c);
+               else
+                       fmtstr(s, 64, "    %c ", c);
+               out1str(s);
+               col = strlen(s);
+               if (mode == SHOWJOBS_VERBOSE) {
+                       fmtstr(s, 64, "%d ", (int)ps->pid);
+                       out1str(s);
+                       col += strlen(s);
+               }
+               if (ps == jp->ps) {
+                       out1str(statestr);
+                       col += strlen(statestr);
+               }
+               do {
+                       out1c(' ');
+                       col++;
+               } while (col < 30);
+               if (mode == SHOWJOBS_VERBOSE) {
+                       out1str(ps->cmd);
+                       out1c('\n');
+               } else
+                       printjobcmd(jp);
+       }
+}
+
+/*
+ * Print a list of jobs.  If "change" is nonzero, only print jobs whose
+ * statuses have changed since the last call to showjobs.
+ *
+ * If the shell is interrupted in the process of creating a job, the
+ * result may be a job structure containing zero processes.  Such structures
+ * will be freed here.
+ */
+
+void
+showjobs(int change, int mode)
+{
+       int jobno;
+       struct job *jp;
+
+       TRACE(("showjobs(%d) called\n", change));
+       checkzombies();
+       for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) {
+               if (! jp->used)
+                       continue;
+               if (jp->nprocs == 0) {
+                       freejob(jp);
+                       continue;
+               }
+               if (change && ! jp->changed)
+                       continue;
+               showjob(jp, mode);
+               if (mode == SHOWJOBS_DEFAULT || mode == SHOWJOBS_VERBOSE) {
+                       jp->changed = 0;
+                       /* Hack: discard jobs for which $! has not been
+                        * referenced in interactive mode when they terminate.
+                        */
+                       if (jp->state == JOBDONE && !jp->remembered &&
+                                       (iflag || jp != bgjob)) {
+                               freejob(jp);
+                       }
+               }
+       }
+}
+
+
+/*
+ * Mark a job structure as unused.
+ */
+
+static void
+freejob(struct job *jp)
+{
+       struct procstat *ps;
+       int i;
+
+       INTOFF;
+       if (bgjob == jp)
+               bgjob = NULL;
+       for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) {
+               if (ps->cmd != nullstr)
+                       ckfree(ps->cmd);
+       }
+       if (jp->ps != &jp->ps0)
+               ckfree(jp->ps);
+       jp->used = 0;
+#if JOBS
+       deljob(jp);
+#endif
+       INTON;
+}
+
+
+
+int
+waitcmd(int argc __unused, char **argv __unused)
+{
+       struct job *job;
+       int retval;
+
+       nextopt("");
+       if (*argptr == NULL)
+               return (waitcmdloop(NULL));
+
+       do {
+               job = getjob_nonotfound(*argptr);
+               if (job == NULL)
+                       retval = 127;
+               else
+                       retval = waitcmdloop(job);
+               argptr++;
+       } while (*argptr != NULL);
+
+       return (retval);
+}
+
+static int
+waitcmdloop(struct job *job)
+{
+       int status, retval, sig;
+       struct job *jp;
+
+       /*
+        * Loop until a process is terminated or stopped, or a SIGINT is
+        * received.
+        */
+
+       do {
+               if (job != NULL) {
+                       if (job->state == JOBDONE) {
+                               status = job->ps[job->nprocs - 1].status;
+                               if (WIFEXITED(status))
+                                       retval = WEXITSTATUS(status);
+                               else
+                                       retval = WTERMSIG(status) + 128;
+                               if (! iflag || ! job->changed)
+                                       freejob(job);
+                               else {
+                                       job->remembered = 0;
+                                       if (job == bgjob)
+                                               bgjob = NULL;
+                               }
+                               return retval;
+                       }
+               } else {
+                       for (jp = jobtab ; jp < jobtab + njobs; jp++)
+                               if (jp->used && jp->state == JOBDONE) {
+                                       if (! iflag || ! jp->changed)
+                                               freejob(jp);
+                                       else {
+                                               jp->remembered = 0;
+                                               if (jp == bgjob)
+                                                       bgjob = NULL;
+                                       }
+                               }
+                       for (jp = jobtab ; ; jp++) {
+                               if (jp >= jobtab + njobs) {     /* no running procs */
+                                       return 0;
+                               }
+                               if (jp->used && jp->state == 0)
+                                       break;
+                       }
+               }
+       } while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, (struct job *)NULL) != -1);
+
+       sig = pendingsig_waitcmd;
+       pendingsig_waitcmd = 0;
+       return sig + 128;
+}
+
+
+
+int
+jobidcmd(int argc __unused, char **argv __unused)
+{
+       struct job *jp;
+       int i;
+
+       nextopt("");
+       jp = getjob(*argptr);
+       for (i = 0 ; i < jp->nprocs ; ) {
+               out1fmt("%d", (int)jp->ps[i].pid);
+               out1c(++i < jp->nprocs? ' ' : '\n');
+       }
+       return 0;
+}
+
+
+
+/*
+ * Convert a job name to a job structure.
+ */
+
+static struct job *
+getjob_nonotfound(const char *name)
+{
+       int jobno;
+       struct job *found, *jp;
+       size_t namelen;
+       pid_t pid;
+       int i;
+
+       if (name == NULL) {
+#if JOBS
+               name = "%+";
+#else
+               error("No current job");
+#endif
+       }
+       if (name[0] == '%') {
+               if (is_digit(name[1])) {
+                       jobno = number(name + 1);
+                       if (jobno > 0 && jobno <= njobs
+                        && jobtab[jobno - 1].used != 0)
+                               return &jobtab[jobno - 1];
+#if JOBS
+               } else if ((name[1] == '%' || name[1] == '+') &&
+                   name[2] == '\0') {
+                       if ((jp = getcurjob(NULL)) == NULL)
+                               error("No current job");
+                       return (jp);
+               } else if (name[1] == '-' && name[2] == '\0') {
+                       if ((jp = getcurjob(NULL)) == NULL ||
+                           (jp = getcurjob(jp)) == NULL)
+                               error("No previous job");
+                       return (jp);
+#endif
+               } else if (name[1] == '?') {
+                       found = NULL;
+                       for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) {
+                               if (jp->used && jp->nprocs > 0
+                                && strstr(jp->ps[0].cmd, name + 2) != NULL) {
+                                       if (found)
+                                               error("%s: ambiguous", name);
+                                       found = jp;
+                               }
+                       }
+                       if (found != NULL)
+                               return (found);
+               } else {
+                       namelen = strlen(name);
+                       found = NULL;
+                       for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) {
+                               if (jp->used && jp->nprocs > 0
+                                && strncmp(jp->ps[0].cmd, name + 1,
+                                namelen - 1) == 0) {
+                                       if (found)
+                                               error("%s: ambiguous", name);
+                                       found = jp;
+                               }
+                       }
+                       if (found)
+                               return found;
+               }
+       } else if (is_number(name)) {
+               pid = (pid_t)number(name);
+               for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) {
+                       if (jp->used && jp->nprocs > 0
+                        && jp->ps[jp->nprocs - 1].pid == pid)
+                               return jp;
+               }
+       }
+       return NULL;
+}
+
+
+static struct job *
+getjob(const char *name)
+{
+       struct job *jp;
+
+       jp = getjob_nonotfound(name);
+       if (jp == NULL)
+               error("No such job: %s", name);
+       return (jp);
+}
+
+
+int
+killjob(const char *name, int sig)
+{
+       struct job *jp;
+       int i, ret;
+
+       jp = getjob(name);
+       if (jp->state == JOBDONE)
+               return 0;
+       if (jp->jobctl)
+               return kill(-jp->ps[0].pid, sig);
+       ret = -1;
+       errno = ESRCH;
+       for (i = 0; i < jp->nprocs; i++)
+               if (jp->ps[i].status == -1 || WIFSTOPPED(jp->ps[i].status)) {
+                       if (kill(jp->ps[i].pid, sig) == 0)
+                               ret = 0;
+               } else
+                       ret = 0;
+       return ret;
+}
+
+/*
+ * Return a new job structure,
+ */
+
+struct job *
+makejob(union node *node __unused, int nprocs)
+{
+       int i;
+       struct job *jp;
+
+       for (i = njobs, jp = jobtab ; ; jp++) {
+               if (--i < 0) {
+                       INTOFF;
+                       if (njobs == 0) {
+                               jobtab = ckmalloc(4 * sizeof jobtab[0]);
+#if JOBS
+                               jobmru = NULL;
+#endif
+                       } else {
+                               jp = ckmalloc((njobs + 4) * sizeof jobtab[0]);
+                               memcpy(jp, jobtab, njobs * sizeof jp[0]);
+#if JOBS
+                               /* Relocate `next' pointers and list head */
+                               if (jobmru != NULL)
+                                       jobmru = &jp[jobmru - jobtab];
+                               for (i = 0; i < njobs; i++)
+                                       if (jp[i].next != NULL)
+                                               jp[i].next = &jp[jp[i].next -
+                                                   jobtab];
+#endif
+                               if (bgjob != NULL)
+                                       bgjob = &jp[bgjob - jobtab];
+                               /* Relocate `ps' pointers */
+                               for (i = 0; i < njobs; i++)
+                                       if (jp[i].ps == &jobtab[i].ps0)
+                                               jp[i].ps = &jp[i].ps0;
+                               ckfree(jobtab);
+                               jobtab = jp;
+                       }
+                       jp = jobtab + njobs;
+                       for (i = 4 ; --i >= 0 ; jobtab[njobs++].used = 0)
+                               ;
+                       INTON;
+                       break;
+               }
+               if (jp->used == 0)
+                       break;
+       }
+       INTOFF;
+       jp->state = 0;
+       jp->used = 1;
+       jp->changed = 0;
+       jp->nprocs = 0;
+       jp->foreground = 0;
+       jp->remembered = 0;
+#if JOBS
+       jp->jobctl = jobctl;
+       jp->next = NULL;
+#endif
+       if (nprocs > 1) {
+               jp->ps = ckmalloc(nprocs * sizeof (struct procstat));
+       } else {
+               jp->ps = &jp->ps0;
+       }
+       INTON;
+       TRACE(("makejob(%p, %d) returns %%%td\n", (void *)node, nprocs,
+           jp - jobtab + 1));
+       return jp;
+}
+
+#if JOBS
+static void
+setcurjob(struct job *cj)
+{
+       struct job *jp, *prev;
+
+       for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) {
+               if (jp == cj) {
+                       if (prev != NULL)
+                               prev->next = jp->next;
+                       else
+                               jobmru = jp->next;
+                       jp->next = jobmru;
+                       jobmru = cj;
+                       return;
+               }
+       }
+       cj->next = jobmru;
+       jobmru = cj;
+}
+
+static void
+deljob(struct job *j)
+{
+       struct job *jp, *prev;
+
+       for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) {
+               if (jp == j) {
+                       if (prev != NULL)
+                               prev->next = jp->next;
+                       else
+                               jobmru = jp->next;
+                       return;
+               }
+       }
+}
+
+/*
+ * Return the most recently used job that isn't `nj', and preferably one
+ * that is stopped.
+ */
+static struct job *
+getcurjob(struct job *nj)
+{
+       struct job *jp;
+
+       /* Try to find a stopped one.. */
+       for (jp = jobmru; jp != NULL; jp = jp->next)
+               if (jp->used && jp != nj && jp->state == JOBSTOPPED)
+                       return (jp);
+       /* Otherwise the most recently used job that isn't `nj' */
+       for (jp = jobmru; jp != NULL; jp = jp->next)
+               if (jp->used && jp != nj)
+                       return (jp);
+
+       return (NULL);
+}
+
+#endif
+
+/*
+ * Fork of a subshell.  If we are doing job control, give the subshell its
+ * own process group.  Jp is a job structure that the job is to be added to.
+ * N is the command that will be evaluated by the child.  Both jp and n may
+ * be NULL.  The mode parameter can be one of the following:
+ *     FORK_FG - Fork off a foreground process.
+ *     FORK_BG - Fork off a background process.
+ *     FORK_NOJOB - Like FORK_FG, but don't give the process its own
+ *                  process group even if job control is on.
+ *
+ * When job control is turned off, background processes have their standard
+ * input redirected to /dev/null (except for the second and later processes
+ * in a pipeline).
+ */
+
+pid_t
+forkshell(struct job *jp, union node *n, int mode)
+{
+       pid_t pid;
+       pid_t pgrp;
+
+       TRACE(("forkshell(%%%td, %p, %d) called\n", jp - jobtab, (void *)n,
+           mode));
+       INTOFF;
+       if (mode == FORK_BG && (jp == NULL || jp->nprocs == 0))
+               checkzombies();
+       flushall();
+       pid = fork();
+       if (pid == -1) {
+               TRACE(("Fork failed, errno=%d\n", errno));
+               INTON;
+               error("Cannot fork: %s", strerror(errno));
+       }
+       if (pid == 0) {
+               struct job *p;
+               int wasroot;
+               int i;
+
+               TRACE(("Child shell %d\n", (int)getpid()));
+               wasroot = rootshell;
+               rootshell = 0;
+               handler = &main_handler;
+               closescript();
+               INTON;
+               forcelocal = 0;
+               clear_traps();
+#if JOBS
+               jobctl = 0;             /* do job control only in root shell */
+               if (wasroot && mode != FORK_NOJOB && mflag) {
+                       if (jp == NULL || jp->nprocs == 0)
+                               pgrp = getpid();
+                       else
+                               pgrp = jp->ps[0].pid;
+                       if (setpgid(0, pgrp) == 0 && mode == FORK_FG &&
+                           ttyfd >= 0) {
+                               /*** this causes superfluous TIOCSPGRPS ***/
+                               if (tcsetpgrp(ttyfd, pgrp) < 0)
+                                       error("tcsetpgrp failed, errno=%d", errno);
+                       }
+                       setsignal(SIGTSTP);
+                       setsignal(SIGTTOU);
+               } else if (mode == FORK_BG) {
+                       ignoresig(SIGINT);
+                       ignoresig(SIGQUIT);
+                       if ((jp == NULL || jp->nprocs == 0) &&
+                           ! fd0_redirected_p ()) {
+                               close(0);
+                               if (open(_PATH_DEVNULL, O_RDONLY) != 0)
+                                       error("cannot open %s: %s",
+                                           _PATH_DEVNULL, strerror(errno));
+                       }
+               }
+#else
+               if (mode == FORK_BG) {
+                       ignoresig(SIGINT);
+                       ignoresig(SIGQUIT);
+                       if ((jp == NULL || jp->nprocs == 0) &&
+                           ! fd0_redirected_p ()) {
+                               close(0);
+                               if (open(_PATH_DEVNULL, O_RDONLY) != 0)
+                                       error("cannot open %s: %s",
+                                           _PATH_DEVNULL, strerror(errno));
+                       }
+               }
+#endif
+               INTOFF;
+               for (i = njobs, p = jobtab ; --i >= 0 ; p++)
+                       if (p->used)
+                               freejob(p);
+               INTON;
+               if (wasroot && iflag) {
+                       setsignal(SIGINT);
+                       setsignal(SIGQUIT);
+                       setsignal(SIGTERM);
+               }
+               return pid;
+       }
+       if (rootshell && mode != FORK_NOJOB && mflag) {
+               if (jp == NULL || jp->nprocs == 0)
+                       pgrp = pid;
+               else
+                       pgrp = jp->ps[0].pid;
+               setpgid(pid, pgrp);
+       }
+       if (mode == FORK_BG) {
+               if (bgjob != NULL && bgjob->state == JOBDONE &&
+                   !bgjob->remembered && !iflag)
+                       freejob(bgjob);
+               backgndpid = pid;               /* set $! */
+               bgjob = jp;
+       }
+       if (jp) {
+               struct procstat *ps = &jp->ps[jp->nprocs++];
+               ps->pid = pid;
+               ps->status = -1;
+               ps->cmd = nullstr;
+               if (iflag && rootshell && n)
+                       ps->cmd = commandtext(n);
+               jp->foreground = mode == FORK_FG;
+#if JOBS
+               setcurjob(jp);
+#endif
+       }
+       INTON;
+       TRACE(("In parent shell:  child = %d\n", (int)pid));
+       return pid;
+}
+
+
+pid_t
+vforkexecshell(struct job *jp, char **argv, char **envp, const char *path, int idx, int pip[2])
+{
+       pid_t pid;
+       struct jmploc jmploc;
+       struct jmploc *savehandler;
+
+       TRACE(("vforkexecshell(%%%td, %s, %p) called\n", jp - jobtab, argv[0],
+           (void *)pip));
+       INTOFF;
+       flushall();
+       savehandler = handler;
+       pid = vfork();
+       if (pid == -1) {
+               TRACE(("Vfork failed, errno=%d\n", errno));
+               INTON;
+               error("Cannot fork: %s", strerror(errno));
+       }
+       if (pid == 0) {
+               TRACE(("Child shell %d\n", (int)getpid()));
+               if (setjmp(jmploc.loc))
+                       _exit(exception == EXEXEC ? exerrno : 2);
+               if (pip != NULL) {
+                       close(pip[0]);
+                       if (pip[1] != 1) {
+                               dup2(pip[1], 1);
+                               close(pip[1]);
+                       }
+               }
+               handler = &jmploc;
+               shellexec(argv, envp, path, idx);
+       }
+       handler = savehandler;
+       if (jp) {
+               struct procstat *ps = &jp->ps[jp->nprocs++];
+               ps->pid = pid;
+               ps->status = -1;
+               ps->cmd = nullstr;
+               jp->foreground = 1;
+#if JOBS
+               setcurjob(jp);
+#endif
+       }
+       INTON;
+       TRACE(("In parent shell:  child = %d\n", (int)pid));
+       return pid;
+}
+
+
+/*
+ * Wait for job to finish.
+ *
+ * Under job control we have the problem that while a child process is
+ * running interrupts generated by the user are sent to the child but not
+ * to the shell.  This means that an infinite loop started by an inter-
+ * active user may be hard to kill.  With job control turned off, an
+ * interactive user may place an interactive program inside a loop.  If
+ * the interactive program catches interrupts, the user doesn't want
+ * these interrupts to also abort the loop.  The approach we take here
+ * is to have the shell ignore interrupt signals while waiting for a
+ * foreground process to terminate, and then send itself an interrupt
+ * signal if the child process was terminated by an interrupt signal.
+ * Unfortunately, some programs want to do a bit of cleanup and then
+ * exit on interrupt; unless these processes terminate themselves by
+ * sending a signal to themselves (instead of calling exit) they will
+ * confuse this approach.
+ */
+
+int
+waitforjob(struct job *jp, int *origstatus)
+{
+#if JOBS
+       int propagate_int = jp->jobctl && jp->foreground;
+#endif
+       int status;
+       int st;
+
+       INTOFF;
+       TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1));
+       while (jp->state == 0)
+               if (dowait(DOWAIT_BLOCK | (Tflag ? DOWAIT_SIG |
+                   DOWAIT_SIG_TRAP : 0), jp) == -1)
+                       dotrap();
+#if JOBS
+       if (jp->jobctl) {
+               if (ttyfd >= 0 && tcsetpgrp(ttyfd, rootpid) < 0)
+                       error("tcsetpgrp failed, errno=%d\n", errno);
+       }
+       if (jp->state == JOBSTOPPED)
+               setcurjob(jp);
+#endif
+       status = jp->ps[jp->nprocs - 1].status;
+       if (origstatus != NULL)
+               *origstatus = status;
+       /* convert to 8 bits */
+       if (WIFEXITED(status))
+               st = WEXITSTATUS(status);
+#if JOBS
+       else if (WIFSTOPPED(status))
+               st = WSTOPSIG(status) + 128;
+#endif
+       else
+               st = WTERMSIG(status) + 128;
+       if (! JOBS || jp->state == JOBDONE)
+               freejob(jp);
+       if (int_pending()) {
+               if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGINT)
+                       CLEAR_PENDING_INT;
+       }
+#if JOBS
+       else if (rootshell && propagate_int &&
+                       WIFSIGNALED(status) && WTERMSIG(status) == SIGINT)
+               kill(getpid(), SIGINT);
+#endif
+       INTON;
+       return st;
+}
+
+
+static void
+dummy_handler(int sig __unused)
+{
+}
+
+/*
+ * Wait for a process to terminate.
+ */
+
+static pid_t
+dowait(int mode, struct job *job)
+{
+       struct sigaction sa, osa;
+       sigset_t mask, omask;
+       pid_t pid;
+       int status;
+       struct procstat *sp;
+       struct job *jp;
+       struct job *thisjob;
+       const char *sigstr;
+       int done;
+       int stopped;
+       int sig;
+       int coredump;
+       int wflags;
+       int restore_sigchld;
+
+       TRACE(("dowait(%d, %p) called\n", mode, job));
+       restore_sigchld = 0;
+       if ((mode & DOWAIT_SIG) != 0) {
+               sigfillset(&mask);
+               sigprocmask(SIG_BLOCK, &mask, &omask);
+               INTOFF;
+               if (!issigchldtrapped()) {
+                       restore_sigchld = 1;
+                       sa.sa_handler = dummy_handler;
+                       sa.sa_flags = 0;
+                       sigemptyset(&sa.sa_mask);
+                       sigaction(SIGCHLD, &sa, &osa);
+               }
+       }
+       do {
+#if JOBS
+               if (iflag)
+                       wflags = WUNTRACED | WCONTINUED;
+               else
+#endif
+                       wflags = 0;
+               if ((mode & (DOWAIT_BLOCK | DOWAIT_SIG)) != DOWAIT_BLOCK)
+                       wflags |= WNOHANG;
+               pid = wait3(&status, wflags, (struct rusage *)NULL);
+               TRACE(("wait returns %d, status=%d\n", (int)pid, status));
+               if (pid == 0 && (mode & DOWAIT_SIG) != 0) {
+                       pid = -1;
+                       if (((mode & DOWAIT_SIG_TRAP) != 0 ?
+                           pendingsig : pendingsig_waitcmd) != 0) {
+                               errno = EINTR;
+                               break;
+                       }
+                       sigsuspend(&omask);
+                       if (int_pending())
+                               break;
+               }
+       } while (pid == -1 && errno == EINTR);
+       if (pid == -1 && errno == ECHILD && job != NULL)
+               job->state = JOBDONE;
+       if ((mode & DOWAIT_SIG) != 0) {
+               if (restore_sigchld)
+                       sigaction(SIGCHLD, &osa, NULL);
+               sigprocmask(SIG_SETMASK, &omask, NULL);
+               INTON;
+       }
+       if (pid <= 0)
+               return pid;
+       INTOFF;
+       thisjob = NULL;
+       for (jp = jobtab ; jp < jobtab + njobs ; jp++) {
+               if (jp->used && jp->nprocs > 0) {
+                       done = 1;
+                       stopped = 1;
+                       for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) {
+                               if (sp->pid == -1)
+                                       continue;
+                               if (sp->pid == pid && (sp->status == -1 ||
+                                   WIFSTOPPED(sp->status))) {
+                                       TRACE(("Changing status of proc %d from 0x%x to 0x%x\n",
+                                                  (int)pid, sp->status,
+                                                  status));
+                                       if (WIFCONTINUED(status)) {
+                                               sp->status = -1;
+                                               jp->state = 0;
+                                       } else
+                                               sp->status = status;
+                                       thisjob = jp;
+                               }
+                               if (sp->status == -1)
+                                       stopped = 0;
+                               else if (WIFSTOPPED(sp->status))
+                                       done = 0;
+                       }
+                       if (stopped) {          /* stopped or done */
+                               int state = done? JOBDONE : JOBSTOPPED;
+                               if (jp->state != state) {
+                                       TRACE(("Job %td: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state));
+                                       jp->state = state;
+                                       if (jp != job) {
+                                               if (done && !jp->remembered &&
+                                                   !iflag && jp != bgjob)
+                                                       freejob(jp);
+#if JOBS
+                                               else if (done)
+                                                       deljob(jp);
+#endif
+                                       }
+                               }
+                       }
+               }
+       }
+       INTON;
+       if (!thisjob || thisjob->state == 0)
+               ;
+       else if ((!rootshell || !iflag || thisjob == job) &&
+           thisjob->foreground && thisjob->state != JOBSTOPPED) {
+               sig = 0;
+               coredump = 0;
+               for (sp = thisjob->ps; sp < thisjob->ps + thisjob->nprocs; sp++)
+                       if (WIFSIGNALED(sp->status)) {
+                               sig = WTERMSIG(sp->status);
+                               coredump = WCOREDUMP(sp->status);
+                       }
+               if (sig > 0 && sig != SIGINT && sig != SIGPIPE) {
+                       sigstr = strsignal(sig);
+                       if (sigstr != NULL)
+                               out2str(sigstr);
+                       else
+                               out2str("Unknown signal");
+                       if (coredump)
+                               out2str(" (core dumped)");
+                       out2c('\n');
+                       flushout(out2);
+               }
+       } else {
+               TRACE(("Not printing status, rootshell=%d, job=%p\n", rootshell, job));
+               thisjob->changed = 1;
+       }
+       return pid;
+}
+
+
+
+/*
+ * return 1 if there are stopped jobs, otherwise 0
+ */
+int job_warning = 0;
+int
+stoppedjobs(void)
+{
+       int jobno;
+       struct job *jp;
+
+       if (job_warning)
+               return (0);
+       for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) {
+               if (jp->used == 0)
+                       continue;
+               if (jp->state == JOBSTOPPED) {
+                       out2fmt_flush("You have stopped jobs.\n");
+                       job_warning = 2;
+                       return (1);
+               }
+       }
+
+       return (0);
+}
+
+
+static void
+checkzombies(void)
+{
+       while (njobs > 0 && dowait(0, NULL) > 0)
+               ;
+}
+
+
+int
+backgndpidset(void)
+{
+       return backgndpid != -1;
+}
+
+
+pid_t
+backgndpidval(void)
+{
+       if (bgjob != NULL && !forcelocal)
+               bgjob->remembered = 1;
+       return backgndpid;
+}
+
+/*
+ * Return a string identifying a command (to be printed by the
+ * jobs command.
+ */
+
+static char *cmdnextc;
+static int cmdnleft;
+#define MAXCMDTEXT     200
+
+char *
+commandtext(union node *n)
+{
+       char *name;
+
+       cmdnextc = name = ckmalloc(MAXCMDTEXT);
+       cmdnleft = MAXCMDTEXT - 4;
+       cmdtxt(n);
+       *cmdnextc = '\0';
+       return name;
+}
+
+
+static void
+cmdtxtdogroup(union node *n)
+{
+       cmdputs("; do ");
+       cmdtxt(n);
+       cmdputs("; done");
+}
+
+
+static void
+cmdtxtredir(union node *n, const char *op, int deffd)
+{
+       char s[2];
+
+       if (n->nfile.fd != deffd) {
+               s[0] = n->nfile.fd + '0';
+               s[1] = '\0';
+               cmdputs(s);
+       }
+       cmdputs(op);
+       if (n->type == NTOFD || n->type == NFROMFD) {
+               if (n->ndup.dupfd >= 0)
+                       s[0] = n->ndup.dupfd + '0';
+               else
+                       s[0] = '-';
+               s[1] = '\0';
+               cmdputs(s);
+       } else {
+               cmdtxt(n->nfile.fname);
+       }
+}
+
+
+static void
+cmdtxt(union node *n)
+{
+       union node *np;
+       struct nodelist *lp;
+
+       if (n == NULL)
+               return;
+       switch (n->type) {
+       case NSEMI:
+               cmdtxt(n->nbinary.ch1);
+               cmdputs("; ");
+               cmdtxt(n->nbinary.ch2);
+               break;
+       case NAND:
+               cmdtxt(n->nbinary.ch1);
+               cmdputs(" && ");
+               cmdtxt(n->nbinary.ch2);
+               break;
+       case NOR:
+               cmdtxt(n->nbinary.ch1);
+               cmdputs(" || ");
+               cmdtxt(n->nbinary.ch2);
+               break;
+       case NPIPE:
+               for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+                       cmdtxt(lp->n);
+                       if (lp->next)
+                               cmdputs(" | ");
+               }
+               break;
+       case NSUBSHELL:
+               cmdputs("(");
+               cmdtxt(n->nredir.n);
+               cmdputs(")");
+               break;
+       case NREDIR:
+       case NBACKGND:
+               cmdtxt(n->nredir.n);
+               break;
+       case NIF:
+               cmdputs("if ");
+               cmdtxt(n->nif.test);
+               cmdputs("; then ");
+               cmdtxt(n->nif.ifpart);
+               cmdputs("...");
+               break;
+       case NWHILE:
+               cmdputs("while ");
+               cmdtxt(n->nbinary.ch1);
+               cmdtxtdogroup(n->nbinary.ch2);
+               break;
+       case NUNTIL:
+               cmdputs("until ");
+               cmdtxt(n->nbinary.ch1);
+               cmdtxtdogroup(n->nbinary.ch2);
+               break;
+       case NFOR:
+               cmdputs("for ");
+               cmdputs(n->nfor.var);
+               cmdputs(" in ...");
+               break;
+       case NCASE:
+               cmdputs("case ");
+               cmdputs(n->ncase.expr->narg.text);
+               cmdputs(" in ...");
+               break;
+       case NDEFUN:
+               cmdputs(n->narg.text);
+               cmdputs("() ...");
+               break;
+       case NNOT:
+               cmdputs("! ");
+               cmdtxt(n->nnot.com);
+               break;
+       case NCMD:
+               for (np = n->ncmd.args ; np ; np = np->narg.next) {
+                       cmdtxt(np);
+                       if (np->narg.next)
+                               cmdputs(" ");
+               }
+               for (np = n->ncmd.redirect ; np ; np = np->nfile.next) {
+                       cmdputs(" ");
+                       cmdtxt(np);
+               }
+               break;
+       case NARG:
+               cmdputs(n->narg.text);
+               break;
+       case NTO:
+               cmdtxtredir(n, ">", 1);
+               break;
+       case NAPPEND:
+               cmdtxtredir(n, ">>", 1);
+               break;
+       case NTOFD:
+               cmdtxtredir(n, ">&", 1);
+               break;
+       case NCLOBBER:
+               cmdtxtredir(n, ">|", 1);
+               break;
+       case NFROM:
+               cmdtxtredir(n, "<", 0);
+               break;
+       case NFROMTO:
+               cmdtxtredir(n, "<>", 0);
+               break;
+       case NFROMFD:
+               cmdtxtredir(n, "<&", 0);
+               break;
+       case NHERE:
+       case NXHERE:
+               cmdputs("<<...");
+               break;
+       default:
+               cmdputs("???");
+               break;
+       }
+}
+
+
+
+static void
+cmdputs(const char *s)
+{
+       const char *p;
+       char *q;
+       char c;
+       int subtype = 0;
+
+       if (cmdnleft <= 0)
+               return;
+       p = s;
+       q = cmdnextc;
+       while ((c = *p++) != '\0') {
+               if (c == CTLESC)
+                       *q++ = *p++;
+               else if (c == CTLVAR) {
+                       *q++ = '$';
+                       if (--cmdnleft > 0)
+                               *q++ = '{';
+                       subtype = *p++;
+                       if ((subtype & VSTYPE) == VSLENGTH && --cmdnleft > 0)
+                               *q++ = '#';
+               } else if (c == '=' && subtype != 0) {
+                       *q = "}-+?=##%%\0X"[(subtype & VSTYPE) - VSNORMAL];
+                       if (*q)
+                               q++;
+                       else
+                               cmdnleft++;
+                       if (((subtype & VSTYPE) == VSTRIMLEFTMAX ||
+                           (subtype & VSTYPE) == VSTRIMRIGHTMAX) &&
+                           --cmdnleft > 0)
+                               *q = q[-1], q++;
+                       subtype = 0;
+               } else if (c == CTLENDVAR) {
+                       *q++ = '}';
+               } else if (c == CTLBACKQ || c == CTLBACKQ+CTLQUOTE) {
+                       cmdnleft -= 5;
+                       if (cmdnleft > 0) {
+                               *q++ = '$';
+                               *q++ = '(';
+                               *q++ = '.';
+                               *q++ = '.';
+                               *q++ = '.';
+                               *q++ = ')';
+                       }
+               } else if (c == CTLARI) {
+                       cmdnleft -= 2;
+                       if (cmdnleft > 0) {
+                               *q++ = '$';
+                               *q++ = '(';
+                               *q++ = '(';
+                       }
+                       p++;
+               } else if (c == CTLENDARI) {
+                       if (--cmdnleft > 0) {
+                               *q++ = ')';
+                               *q++ = ')';
+                       }
+               } else if (c == CTLQUOTEMARK || c == CTLQUOTEEND)
+                       cmdnleft++; /* ignore */
+               else
+                       *q++ = c;
+               if (--cmdnleft <= 0) {
+                       *q++ = '.';
+                       *q++ = '.';
+                       *q++ = '.';
+                       break;
+               }
+       }
+       cmdnextc = q;
+}
diff --git a/sh/jobs.h b/sh/jobs.h
new file mode 100644 (file)
index 0000000..892f129
--- /dev/null
+++ b/sh/jobs.h
@@ -0,0 +1,100 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)jobs.h      8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+/* Mode argument to forkshell.  Don't change FORK_FG or FORK_BG. */
+#define FORK_FG 0
+#define FORK_BG 1
+#define FORK_NOJOB 2
+
+#include <signal.h>            /* for sig_atomic_t */
+
+/*
+ * A job structure contains information about a job.  A job is either a
+ * single process or a set of processes contained in a pipeline.  In the
+ * latter case, pidlist will be non-NULL, and will point to a -1 terminated
+ * array of pids.
+ */
+
+struct procstat {
+       pid_t pid;              /* process id */
+       int status;             /* status flags (defined above) */
+       char *cmd;              /* text of command being run */
+};
+
+
+/* states */
+#define JOBSTOPPED 1           /* all procs are stopped */
+#define JOBDONE 2              /* all procs are completed */
+
+
+struct job {
+       struct procstat ps0;    /* status of process */
+       struct procstat *ps;    /* status or processes when more than one */
+       short nprocs;           /* number of processes */
+       pid_t pgrp;             /* process group of this job */
+       char state;             /* true if job is finished */
+       char used;              /* true if this entry is in used */
+       char changed;           /* true if status has changed */
+       char foreground;        /* true if running in the foreground */
+       char remembered;        /* true if $! referenced */
+#if JOBS
+       char jobctl;            /* job running under job control */
+       struct job *next;       /* job used after this one */
+#endif
+};
+
+enum {
+       SHOWJOBS_DEFAULT,       /* job number, status, command */
+       SHOWJOBS_VERBOSE,       /* job number, PID, status, command */
+       SHOWJOBS_PIDS,          /* PID only */
+       SHOWJOBS_PGIDS          /* PID of the group leader only */
+};
+
+extern int job_warning;                /* user was warned about stopped jobs */
+
+void setjobctl(int);
+void showjobs(int, int);
+struct job *makejob(union node *, int);
+pid_t forkshell(struct job *, union node *, int);
+pid_t vforkexecshell(struct job *, char **, char **, const char *, int, int []);
+int waitforjob(struct job *, int *);
+int stoppedjobs(void);
+int backgndpidset(void);
+pid_t backgndpidval(void);
+char *commandtext(union node *);
+
+#if ! JOBS
+#define setjobctl(on)  /* do nothing */
+#endif
diff --git a/sh/mail.c b/sh/mail.c
new file mode 100644 (file)
index 0000000..720cab0
--- /dev/null
+++ b/sh/mail.c
@@ -0,0 +1,117 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mail.c     8.2 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Routines to check for mail.  (Perhaps make part of main.c?)
+ */
+
+#include "shell.h"
+#include "exec.h"      /* defines padvance() */
+#include "mail.h"
+#include "var.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+
+
+#define MAXMBOXES 10
+
+
+static int nmboxes;                    /* number of mailboxes */
+static time_t mailtime[MAXMBOXES];     /* times of mailboxes */
+
+
+
+/*
+ * Print appropriate message(s) if mail has arrived.  If the argument is
+ * nozero, then the value of MAIL has changed, so we just update the
+ * values.
+ */
+
+void
+chkmail(int silent)
+{
+       int i;
+       const char *mpath;
+       char *p;
+       char *q;
+       struct stackmark smark;
+       struct stat statb;
+
+       if (silent)
+               nmboxes = 10;
+       if (nmboxes == 0)
+               return;
+       setstackmark(&smark);
+       mpath = mpathset()? mpathval() : mailval();
+       for (i = 0 ; i < nmboxes ; i++) {
+               p = padvance(&mpath, "");
+               if (p == NULL)
+                       break;
+               if (*p == '\0')
+                       continue;
+               for (q = p ; *q ; q++);
+               if (q[-1] != '/')
+                       abort();
+               q[-1] = '\0';                   /* delete trailing '/' */
+#ifdef notdef /* this is what the System V shell claims to do (it lies) */
+               if (stat(p, &statb) < 0)
+                       statb.st_mtime = 0;
+               if (statb.st_mtime > mailtime[i] && ! silent) {
+                       out2str(pathopt? pathopt : "you have mail");
+                       out2c('\n');
+               }
+               mailtime[i] = statb.st_mtime;
+#else /* this is what it should do */
+               if (stat(p, &statb) < 0)
+                       statb.st_size = 0;
+               if (statb.st_size > mailtime[i] && ! silent) {
+                       out2str(pathopt? pathopt : "you have mail");
+                       out2c('\n');
+               }
+               mailtime[i] = statb.st_size;
+#endif
+       }
+       nmboxes = i;
+       popstackmark(&smark);
+}
diff --git a/sh/mail.h b/sh/mail.h
new file mode 100644 (file)
index 0000000..f88324c
--- /dev/null
+++ b/sh/mail.h
@@ -0,0 +1,36 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)mail.h      8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+void chkmail(int);
diff --git a/sh/main.c b/sh/main.c
new file mode 100644 (file)
index 0000000..08f234e
--- /dev/null
+++ b/sh/main.c
@@ -0,0 +1,349 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1991, 1993\n\
+       The Regents of the University of California.  All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)main.c     8.6 (Berkeley) 5/28/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdio.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <errno.h>
+
+#include "shell.h"
+#include "main.h"
+#include "mail.h"
+#include "options.h"
+#include "output.h"
+#include "parser.h"
+#include "nodes.h"
+#include "expand.h"
+#include "eval.h"
+#include "jobs.h"
+#include "input.h"
+#include "trap.h"
+#include "var.h"
+#include "show.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "exec.h"
+#include "cd.h"
+#include "redir.h"
+#include "builtins.h"
+
+int rootpid;
+int rootshell;
+struct jmploc main_handler;
+int localeisutf8, initial_localeisutf8;
+
+static void reset(void);
+static void cmdloop(int);
+static void read_profile(const char *);
+static char *find_dot_file(char *);
+
+/*
+ * Main routine.  We initialize things, parse the arguments, execute
+ * profiles if we're a login shell, and then call cmdloop to execute
+ * commands.  The setjmp call sets up the location to jump to when an
+ * exception occurs.  When an exception occurs the variable "state"
+ * is used to figure out how far we had gotten.
+ */
+
+int
+main(int argc, char *argv[])
+{
+       struct stackmark smark, smark2;
+       volatile int state;
+       char *shinit;
+
+       (void) setlocale(LC_ALL, "");
+       initcharset();
+       state = 0;
+       if (setjmp(main_handler.loc)) {
+               switch (exception) {
+               case EXEXEC:
+                       exitstatus = exerrno;
+                       break;
+
+               case EXERROR:
+                       exitstatus = 2;
+                       break;
+
+               default:
+                       break;
+               }
+
+               if (state == 0 || iflag == 0 || ! rootshell ||
+                   exception == EXEXIT)
+                       exitshell(exitstatus);
+               reset();
+               if (exception == EXINT)
+                       out2fmt_flush("\n");
+               popstackmark(&smark);
+               FORCEINTON;                             /* enable interrupts */
+               if (state == 1)
+                       goto state1;
+               else if (state == 2)
+                       goto state2;
+               else if (state == 3)
+                       goto state3;
+               else
+                       goto state4;
+       }
+       handler = &main_handler;
+#ifdef DEBUG
+       opentrace();
+       trputs("Shell args:  ");  trargs(argv);
+#endif
+       rootpid = getpid();
+       rootshell = 1;
+       INTOFF;
+       initvar();
+       setstackmark(&smark);
+       setstackmark(&smark2);
+       procargs(argc, argv);
+       pwd_init(iflag);
+       INTON;
+       if (iflag)
+               chkmail(1);
+       if (argv[0] && argv[0][0] == '-') {
+               state = 1;
+               read_profile("/etc/profile");
+state1:
+               state = 2;
+               if (privileged == 0)
+                       read_profile("${HOME-}/.profile");
+               else
+                       read_profile("/etc/suid_profile");
+       }
+state2:
+       state = 3;
+       if (!privileged && iflag) {
+               if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') {
+                       state = 3;
+                       read_profile(shinit);
+               }
+       }
+state3:
+       state = 4;
+       popstackmark(&smark2);
+       if (minusc) {
+               evalstring(minusc, sflag ? 0 : EV_EXIT);
+       }
+state4:
+       if (sflag || minusc == NULL) {
+               cmdloop(1);
+       }
+       exitshell(exitstatus);
+       /*NOTREACHED*/
+       return 0;
+}
+
+static void
+reset(void)
+{
+       reseteval();
+       resetinput();
+}
+
+/*
+ * Read and execute commands.  "Top" is nonzero for the top level command
+ * loop; it turns on prompting if the shell is interactive.
+ */
+
+static void
+cmdloop(int top)
+{
+       union node *n;
+       struct stackmark smark;
+       int inter;
+       int numeof = 0;
+
+       TRACE(("cmdloop(%d) called\n", top));
+       setstackmark(&smark);
+       for (;;) {
+               if (pendingsig)
+                       dotrap();
+               inter = 0;
+               if (iflag && top) {
+                       inter++;
+                       showjobs(1, SHOWJOBS_DEFAULT);
+                       chkmail(0);
+                       flushout(&output);
+               }
+               n = parsecmd(inter);
+               /* showtree(n); DEBUG */
+               if (n == NEOF) {
+                       if (!top || numeof >= 50)
+                               break;
+                       if (!stoppedjobs()) {
+                               if (!Iflag)
+                                       break;
+                               out2fmt_flush("\nUse \"exit\" to leave shell.\n");
+                       }
+                       numeof++;
+               } else if (n != NULL && nflag == 0) {
+                       job_warning = (job_warning == 2) ? 1 : 0;
+                       numeof = 0;
+                       evaltree(n, 0);
+               }
+               popstackmark(&smark);
+               setstackmark(&smark);
+               if (evalskip != 0) {
+                       if (evalskip == SKIPRETURN)
+                               evalskip = 0;
+                       break;
+               }
+       }
+       popstackmark(&smark);
+}
+
+
+
+/*
+ * Read /etc/profile or .profile.  Return on error.
+ */
+
+static void
+read_profile(const char *name)
+{
+       int fd;
+       const char *expandedname;
+
+       expandedname = expandstr(name);
+       if (expandedname == NULL)
+               return;
+       INTOFF;
+       if ((fd = open(expandedname, O_RDONLY | O_CLOEXEC)) >= 0)
+               setinputfd(fd, 1);
+       INTON;
+       if (fd < 0)
+               return;
+       cmdloop(0);
+       popfile();
+}
+
+
+
+/*
+ * Read a file containing shell functions.
+ */
+
+void
+readcmdfile(const char *name)
+{
+       setinputfile(name, 1);
+       cmdloop(0);
+       popfile();
+}
+
+
+
+/*
+ * Take commands from a file.  To be compatible we should do a path
+ * search for the file, which is necessary to find sub-commands.
+ */
+
+
+static char *
+find_dot_file(char *basename)
+{
+       char *fullname;
+       const char *path = pathval();
+       struct stat statb;
+
+       /* don't try this for absolute or relative paths */
+       if( strchr(basename, '/'))
+               return basename;
+
+       while ((fullname = padvance(&path, basename)) != NULL) {
+               if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) {
+                       /*
+                        * Don't bother freeing here, since it will
+                        * be freed by the caller.
+                        */
+                       return fullname;
+               }
+               stunalloc(fullname);
+       }
+       return basename;
+}
+
+int
+dotcmd(int argc, char **argv)
+{
+       char *filename, *fullname;
+
+       if (argc < 2)
+               error("missing filename");
+
+       exitstatus = 0;
+
+       /*
+        * Because we have historically not supported any options,
+        * only treat "--" specially.
+        */
+       filename = argc > 2 && strcmp(argv[1], "--") == 0 ? argv[2] : argv[1];
+
+       fullname = find_dot_file(filename);
+       setinputfile(fullname, 1);
+       commandname = fullname;
+       cmdloop(0);
+       popfile();
+       return exitstatus;
+}
+
+
+int
+exitcmd(int argc, char **argv)
+{
+       if (stoppedjobs())
+               return 0;
+       if (argc > 1)
+               exitshell(number(argv[1]));
+       else
+               exitshell_savedstatus();
+}
diff --git a/sh/main.h b/sh/main.h
new file mode 100644 (file)
index 0000000..3c48e33
--- /dev/null
+++ b/sh/main.h
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)main.h      8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+extern int rootpid;    /* pid of main shell */
+extern int rootshell;  /* true if we aren't a child of the main shell */
+extern struct jmploc main_handler;     /* top level exception handler */
+
+void readcmdfile(const char *);
diff --git a/sh/memalloc.c b/sh/memalloc.c
new file mode 100644 (file)
index 0000000..a04020f
--- /dev/null
@@ -0,0 +1,342 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)memalloc.c 8.3 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include "shell.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "expand.h"
+#include <stdlib.h>
+#include <unistd.h>
+
+/*
+ * Like malloc, but returns an error when out of space.
+ */
+
+pointer
+ckmalloc(size_t nbytes)
+{
+       pointer p;
+
+       INTOFF;
+       p = malloc(nbytes);
+       INTON;
+       if (p == NULL)
+               error("Out of space");
+       return p;
+}
+
+
+/*
+ * Same for realloc.
+ */
+
+pointer
+ckrealloc(pointer p, int nbytes)
+{
+       INTOFF;
+       p = realloc(p, nbytes);
+       INTON;
+       if (p == NULL)
+               error("Out of space");
+       return p;
+}
+
+void
+ckfree(pointer p)
+{
+       INTOFF;
+       free(p);
+       INTON;
+}
+
+
+/*
+ * Make a copy of a string in safe storage.
+ */
+
+char *
+savestr(const char *s)
+{
+       char *p;
+       size_t len;
+
+       len = strlen(s);
+       p = ckmalloc(len + 1);
+       memcpy(p, s, len + 1);
+       return p;
+}
+
+
+/*
+ * Parse trees for commands are allocated in lifo order, so we use a stack
+ * to make this more efficient, and also to avoid all sorts of exception
+ * handling code to handle interrupts in the middle of a parse.
+ *
+ * The size 496 was chosen because with 16-byte alignment the total size
+ * for the allocated block is 512.
+ */
+
+#define MINSIZE 496            /* minimum size of a block. */
+
+
+struct stack_block {
+       struct stack_block *prev;
+       /* Data follows */
+};
+#define SPACE(sp)      ((char*)(sp) + ALIGN(sizeof(struct stack_block)))
+
+static struct stack_block *stackp;
+char *stacknxt;
+int stacknleft;
+char *sstrend;
+
+
+static void
+stnewblock(int nbytes)
+{
+       struct stack_block *sp;
+       int allocsize;
+
+       if (nbytes < MINSIZE)
+               nbytes = MINSIZE;
+
+       allocsize = ALIGN(sizeof(struct stack_block)) + ALIGN(nbytes);
+
+       INTOFF;
+       sp = ckmalloc(allocsize);
+       sp->prev = stackp;
+       stacknxt = SPACE(sp);
+       stacknleft = allocsize - (stacknxt - (char*)sp);
+       sstrend = stacknxt + stacknleft;
+       stackp = sp;
+       INTON;
+}
+
+
+pointer
+stalloc(int nbytes)
+{
+       char *p;
+
+       nbytes = ALIGN(nbytes);
+       if (nbytes > stacknleft)
+               stnewblock(nbytes);
+       p = stacknxt;
+       stacknxt += nbytes;
+       stacknleft -= nbytes;
+       return p;
+}
+
+
+void
+stunalloc(pointer p)
+{
+       if (p == NULL) {                /*DEBUG */
+               write(STDERR_FILENO, "stunalloc\n", 10);
+               abort();
+       }
+       stacknleft += stacknxt - (char *)p;
+       stacknxt = p;
+}
+
+
+char *
+stsavestr(const char *s)
+{
+       char *p;
+       size_t len;
+
+       len = strlen(s);
+       p = stalloc(len + 1);
+       memcpy(p, s, len + 1);
+       return p;
+}
+
+
+void
+setstackmark(struct stackmark *mark)
+{
+       mark->stackp = stackp;
+       mark->stacknxt = stacknxt;
+       mark->stacknleft = stacknleft;
+       /* Ensure this block stays in place. */
+       if (stackp != NULL && stacknxt == SPACE(stackp))
+               stalloc(1);
+}
+
+
+void
+popstackmark(struct stackmark *mark)
+{
+       struct stack_block *sp;
+
+       INTOFF;
+       while (stackp != mark->stackp) {
+               sp = stackp;
+               stackp = sp->prev;
+               ckfree(sp);
+       }
+       stacknxt = mark->stacknxt;
+       stacknleft = mark->stacknleft;
+       sstrend = stacknxt + stacknleft;
+       INTON;
+}
+
+
+/*
+ * When the parser reads in a string, it wants to stick the string on the
+ * stack and only adjust the stack pointer when it knows how big the
+ * string is.  Stackblock (defined in stack.h) returns a pointer to a block
+ * of space on top of the stack and stackblocklen returns the length of
+ * this block.  Growstackblock will grow this space by at least one byte,
+ * possibly moving it (like realloc).  Grabstackblock actually allocates the
+ * part of the block that has been used.
+ */
+
+static void
+growstackblock(int min)
+{
+       char *p;
+       int newlen;
+       char *oldspace;
+       int oldlen;
+       struct stack_block *sp;
+       struct stack_block *oldstackp;
+
+       if (min < stacknleft)
+               min = stacknleft;
+       if ((unsigned int)min >=
+           INT_MAX / 2 - ALIGN(sizeof(struct stack_block)))
+               error("Out of space");
+       min += stacknleft;
+       min += ALIGN(sizeof(struct stack_block));
+       newlen = 512;
+       while (newlen < min)
+               newlen <<= 1;
+       oldspace = stacknxt;
+       oldlen = stacknleft;
+
+       if (stackp != NULL && stacknxt == SPACE(stackp)) {
+               INTOFF;
+               oldstackp = stackp;
+               stackp = oldstackp->prev;
+               sp = ckrealloc((pointer)oldstackp, newlen);
+               sp->prev = stackp;
+               stackp = sp;
+               stacknxt = SPACE(sp);
+               stacknleft = newlen - (stacknxt - (char*)sp);
+               sstrend = stacknxt + stacknleft;
+               INTON;
+       } else {
+               newlen -= ALIGN(sizeof(struct stack_block));
+               p = stalloc(newlen);
+               if (oldlen != 0)
+                       memcpy(p, oldspace, oldlen);
+               stunalloc(p);
+       }
+}
+
+
+
+/*
+ * The following routines are somewhat easier to use that the above.
+ * The user declares a variable of type STACKSTR, which may be declared
+ * to be a register.  The macro STARTSTACKSTR initializes things.  Then
+ * the user uses the macro STPUTC to add characters to the string.  In
+ * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is
+ * grown as necessary.  When the user is done, she can just leave the
+ * string there and refer to it using stackblock().  Or she can allocate
+ * the space for it using grabstackstr().  If it is necessary to allow
+ * someone else to use the stack temporarily and then continue to grow
+ * the string, the user should use grabstack to allocate the space, and
+ * then call ungrabstr(p) to return to the previous mode of operation.
+ *
+ * USTPUTC is like STPUTC except that it doesn't check for overflow.
+ * CHECKSTACKSPACE can be called before USTPUTC to ensure that there
+ * is space for at least one character.
+ */
+
+static char *
+growstrstackblock(int n, int min)
+{
+       growstackblock(min);
+       return stackblock() + n;
+}
+
+char *
+growstackstr(void)
+{
+       int len;
+
+       len = stackblocksize();
+       return (growstrstackblock(len, 0));
+}
+
+
+/*
+ * Called from CHECKSTRSPACE.
+ */
+
+char *
+makestrspace(int min, char *p)
+{
+       int len;
+
+       len = p - stackblock();
+       return (growstrstackblock(len, min));
+}
+
+
+char *
+stputbin(const char *data, size_t len, char *p)
+{
+       CHECKSTRSPACE(len, p);
+       memcpy(p, data, len);
+       return (p + len);
+}
+
+char *
+stputs(const char *data, char *p)
+{
+       return (stputbin(data, strlen(data), p));
+}
diff --git a/sh/memalloc.h b/sh/memalloc.h
new file mode 100644 (file)
index 0000000..e8df7cb
--- /dev/null
@@ -0,0 +1,86 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)memalloc.h  8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+#include <string.h>
+
+struct stackmark {
+       struct stack_block *stackp;
+       char *stacknxt;
+       int stacknleft;
+};
+
+
+extern char *stacknxt;
+extern int stacknleft;
+extern char *sstrend;
+
+pointer ckmalloc(size_t);
+pointer ckrealloc(pointer, int);
+void ckfree(pointer);
+char *savestr(const char *);
+pointer stalloc(int);
+void stunalloc(pointer);
+char *stsavestr(const char *);
+void setstackmark(struct stackmark *);
+void popstackmark(struct stackmark *);
+char *growstackstr(void);
+char *makestrspace(int, char *);
+char *stputbin(const char *data, size_t len, char *p);
+char *stputs(const char *data, char *p);
+
+
+
+#define stackblock() stacknxt
+#define stackblocksize() stacknleft
+#define grabstackblock(n) stalloc(n)
+#define STARTSTACKSTR(p)       p = stackblock()
+#define STPUTC(c, p)   do { if (p == sstrend) p = growstackstr(); *p++ = (c); } while(0)
+#define CHECKSTRSPACE(n, p)    { if ((size_t)(sstrend - p) < n) p = makestrspace(n, p); }
+#define USTPUTC(c, p)  (*p++ = (c))
+/*
+ * STACKSTRNUL's use is where we want to be able to turn a stack
+ * (non-sentinel, character counting string) into a C string,
+ * and later pretend the NUL is not there.
+ * Note: Because of STACKSTRNUL's semantics, STACKSTRNUL cannot be used
+ * on a stack that will grabstackstr()ed.
+ */
+#define STACKSTRNUL(p) (p == sstrend ? (p = growstackstr(), *p = '\0') : (*p = '\0'))
+#define STUNPUTC(p)    (--p)
+#define STTOPC(p)      p[-1]
+#define STADJUST(amount, p)    (p += (amount))
+#define grabstackstr(p)        stalloc((char *)p - stackblock())
+#define ungrabstackstr(s, p)   stunalloc((s))
+#define STPUTBIN(s, len, p)    p = stputbin((s), (len), p)
+#define STPUTS(s, p)   p = stputs((s), p)
diff --git a/sh/miscbltin.c b/sh/miscbltin.c
new file mode 100644 (file)
index 0000000..789da25
--- /dev/null
@@ -0,0 +1,523 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)miscbltin.c        8.4 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Miscellaneous builtins.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "shell.h"
+#include "options.h"
+#include "var.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "syntax.h"
+#include "trap.h"
+
+#undef eflag
+
+int readcmd(int, char **);
+int umaskcmd(int, char **);
+int ulimitcmd(int, char **);
+
+/*
+ * The read builtin.  The -r option causes backslashes to be treated like
+ * ordinary characters.
+ *
+ * This uses unbuffered input, which may be avoidable in some cases.
+ *
+ * Note that if IFS=' :' then read x y should work so that:
+ * 'a b'       x='a', y='b'
+ * ' a b '     x='a', y='b'
+ * ':b'                x='',  y='b'
+ * ':'         x='',  y=''
+ * '::'                x='',  y=''
+ * ': :'       x='',  y=''
+ * ':::'       x='',  y='::'
+ * ':b c:'     x='',  y='b c:'
+ */
+
+int
+readcmd(int argc __unused, char **argv __unused)
+{
+       char **ap;
+       int backslash;
+       char c;
+       int rflag;
+       char *prompt;
+       const char *ifs;
+       char *p;
+       int startword;
+       int status;
+       int i;
+       int is_ifs;
+       int saveall = 0;
+       struct timeval tv;
+       char *tvptr;
+       fd_set ifds;
+       ssize_t nread;
+       int sig;
+
+       rflag = 0;
+       prompt = NULL;
+       tv.tv_sec = -1;
+       tv.tv_usec = 0;
+       while ((i = nextopt("erp:t:")) != '\0') {
+               switch(i) {
+               case 'p':
+                       prompt = shoptarg;
+                       break;
+               case 'e':
+                       break;
+               case 'r':
+                       rflag = 1;
+                       break;
+               case 't':
+                       tv.tv_sec = strtol(shoptarg, &tvptr, 0);
+                       if (tvptr == shoptarg)
+                               error("timeout value");
+                       switch(*tvptr) {
+                       case 0:
+                       case 's':
+                               break;
+                       case 'h':
+                               tv.tv_sec *= 60;
+                               /* FALLTHROUGH */
+                       case 'm':
+                               tv.tv_sec *= 60;
+                               break;
+                       default:
+                               error("timeout unit");
+                       }
+                       break;
+               }
+       }
+       if (prompt && isatty(0)) {
+               out2str(prompt);
+               flushall();
+       }
+       if (*(ap = argptr) == NULL)
+               error("arg count");
+       if ((ifs = bltinlookup("IFS", 1)) == NULL)
+               ifs = " \t\n";
+
+       if (tv.tv_sec >= 0) {
+               /*
+                * Wait for something to become available.
+                */
+               FD_ZERO(&ifds);
+               FD_SET(0, &ifds);
+               status = select(1, &ifds, NULL, NULL, &tv);
+               /*
+                * If there's nothing ready, return an error.
+                */
+               if (status <= 0) {
+                       sig = pendingsig;
+                       return (128 + (sig != 0 ? sig : SIGALRM));
+               }
+       }
+
+       status = 0;
+       startword = 2;
+       backslash = 0;
+       STARTSTACKSTR(p);
+       for (;;) {
+               nread = read(STDIN_FILENO, &c, 1);
+               if (nread == -1) {
+                       if (errno == EINTR) {
+                               sig = pendingsig;
+                               if (sig == 0)
+                                       continue;
+                               status = 128 + sig;
+                               break;
+                       }
+                       warning("read error: %s", strerror(errno));
+                       status = 2;
+                       break;
+               } else if (nread != 1) {
+                       status = 1;
+                       break;
+               }
+               if (c == '\0')
+                       continue;
+               CHECKSTRSPACE(1, p);
+               if (backslash) {
+                       backslash = 0;
+                       startword = 0;
+                       if (c != '\n')
+                               USTPUTC(c, p);
+                       continue;
+               }
+               if (!rflag && c == '\\') {
+                       backslash++;
+                       continue;
+               }
+               if (c == '\n')
+                       break;
+               if (strchr(ifs, c))
+                       is_ifs = strchr(" \t\n", c) ? 1 : 2;
+               else
+                       is_ifs = 0;
+
+               if (startword != 0) {
+                       if (is_ifs == 1) {
+                               /* Ignore leading IFS whitespace */
+                               if (saveall)
+                                       USTPUTC(c, p);
+                               continue;
+                       }
+                       if (is_ifs == 2 && startword == 1) {
+                               /* Only one non-whitespace IFS per word */
+                               startword = 2;
+                               if (saveall)
+                                       USTPUTC(c, p);
+                               continue;
+                       }
+               }
+
+               if (is_ifs == 0) {
+                       /* append this character to the current variable */
+                       startword = 0;
+                       if (saveall)
+                               /* Not just a spare terminator */
+                               saveall++;
+                       USTPUTC(c, p);
+                       continue;
+               }
+
+               /* end of variable... */
+               startword = is_ifs;
+
+               if (ap[1] == NULL) {
+                       /* Last variable needs all IFS chars */
+                       saveall++;
+                       USTPUTC(c, p);
+                       continue;
+               }
+
+               STACKSTRNUL(p);
+               setvar(*ap, stackblock(), 0);
+               ap++;
+               STARTSTACKSTR(p);
+       }
+       STACKSTRNUL(p);
+
+       /* Remove trailing IFS chars */
+       for (; stackblock() <= --p; *p = 0) {
+               if (!strchr(ifs, *p))
+                       break;
+               if (strchr(" \t\n", *p))
+                       /* Always remove whitespace */
+                       continue;
+               if (saveall > 1)
+                       /* Don't remove non-whitespace unless it was naked */
+                       break;
+       }
+       setvar(*ap, stackblock(), 0);
+
+       /* Set any remaining args to "" */
+       while (*++ap != NULL)
+               setvar(*ap, "", 0);
+       return status;
+}
+
+
+
+int
+umaskcmd(int argc __unused, char **argv __unused)
+{
+       char *ap;
+       int mask;
+       int i;
+       int symbolic_mode = 0;
+
+       while ((i = nextopt("S")) != '\0') {
+               symbolic_mode = 1;
+       }
+
+       INTOFF;
+       mask = umask(0);
+       umask(mask);
+       INTON;
+
+       if ((ap = *argptr) == NULL) {
+               if (symbolic_mode) {
+                       char u[4], g[4], o[4];
+
+                       i = 0;
+                       if ((mask & S_IRUSR) == 0)
+                               u[i++] = 'r';
+                       if ((mask & S_IWUSR) == 0)
+                               u[i++] = 'w';
+                       if ((mask & S_IXUSR) == 0)
+                               u[i++] = 'x';
+                       u[i] = '\0';
+
+                       i = 0;
+                       if ((mask & S_IRGRP) == 0)
+                               g[i++] = 'r';
+                       if ((mask & S_IWGRP) == 0)
+                               g[i++] = 'w';
+                       if ((mask & S_IXGRP) == 0)
+                               g[i++] = 'x';
+                       g[i] = '\0';
+
+                       i = 0;
+                       if ((mask & S_IROTH) == 0)
+                               o[i++] = 'r';
+                       if ((mask & S_IWOTH) == 0)
+                               o[i++] = 'w';
+                       if ((mask & S_IXOTH) == 0)
+                               o[i++] = 'x';
+                       o[i] = '\0';
+
+                       out1fmt("u=%s,g=%s,o=%s\n", u, g, o);
+               } else {
+                       out1fmt("%.4o\n", mask);
+               }
+       } else {
+               if (is_digit(*ap)) {
+                       mask = 0;
+                       do {
+                               if (*ap >= '8' || *ap < '0')
+                                       error("Illegal number: %s", *argptr);
+                               mask = (mask << 3) + (*ap - '0');
+                       } while (*++ap != '\0');
+                       umask(mask);
+               } else {
+                       void *set;
+                       INTOFF;
+                       if ((set = setmode (ap)) == 0)
+                               error("Illegal number: %s", ap);
+
+                       mask = getmode (set, ~mask & 0777);
+                       umask(~mask & 0777);
+                       free(set);
+                       INTON;
+               }
+       }
+       return 0;
+}
+
+/*
+ * ulimit builtin
+ *
+ * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and
+ * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with
+ * ash by J.T. Conklin.
+ *
+ * Public domain.
+ */
+
+struct limits {
+       const char *name;
+       const char *units;
+       int     cmd;
+       int     factor; /* multiply by to get rlim_{cur,max} values */
+       char    option;
+};
+
+static const struct limits limits[] = {
+#ifdef RLIMIT_CPU
+       { "cpu time",           "seconds",      RLIMIT_CPU,        1, 't' },
+#endif
+#ifdef RLIMIT_FSIZE
+       { "file size",          "512-blocks",   RLIMIT_FSIZE,    512, 'f' },
+#endif
+#ifdef RLIMIT_DATA
+       { "data seg size",      "kbytes",       RLIMIT_DATA,    1024, 'd' },
+#endif
+#ifdef RLIMIT_STACK
+       { "stack size",         "kbytes",       RLIMIT_STACK,   1024, 's' },
+#endif
+#ifdef  RLIMIT_CORE
+       { "core file size",     "512-blocks",   RLIMIT_CORE,     512, 'c' },
+#endif
+#ifdef RLIMIT_RSS
+       { "max memory size",    "kbytes",       RLIMIT_RSS,     1024, 'm' },
+#endif
+#ifdef RLIMIT_MEMLOCK
+       { "locked memory",      "kbytes",       RLIMIT_MEMLOCK, 1024, 'l' },
+#endif
+#ifdef RLIMIT_NPROC
+       { "max user processes", (char *)0,      RLIMIT_NPROC,      1, 'u' },
+#endif
+#ifdef RLIMIT_NOFILE
+       { "open files",         (char *)0,      RLIMIT_NOFILE,     1, 'n' },
+#endif
+#ifdef RLIMIT_VMEM
+       { "virtual mem size",   "kbytes",       RLIMIT_VMEM,    1024, 'v' },
+#endif
+#ifdef RLIMIT_SWAP
+       { "swap limit",         "kbytes",       RLIMIT_SWAP,    1024, 'w' },
+#endif
+#ifdef RLIMIT_SBSIZE
+       { "sbsize",             "bytes",        RLIMIT_SBSIZE,     1, 'b' },
+#endif
+#ifdef RLIMIT_NPTS
+       { "pseudo-terminals",   (char *)0,      RLIMIT_NPTS,       1, 'p' },
+#endif
+#ifdef RLIMIT_KQUEUES
+       { "kqueues",            (char *)0,      RLIMIT_KQUEUES,    1, 'k' },
+#endif
+       { (char *) 0,           (char *)0,      0,                 0, '\0' }
+};
+
+enum limithow { SOFT = 0x1, HARD = 0x2 };
+
+static void
+printlimit(enum limithow how, const struct rlimit *limit,
+    const struct limits *l)
+{
+       rlim_t val = 0;
+
+       if (how & SOFT)
+               val = limit->rlim_cur;
+       else if (how & HARD)
+               val = limit->rlim_max;
+       if (val == RLIM_INFINITY)
+               out1str("unlimited\n");
+       else
+       {
+               val /= l->factor;
+               out1fmt("%jd\n", (intmax_t)val);
+       }
+}
+
+int
+ulimitcmd(int argc __unused, char **argv __unused)
+{
+       rlim_t val = 0;
+       enum limithow how = SOFT | HARD;
+       const struct limits     *l;
+       int             set, all = 0;
+       int             optc, what;
+       struct rlimit   limit;
+
+       what = 'f';
+       while ((optc = nextopt("HSatfdsmcnuvlbpwk")) != '\0')
+               switch (optc) {
+               case 'H':
+                       how = HARD;
+                       break;
+               case 'S':
+                       how = SOFT;
+                       break;
+               case 'a':
+                       all = 1;
+                       break;
+               default:
+                       what = optc;
+               }
+
+       for (l = limits; l->name && l->option != what; l++)
+               ;
+       if (!l->name)
+               error("internal error (%c)", what);
+
+       set = *argptr ? 1 : 0;
+       if (set) {
+               char *p = *argptr;
+
+               if (all || argptr[1])
+                       error("too many arguments");
+               if (strcmp(p, "unlimited") == 0)
+                       val = RLIM_INFINITY;
+               else {
+                       char *end;
+                       uintmax_t uval;
+
+                       if (*p < '0' || *p > '9')
+                               error("bad number");
+                       errno = 0;
+                       uval = strtoumax(p, &end, 10);
+                       if (errno != 0 || *end != '\0')
+                               error("bad number");
+                       if (uval > UINTMAX_MAX / l->factor)
+                               error("bad number");
+                       uval *= l->factor;
+                       val = (rlim_t)uval;
+                       if ((uintmax_t)val != uval ||
+                           val == RLIM_INFINITY)
+                               error("bad number");
+               }
+       }
+       if (all) {
+               for (l = limits; l->name; l++) {
+                       char optbuf[40];
+                       if (getrlimit(l->cmd, &limit) < 0)
+                               error("can't get limit: %s", strerror(errno));
+
+                       if (l->units)
+                               snprintf(optbuf, sizeof(optbuf),
+                                       "(%s, -%c) ", l->units, l->option);
+                       else
+                               snprintf(optbuf, sizeof(optbuf),
+                                       "(-%c) ", l->option);
+                       out1fmt("%-18s %18s ", l->name, optbuf);
+                       printlimit(how, &limit, l);
+               }
+               return 0;
+       }
+
+       if (getrlimit(l->cmd, &limit) < 0)
+               error("can't get limit: %s", strerror(errno));
+       if (set) {
+               if (how & SOFT)
+                       limit.rlim_cur = val;
+               if (how & HARD)
+                       limit.rlim_max = val;
+               if (setrlimit(l->cmd, &limit) < 0)
+                       error("bad limit: %s", strerror(errno));
+       } else
+               printlimit(how, &limit, l);
+       return 0;
+}
diff --git a/sh/mkbuiltins b/sh/mkbuiltins
new file mode 100755 (executable)
index 0000000..1be7ff1
--- /dev/null
@@ -0,0 +1,98 @@
+#!/bin/sh -
+
+#-
+# Copyright (c) 1991, 1993
+#      The Regents of the University of California.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 4. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#      @(#)mkbuiltins  8.2 (Berkeley) 5/4/95
+# $FreeBSD$
+
+temp=`/usr/bin/mktemp -t ka`
+havehist=1
+if [ "X$1" = "X-h" ]; then
+       havehist=0
+       shift
+fi
+srcdir=$1
+havejobs=0
+if grep '^#define[      ]*JOBS[         ]*1' $srcdir/shell.h > /dev/null
+then   havejobs=1
+fi
+exec > builtins.c
+cat <<\!
+/*
+ * This file was generated by the mkbuiltins program.
+ */
+
+#include <stdlib.h>
+#include "shell.h"
+#include "builtins.h"
+
+!
+awk '/^[^#]/ {if(('$havejobs' || $2 != "-j") && ('$havehist' || $2 != "-h")) \
+    print $0}' $srcdir/builtins.def | sed 's/-[hj]//' > $temp
+echo 'int (*const builtinfunc[])(int, char **) = {'
+awk '/^[^#]/ { printf "\t%s,\n", $1}' $temp
+echo '};
+
+const struct builtincmd builtincmd[] = {'
+awk '{ for (i = 2 ; i <= NF ; i++) {
+               if ($i == "-s") {
+                       spc = 1;
+               } else {
+                       printf "\t{ \"%s\", %d, %d },\n",  $i, NR-1, spc
+                       spc = 0;
+               }
+       }}' $temp
+echo ' { NULL, 0, 0 }
+};'
+
+exec > builtins.h
+cat <<\!
+/*
+ * This file was generated by the mkbuiltins program.
+ */
+
+#include <sys/cdefs.h>
+!
+tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ < $temp |
+       awk '{  printf "#define %s %d\n", $1, NR-1}'
+echo '
+struct builtincmd {
+      const char *name;
+      int code;
+      int special;
+};
+
+extern int (*const builtinfunc[])(int, char **);
+extern const struct builtincmd builtincmd[];
+'
+awk '{ printf "int %s(int, char **);\n", $1}' $temp
+rm -f $temp
diff --git a/sh/mknodes.c b/sh/mknodes.c
new file mode 100644 (file)
index 0000000..d2fed0d
--- /dev/null
@@ -0,0 +1,455 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1991, 1993\n\
+       The Regents of the University of California.  All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)mknodes.c  8.2 (Berkeley) 5/4/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * This program reads the nodetypes file and nodes.c.pat file.  It generates
+ * the files nodes.h and nodes.c.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+
+// When building for iOS, we don't have a new enough OS X SDK.
+#ifndef __printf0like
+#define __printf0like(fmtarg, firstvararg) \
+__attribute__((__format__ (__printf0__, fmtarg, firstvararg)))
+#endif
+
+#define MAXTYPES 50            /* max number of node types */
+#define MAXFIELDS 20           /* max fields in a structure */
+#define BUFLEN 100             /* size of character buffers */
+
+/* field types */
+#define T_NODE 1               /* union node *field */
+#define T_NODELIST 2           /* struct nodelist *field */
+#define T_STRING 3
+#define T_INT 4                        /* int field */
+#define T_OTHER 5              /* other */
+#define T_TEMP 6               /* don't copy this field */
+
+
+struct field {                 /* a structure field */
+       char *name;             /* name of field */
+       int type;                       /* type of field */
+       char *decl;             /* declaration of field */
+};
+
+
+struct str {                   /* struct representing a node structure */
+       char *tag;              /* structure tag */
+       int nfields;            /* number of fields in the structure */
+       struct field field[MAXFIELDS];  /* the fields of the structure */
+       int done;                       /* set if fully parsed */
+};
+
+
+static int ntypes;                     /* number of node types */
+static char *nodename[MAXTYPES];       /* names of the nodes */
+static struct str *nodestr[MAXTYPES];  /* type of structure used by the node */
+static int nstr;                       /* number of structures */
+static struct str str[MAXTYPES];       /* the structures */
+static struct str *curstr;             /* current structure */
+static FILE *infp;
+static char line[1024];
+static int linno;
+static char *linep;
+
+static void parsenode(void);
+static void parsefield(void);
+static void output(char *);
+static void outsizes(FILE *);
+static void outfunc(FILE *, int);
+static void indent(int, FILE *);
+static int nextfield(char *);
+static void skipbl(void);
+static int readline(void);
+static void error(const char *, ...) __printf0like(1, 2) __dead2;
+static char *savestr(const char *);
+
+
+int
+main(int argc, char *argv[])
+{
+       if (argc != 3)
+               error("usage: mknodes file");
+       infp = stdin;
+       if ((infp = fopen(argv[1], "r")) == NULL)
+               error("Can't open %s: %s", argv[1], strerror(errno));
+       while (readline()) {
+               if (line[0] == ' ' || line[0] == '\t')
+                       parsefield();
+               else if (line[0] != '\0')
+                       parsenode();
+       }
+       output(argv[2]);
+       exit(0);
+}
+
+
+
+static void
+parsenode(void)
+{
+       char name[BUFLEN];
+       char tag[BUFLEN];
+       struct str *sp;
+
+       if (curstr && curstr->nfields > 0)
+               curstr->done = 1;
+       nextfield(name);
+       if (! nextfield(tag))
+               error("Tag expected");
+       if (*linep != '\0')
+               error("Garbage at end of line");
+       nodename[ntypes] = savestr(name);
+       for (sp = str ; sp < str + nstr ; sp++) {
+               if (strcmp(sp->tag, tag) == 0)
+                       break;
+       }
+       if (sp >= str + nstr) {
+               sp->tag = savestr(tag);
+               sp->nfields = 0;
+               curstr = sp;
+               nstr++;
+       }
+       nodestr[ntypes] = sp;
+       ntypes++;
+}
+
+
+static void
+parsefield(void)
+{
+       char name[BUFLEN];
+       char type[BUFLEN];
+       char decl[2 * BUFLEN];
+       struct field *fp;
+
+       if (curstr == NULL || curstr->done)
+               error("No current structure to add field to");
+       if (! nextfield(name))
+               error("No field name");
+       if (! nextfield(type))
+               error("No field type");
+       fp = &curstr->field[curstr->nfields];
+       fp->name = savestr(name);
+       if (strcmp(type, "nodeptr") == 0) {
+               fp->type = T_NODE;
+               sprintf(decl, "union node *%s", name);
+       } else if (strcmp(type, "nodelist") == 0) {
+               fp->type = T_NODELIST;
+               sprintf(decl, "struct nodelist *%s", name);
+       } else if (strcmp(type, "string") == 0) {
+               fp->type = T_STRING;
+               sprintf(decl, "char *%s", name);
+       } else if (strcmp(type, "int") == 0) {
+               fp->type = T_INT;
+               sprintf(decl, "int %s", name);
+       } else if (strcmp(type, "other") == 0) {
+               fp->type = T_OTHER;
+       } else if (strcmp(type, "temp") == 0) {
+               fp->type = T_TEMP;
+       } else {
+               error("Unknown type %s", type);
+       }
+       if (fp->type == T_OTHER || fp->type == T_TEMP) {
+               skipbl();
+               fp->decl = savestr(linep);
+       } else {
+               if (*linep)
+                       error("Garbage at end of line");
+               fp->decl = savestr(decl);
+       }
+       curstr->nfields++;
+}
+
+
+static const char writer[] = "\
+/*\n\
+ * This file was generated by the mknodes program.\n\
+ */\n\
+\n";
+
+static void
+output(char *file)
+{
+       FILE *hfile;
+       FILE *cfile;
+       FILE *patfile;
+       int i;
+       struct str *sp;
+       struct field *fp;
+       char *p;
+
+       if ((patfile = fopen(file, "r")) == NULL)
+               error("Can't open %s: %s", file, strerror(errno));
+       if ((hfile = fopen("nodes.h", "w")) == NULL)
+               error("Can't create nodes.h: %s", strerror(errno));
+       if ((cfile = fopen("nodes.c", "w")) == NULL)
+               error("Can't create nodes.c");
+       fputs(writer, hfile);
+       for (i = 0 ; i < ntypes ; i++)
+               fprintf(hfile, "#define %s %d\n", nodename[i], i);
+       fputs("\n\n\n", hfile);
+       for (sp = str ; sp < &str[nstr] ; sp++) {
+               fprintf(hfile, "struct %s {\n", sp->tag);
+               for (i = sp->nfields, fp = sp->field ; --i >= 0 ; fp++) {
+                       fprintf(hfile, "      %s;\n", fp->decl);
+               }
+               fputs("};\n\n\n", hfile);
+       }
+       fputs("union node {\n", hfile);
+       fprintf(hfile, "      int type;\n");
+       for (sp = str ; sp < &str[nstr] ; sp++) {
+               fprintf(hfile, "      struct %s %s;\n", sp->tag, sp->tag);
+       }
+       fputs("};\n\n\n", hfile);
+       fputs("struct nodelist {\n", hfile);
+       fputs("\tstruct nodelist *next;\n", hfile);
+       fputs("\tunion node *n;\n", hfile);
+       fputs("};\n\n\n", hfile);
+       fputs("struct funcdef;\n", hfile);
+       fputs("struct funcdef *copyfunc(union node *);\n", hfile);
+       fputs("union node *getfuncnode(struct funcdef *);\n", hfile);
+       fputs("void reffunc(struct funcdef *);\n", hfile);
+       fputs("void unreffunc(struct funcdef *);\n", hfile);
+
+       fputs(writer, cfile);
+       while (fgets(line, sizeof line, patfile) != NULL) {
+               for (p = line ; *p == ' ' || *p == '\t' ; p++);
+               if (strcmp(p, "%SIZES\n") == 0)
+                       outsizes(cfile);
+               else if (strcmp(p, "%CALCSIZE\n") == 0)
+                       outfunc(cfile, 1);
+               else if (strcmp(p, "%COPY\n") == 0)
+                       outfunc(cfile, 0);
+               else
+                       fputs(line, cfile);
+       }
+}
+
+
+
+static void
+outsizes(FILE *cfile)
+{
+       int i;
+
+       fprintf(cfile, "static const short nodesize[%d] = {\n", ntypes);
+       for (i = 0 ; i < ntypes ; i++) {
+               fprintf(cfile, "      ALIGN(sizeof (struct %s)),\n", nodestr[i]->tag);
+       }
+       fprintf(cfile, "};\n");
+}
+
+
+static void
+outfunc(FILE *cfile, int calcsize)
+{
+       struct str *sp;
+       struct field *fp;
+       int i;
+
+       fputs("      if (n == NULL)\n", cfile);
+       if (calcsize)
+               fputs("     return;\n", cfile);
+       else
+               fputs("     return NULL;\n", cfile);
+       if (calcsize)
+               fputs("      funcblocksize += nodesize[n->type];\n", cfile);
+       else {
+               fputs("      new = funcblock;\n", cfile);
+               fputs("      funcblock = (char *)funcblock + nodesize[n->type];\n", cfile);
+       }
+       fputs("      switch (n->type) {\n", cfile);
+       for (sp = str ; sp < &str[nstr] ; sp++) {
+               for (i = 0 ; i < ntypes ; i++) {
+                       if (nodestr[i] == sp)
+                               fprintf(cfile, "      case %s:\n", nodename[i]);
+               }
+               for (i = sp->nfields ; --i >= 1 ; ) {
+                       fp = &sp->field[i];
+                       switch (fp->type) {
+                       case T_NODE:
+                               if (calcsize) {
+                                       indent(12, cfile);
+                                       fprintf(cfile, "calcsize(n->%s.%s);\n",
+                                               sp->tag, fp->name);
+                               } else {
+                                       indent(12, cfile);
+                                       fprintf(cfile, "new->%s.%s = copynode(n->%s.%s);\n",
+                                               sp->tag, fp->name, sp->tag, fp->name);
+                               }
+                               break;
+                       case T_NODELIST:
+                               if (calcsize) {
+                                       indent(12, cfile);
+                                       fprintf(cfile, "sizenodelist(n->%s.%s);\n",
+                                               sp->tag, fp->name);
+                               } else {
+                                       indent(12, cfile);
+                                       fprintf(cfile, "new->%s.%s = copynodelist(n->%s.%s);\n",
+                                               sp->tag, fp->name, sp->tag, fp->name);
+                               }
+                               break;
+                       case T_STRING:
+                               if (calcsize) {
+                                       indent(12, cfile);
+                                       fprintf(cfile, "funcstringsize += strlen(n->%s.%s) + 1;\n",
+                                               sp->tag, fp->name);
+                               } else {
+                                       indent(12, cfile);
+                                       fprintf(cfile, "new->%s.%s = nodesavestr(n->%s.%s);\n",
+                                               sp->tag, fp->name, sp->tag, fp->name);
+                               }
+                               break;
+                       case T_INT:
+                       case T_OTHER:
+                               if (! calcsize) {
+                                       indent(12, cfile);
+                                       fprintf(cfile, "new->%s.%s = n->%s.%s;\n",
+                                               sp->tag, fp->name, sp->tag, fp->name);
+                               }
+                               break;
+                       }
+               }
+               indent(12, cfile);
+               fputs("break;\n", cfile);
+       }
+       fputs("      };\n", cfile);
+       if (! calcsize)
+               fputs("      new->type = n->type;\n", cfile);
+}
+
+
+static void
+indent(int amount, FILE *fp)
+{
+       while (amount >= 8) {
+               putc('\t', fp);
+               amount -= 8;
+       }
+       while (--amount >= 0) {
+               putc(' ', fp);
+       }
+}
+
+
+static int
+nextfield(char *buf)
+{
+       char *p, *q;
+
+       p = linep;
+       while (*p == ' ' || *p == '\t')
+               p++;
+       q = buf;
+       while (*p != ' ' && *p != '\t' && *p != '\0')
+               *q++ = *p++;
+       *q = '\0';
+       linep = p;
+       return (q > buf);
+}
+
+
+static void
+skipbl(void)
+{
+       while (*linep == ' ' || *linep == '\t')
+               linep++;
+}
+
+
+static int
+readline(void)
+{
+       char *p;
+
+       if (fgets(line, 1024, infp) == NULL)
+               return 0;
+       for (p = line ; *p != '#' && *p != '\n' && *p != '\0' ; p++);
+       while (p > line && (p[-1] == ' ' || p[-1] == '\t'))
+               p--;
+       *p = '\0';
+       linep = line;
+       linno++;
+       if (p - line > BUFLEN)
+               error("Line too long");
+       return 1;
+}
+
+
+
+static void
+error(const char *msg, ...)
+{
+       va_list va;
+       va_start(va, msg);
+
+       (void) fprintf(stderr, "line %d: ", linno);
+       (void) vfprintf(stderr, msg, va);
+       (void) fputc('\n', stderr);
+
+       va_end(va);
+
+       exit(2);
+}
+
+
+
+static char *
+savestr(const char *s)
+{
+       char *p;
+
+       if ((p = malloc(strlen(s) + 1)) == NULL)
+               error("Out of space");
+       (void) strcpy(p, s);
+       return p;
+}
diff --git a/sh/mksyntax.c b/sh/mksyntax.c
new file mode 100644 (file)
index 0000000..1f81828
--- /dev/null
@@ -0,0 +1,329 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static char const copyright[] =
+"@(#) Copyright (c) 1991, 1993\n\
+       The Regents of the University of California.  All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)mksyntax.c 8.2 (Berkeley) 5/4/95";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * This program creates syntax.h and syntax.c.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "parser.h"
+
+
+struct synclass {
+       const char *name;
+       const char *comment;
+};
+
+/* Syntax classes */
+static const struct synclass synclass[] = {
+       { "CWORD",      "character is nothing special" },
+       { "CNL",        "newline character" },
+       { "CBACK",      "a backslash character" },
+       { "CSBACK",     "a backslash character in single quotes" },
+       { "CSQUOTE",    "single quote" },
+       { "CDQUOTE",    "double quote" },
+       { "CENDQUOTE",  "a terminating quote" },
+       { "CBQUOTE",    "backwards single quote" },
+       { "CVAR",       "a dollar sign" },
+       { "CENDVAR",    "a '}' character" },
+       { "CLP",        "a left paren in arithmetic" },
+       { "CRP",        "a right paren in arithmetic" },
+       { "CEOF",       "end of file" },
+       { "CCTL",       "like CWORD, except it must be escaped" },
+       { "CSPCL",      "these terminate a word" },
+       { "CIGN",       "character should be ignored" },
+       { NULL,         NULL }
+};
+
+
+/*
+ * Syntax classes for is_ functions.  Warning:  if you add new classes
+ * you may have to change the definition of the is_in_name macro.
+ */
+static const struct synclass is_entry[] = {
+       { "ISDIGIT",    "a digit" },
+       { "ISUPPER",    "an upper case letter" },
+       { "ISLOWER",    "a lower case letter" },
+       { "ISUNDER",    "an underscore" },
+       { "ISSPECL",    "the name of a special parameter" },
+       { NULL,         NULL }
+};
+
+static const char writer[] = "\
+/*\n\
+ * This file was generated by the mksyntax program.\n\
+ */\n\
+\n";
+
+
+static FILE *cfile;
+static FILE *hfile;
+
+static void add_default(void);
+static void finish(void);
+static void init(const char *);
+static void add(const char *, const char *);
+static void output_type_macros(void);
+
+int
+main(int argc __unused, char **argv __unused)
+{
+       int i;
+       char buf[80];
+       int pos;
+
+       /* Create output files */
+       if ((cfile = fopen("syntax.c", "w")) == NULL) {
+               perror("syntax.c");
+               exit(2);
+       }
+       if ((hfile = fopen("syntax.h", "w")) == NULL) {
+               perror("syntax.h");
+               exit(2);
+       }
+       fputs(writer, hfile);
+       fputs(writer, cfile);
+
+       fputs("#include <sys/cdefs.h>\n", hfile);
+       fputs("#include <limits.h>\n\n", hfile);
+
+       /* Generate the #define statements in the header file */
+       fputs("/* Syntax classes */\n", hfile);
+       for (i = 0 ; synclass[i].name ; i++) {
+               sprintf(buf, "#define %s %d", synclass[i].name, i);
+               fputs(buf, hfile);
+               for (pos = strlen(buf) ; pos < 32 ; pos = (pos + 8) & ~07)
+                       putc('\t', hfile);
+               fprintf(hfile, "/* %s */\n", synclass[i].comment);
+       }
+       putc('\n', hfile);
+       fputs("/* Syntax classes for is_ functions */\n", hfile);
+       for (i = 0 ; is_entry[i].name ; i++) {
+               sprintf(buf, "#define %s %#o", is_entry[i].name, 1 << i);
+               fputs(buf, hfile);
+               for (pos = strlen(buf) ; pos < 32 ; pos = (pos + 8) & ~07)
+                       putc('\t', hfile);
+               fprintf(hfile, "/* %s */\n", is_entry[i].comment);
+       }
+       putc('\n', hfile);
+       fputs("#define SYNBASE (1 - CHAR_MIN)\n", hfile);
+       fputs("#define PEOF -SYNBASE\n\n", hfile);
+       putc('\n', hfile);
+       fputs("#define BASESYNTAX (basesyntax + SYNBASE)\n", hfile);
+       fputs("#define DQSYNTAX (dqsyntax + SYNBASE)\n", hfile);
+       fputs("#define SQSYNTAX (sqsyntax + SYNBASE)\n", hfile);
+       fputs("#define ARISYNTAX (arisyntax + SYNBASE)\n", hfile);
+       putc('\n', hfile);
+       output_type_macros();           /* is_digit, etc. */
+       putc('\n', hfile);
+
+       /* Generate the syntax tables. */
+       fputs("#include \"parser.h\"\n", cfile);
+       fputs("#include \"shell.h\"\n", cfile);
+       fputs("#include \"syntax.h\"\n\n", cfile);
+
+       fputs("/* syntax table used when not in quotes */\n", cfile);
+       init("basesyntax");
+       add_default();
+       add("\n", "CNL");
+       add("\\", "CBACK");
+       add("'", "CSQUOTE");
+       add("\"", "CDQUOTE");
+       add("`", "CBQUOTE");
+       add("$", "CVAR");
+       add("}", "CENDVAR");
+       add("<>();&| \t", "CSPCL");
+       finish();
+
+       fputs("\n/* syntax table used when in double quotes */\n", cfile);
+       init("dqsyntax");
+       add_default();
+       add("\n", "CNL");
+       add("\\", "CBACK");
+       add("\"", "CENDQUOTE");
+       add("`", "CBQUOTE");
+       add("$", "CVAR");
+       add("}", "CENDVAR");
+       /* ':/' for tilde expansion, '-^]' for [a\-x] pattern ranges */
+       add("!*?[]=~:/-^", "CCTL");
+       finish();
+
+       fputs("\n/* syntax table used when in single quotes */\n", cfile);
+       init("sqsyntax");
+       add_default();
+       add("\n", "CNL");
+       add("\\", "CSBACK");
+       add("'", "CENDQUOTE");
+       /* ':/' for tilde expansion, '-^]' for [a\-x] pattern ranges */
+       add("!*?[]=~:/-^", "CCTL");
+       finish();
+
+       fputs("\n/* syntax table used when in arithmetic */\n", cfile);
+       init("arisyntax");
+       add_default();
+       add("\n", "CNL");
+       add("\\", "CBACK");
+       add("`", "CBQUOTE");
+       add("\"", "CIGN");
+       add("$", "CVAR");
+       add("}", "CENDVAR");
+       add("(", "CLP");
+       add(")", "CRP");
+       finish();
+
+       fputs("\n/* character classification table */\n", cfile);
+       init("is_type");
+       add("0123456789", "ISDIGIT");
+       add("abcdefghijklmnopqrstuvwxyz", "ISLOWER");
+       add("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "ISUPPER");
+       add("_", "ISUNDER");
+       add("#?$!-*@", "ISSPECL");
+       finish();
+
+       exit(0);
+}
+
+
+/*
+ * Output the header and declaration of a syntax table.
+ */
+
+static void
+init(const char *name)
+{
+       fprintf(hfile, "extern const char %s[];\n", name);
+       fprintf(cfile, "const char %s[SYNBASE + CHAR_MAX + 1] = {\n", name);
+}
+
+
+static void
+add_one(const char *key, const char *type)
+{
+       fprintf(cfile, "\t[SYNBASE + %s] = %s,\n", key, type);
+}
+
+
+/*
+ * Add default values to the syntax table.
+ */
+
+static void
+add_default(void)
+{
+       add_one("PEOF",                "CEOF");
+       add_one("CTLESC",              "CCTL");
+       add_one("CTLVAR",              "CCTL");
+       add_one("CTLENDVAR",           "CCTL");
+       add_one("CTLBACKQ",            "CCTL");
+       add_one("CTLBACKQ + CTLQUOTE", "CCTL");
+       add_one("CTLARI",              "CCTL");
+       add_one("CTLENDARI",           "CCTL");
+       add_one("CTLQUOTEMARK",        "CCTL");
+       add_one("CTLQUOTEEND",         "CCTL");
+}
+
+
+/*
+ * Output the footer of a syntax table.
+ */
+
+static void
+finish(void)
+{
+       fputs("};\n", cfile);
+}
+
+
+/*
+ * Add entries to the syntax table.
+ */
+
+static void
+add(const char *p, const char *type)
+{
+       for (; *p; ++p) {
+               char c = *p;
+               switch (c) {
+               case '\t': c = 't';  break;
+               case '\n': c = 'n';  break;
+               case '\'': c = '\''; break;
+               case '\\': c = '\\'; break;
+
+               default:
+                       fprintf(cfile, "\t[SYNBASE + '%c'] = %s,\n", c, type);
+                       continue;
+               }
+               fprintf(cfile, "\t[SYNBASE + '\\%c'] = %s,\n", c, type);
+       }
+}
+
+
+/*
+ * Output character classification macros (e.g. is_digit).  If digits are
+ * contiguous, we can test for them quickly.
+ */
+
+static const char *macro[] = {
+       "#define is_digit(c)\t((unsigned int)((c) - '0') <= 9)",
+       "#define is_eof(c)\t((c) == PEOF)",
+       "#define is_alpha(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER))",
+       "#define is_name(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER|ISUNDER))",
+       "#define is_in_name(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER|ISUNDER|ISDIGIT))",
+       "#define is_special(c)\t((is_type+SYNBASE)[(int)c] & (ISSPECL|ISDIGIT))",
+       "#define digit_val(c)\t((c) - '0')",
+       NULL
+};
+
+static void
+output_type_macros(void)
+{
+       const char **pp;
+
+       for (pp = macro ; *pp ; pp++)
+               fprintf(hfile, "%s\n", *pp);
+}
diff --git a/sh/mktokens b/sh/mktokens
new file mode 100644 (file)
index 0000000..b6f5f97
--- /dev/null
@@ -0,0 +1,93 @@
+#!/bin/sh -
+
+#-
+# Copyright (c) 1991, 1993
+#      The Regents of the University of California.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 4. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#      @(#)mktokens    8.1 (Berkeley) 5/31/93
+# $FreeBSD$
+
+# The following is a list of tokens.  The second column is nonzero if the
+# token marks the end of a list.  The third column is the name to print in
+# error messages.
+
+temp=`/usr/bin/mktemp -t ka`
+cat > $temp <<\!
+TEOF   1       end of file
+TNL    0       newline
+TSEMI  0       ";"
+TBACKGND 0     "&"
+TAND   0       "&&"
+TOR    0       "||"
+TPIPE  0       "|"
+TLP    0       "("
+TRP    1       ")"
+TENDCASE 1     ";;"
+TFALLTHRU 1    ";&"
+TREDIR 0       redirection
+TWORD  0       word
+TIF    0       "if"
+TTHEN  1       "then"
+TELSE  1       "else"
+TELIF  1       "elif"
+TFI    1       "fi"
+TWHILE 0       "while"
+TUNTIL 0       "until"
+TFOR   0       "for"
+TDO    1       "do"
+TDONE  1       "done"
+TBEGIN 0       "{"
+TEND   1       "}"
+TCASE  0       "case"
+TESAC  1       "esac"
+TNOT   0       "!"
+!
+nl=`wc -l $temp`
+exec > token.h
+awk '{print "#define " $1 " " NR-1}' $temp
+echo '
+/* Array indicating which tokens mark the end of a list */
+static const char tokendlist[] = {'
+awk '{print "\t" $2 ","}' $temp
+echo '};
+
+static const char *const tokname[] = {'
+sed -e 's/"/\\"/g' \
+    -e 's/[^    ]*[     ][      ]*[^    ]*[     ][      ]*\(.*\)/      "\1",/' \
+    $temp
+echo '};
+'
+sed 's/"//g' $temp | awk '
+/TIF/{print "#define KWDOFFSET " NR-1; print ""; print "const char *const parsekwd[] = {"}
+/TIF/,/neverfound/{print "     \"" $3 "\","}'
+echo ' 0
+};'
+
+rm $temp
diff --git a/sh/myhistedit.h b/sh/myhistedit.h
new file mode 100644 (file)
index 0000000..e31276d
--- /dev/null
@@ -0,0 +1,42 @@
+/*-
+ * Copyright (c) 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)myhistedit.h        8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+#include <histedit.h>
+
+extern History *hist;
+extern EditLine *el;
+extern int displayhist;
+
+void histedit(void);
+void sethistsize(const char *);
+void setterm(const char *);
+
diff --git a/sh/mystring.c b/sh/mystring.c
new file mode 100644 (file)
index 0000000..19de78d
--- /dev/null
@@ -0,0 +1,98 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)mystring.c 8.2 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * String functions.
+ *
+ *     equal(s1, s2)           Return true if strings are equal.
+ *     number(s)               Convert a string of digits to an integer.
+ *     is_number(s)            Return true if s is a string of digits.
+ */
+
+#include <stdlib.h>
+#include "shell.h"
+#include "syntax.h"
+#include "error.h"
+#include "mystring.h"
+
+
+char nullstr[1];               /* zero length string */
+
+/*
+ * equal - #defined in mystring.h
+ */
+
+
+/*
+ * Convert a string of digits to an integer, printing an error message on
+ * failure.
+ */
+
+int
+number(const char *s)
+{
+       if (! is_number(s))
+               error("Illegal number: %s", s);
+       return atoi(s);
+}
+
+
+
+/*
+ * Check for a valid number.  This should be elsewhere.
+ */
+
+int
+is_number(const char *p)
+{
+       const char *q;
+
+       if (*p == '\0')
+               return 0;
+       while (*p == '0')
+               p++;
+       for (q = p; *q != '\0'; q++)
+               if (! is_digit(*q))
+                       return 0;
+       if (q - p > 10 ||
+           (q - p == 10 && memcmp(p, "2147483647", 10) > 0))
+               return 0;
+       return 1;
+}
diff --git a/sh/mystring.h b/sh/mystring.h
new file mode 100644 (file)
index 0000000..919fc86
--- /dev/null
@@ -0,0 +1,41 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)mystring.h  8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+#include <string.h>
+
+int number(const char *);
+int is_number(const char *);
+
+#define equal(s1, s2)  (strcmp(s1, s2) == 0)
diff --git a/sh/nodes.c.pat b/sh/nodes.c.pat
new file mode 100644 (file)
index 0000000..e99e3ab
--- /dev/null
@@ -0,0 +1,185 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)nodes.c.pat 8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <stdlib.h>
+#include <stddef.h>
+/*
+ * Routine for dealing with parsed shell commands.
+ */
+
+#include "shell.h"
+#include "nodes.h"
+#include "memalloc.h"
+#include "mystring.h"
+
+
+static int     funcblocksize;  /* size of structures in function */
+static int     funcstringsize; /* size of strings in node */
+static pointer funcblock;      /* block to allocate function from */
+static char   *funcstring;     /* block to allocate strings from */
+
+%SIZES
+
+
+static void calcsize(union node *);
+static void sizenodelist(struct nodelist *);
+static union node *copynode(union node *);
+static struct nodelist *copynodelist(struct nodelist *);
+static char *nodesavestr(const char *);
+
+
+struct funcdef {
+       unsigned int refcount;
+       union node n;
+};
+
+/*
+ * Make a copy of a parse tree.
+ */
+
+struct funcdef *
+copyfunc(union node *n)
+{
+       struct funcdef *fn;
+
+       if (n == NULL)
+               return NULL;
+       funcblocksize = offsetof(struct funcdef, n);
+       funcstringsize = 0;
+       calcsize(n);
+       fn = ckmalloc(funcblocksize + funcstringsize);
+       fn->refcount = 1;
+       funcblock = (char *)fn + offsetof(struct funcdef, n);
+       funcstring = (char *)fn + funcblocksize;
+       copynode(n);
+       return fn;
+}
+
+
+union node *
+getfuncnode(struct funcdef *fn)
+{
+       return fn == NULL ? NULL : &fn->n;
+}
+
+
+static void
+calcsize(union node *n)
+{
+       %CALCSIZE
+}
+
+
+
+static void
+sizenodelist(struct nodelist *lp)
+{
+       while (lp) {
+               funcblocksize += ALIGN(sizeof(struct nodelist));
+               calcsize(lp->n);
+               lp = lp->next;
+       }
+}
+
+
+
+static union node *
+copynode(union node *n)
+{
+       union node *new;
+
+       %COPY
+       return new;
+}
+
+
+static struct nodelist *
+copynodelist(struct nodelist *lp)
+{
+       struct nodelist *start;
+       struct nodelist **lpp;
+
+       lpp = &start;
+       while (lp) {
+               *lpp = funcblock;
+               funcblock = (char *)funcblock + ALIGN(sizeof(struct nodelist));
+               (*lpp)->n = copynode(lp->n);
+               lp = lp->next;
+               lpp = &(*lpp)->next;
+       }
+       *lpp = NULL;
+       return start;
+}
+
+
+
+static char *
+nodesavestr(const char *s)
+{
+       const char *p = s;
+       char *q = funcstring;
+       char   *rtn = funcstring;
+
+       while ((*q++ = *p++) != '\0')
+               continue;
+       funcstring = q;
+       return rtn;
+}
+
+
+void
+reffunc(struct funcdef *fn)
+{
+       if (fn)
+               fn->refcount++;
+}
+
+
+/*
+ * Decrement the reference count of a function definition, freeing it
+ * if it falls to 0.
+ */
+
+void
+unreffunc(struct funcdef *fn)
+{
+       if (fn) {
+               fn->refcount--;
+               if (fn->refcount > 0)
+                       return;
+               ckfree(fn);
+       }
+}
diff --git a/sh/nodetypes b/sh/nodetypes
new file mode 100644 (file)
index 0000000..d480093
--- /dev/null
@@ -0,0 +1,145 @@
+#-
+# Copyright (c) 1991, 1993
+#      The Regents of the University of California.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 4. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#      @(#)nodetypes   8.2 (Berkeley) 5/4/95
+# $FreeBSD$
+
+# This file describes the nodes used in parse trees.  Unindented lines
+# contain a node type followed by a structure tag.  Subsequent indented
+# lines specify the fields of the structure.  Several node types can share
+# the same structure, in which case the fields of the structure should be
+# specified only once.
+#
+# A field of a structure is described by the name of the field followed
+# by a type.  The currently implemented types are:
+#      nodeptr - a pointer to a node
+#      nodelist - a pointer to a list of nodes
+#      string - a pointer to a nul terminated string
+#      int - an integer
+#      other - any type that can be copied by assignment
+#      temp - a field that doesn't have to be copied when the node is copied
+# The last two types should be followed by the text of a C declaration for
+# the field.
+
+NSEMI nbinary                  # two commands separated by a semicolon
+       type      int
+       ch1       nodeptr               # the first child
+       ch2       nodeptr               # the second child
+
+NCMD ncmd                      # a simple command
+       type      int
+       args      nodeptr               # the arguments
+       redirect  nodeptr               # list of file redirections
+
+NPIPE npipe                    # a pipeline
+       type      int
+       backgnd   int                   # set to run pipeline in background
+       cmdlist   nodelist              # the commands in the pipeline
+
+NREDIR nredir                  # redirection (of a compex command)
+       type      int
+       n         nodeptr               # the command
+       redirect  nodeptr               # list of file redirections
+
+NBACKGND nredir                        # run command in background
+NSUBSHELL nredir               # run command in a subshell
+
+NAND nbinary                   # the && operator
+NOR nbinary                    # the || operator
+
+NIF nif                                # the if statement.  Elif clauses are handled
+       type      int               # using multiple if nodes.
+       test      nodeptr               # if test
+       ifpart    nodeptr               # then ifpart
+       elsepart  nodeptr               # else elsepart
+
+NWHILE nbinary                 # the while statement.  First child is the test
+NUNTIL nbinary                 # the until statement
+
+NFOR nfor                      # the for statement
+       type      int
+       args      nodeptr               # for var in args
+       body      nodeptr               # do body; done
+       var       string                # the for variable
+
+NCASE ncase                    # a case statement
+       type      int
+       expr      nodeptr               # the word to switch on
+       cases     nodeptr               # the list of cases (NCLIST nodes)
+
+NCLIST nclist                  # a case ending with ;;
+       type      int
+       next      nodeptr               # the next case in list
+       pattern   nodeptr               # list of patterns for this case
+       body      nodeptr               # code to execute for this case
+
+NCLISTFALLTHRU nclist          # a case ending with ;&
+
+NDEFUN narg                    # define a function.  The "next" field contains
+                               # the body of the function.
+
+NARG narg                      # represents a word
+       type      int
+       next      nodeptr               # next word in list
+       text      string                # the text of the word
+       backquote nodelist              # list of commands in back quotes
+
+NTO nfile                      # fd> fname
+NFROM nfile                    # fd< fname
+NFROMTO nfile                  # fd<> fname
+NAPPEND nfile                  # fd>> fname
+NCLOBBER nfile                 # fd>| fname
+       type      int
+       fd        int                   # file descriptor being redirected
+       next      nodeptr               # next redirection in list
+       fname     nodeptr               # file name, in a NARG node
+       expfname  temp  char *expfname  # actual file name
+
+NTOFD ndup                     # fd<&dupfd
+NFROMFD ndup                   # fd>&dupfd
+       type      int
+       fd        int                   # file descriptor being redirected
+       next      nodeptr               # next redirection in list
+       dupfd     int                   # file descriptor to duplicate
+       vname     nodeptr               # file name if fd>&$var
+
+
+NHERE nhere                    # fd<<\!
+NXHERE nhere                   # fd<<!
+       type      int
+       fd        int                   # file descriptor being redirected
+       next      nodeptr               # next redirection in list
+       doc       nodeptr               # input to command (NARG node)
+       expdoc    temp  const char *expdoc      # actual document (for NXHERE)
+
+NNOT nnot                      # ! command  (actually pipeline)
+       type    int
+       com     nodeptr
diff --git a/sh/options.c b/sh/options.c
new file mode 100644 (file)
index 0000000..2d0ddce
--- /dev/null
@@ -0,0 +1,588 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)options.c  8.2 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "shell.h"
+#define DEFINE_OPTIONS
+#include "options.h"
+#undef DEFINE_OPTIONS
+#include "nodes.h"     /* for other header files */
+#include "eval.h"
+#include "jobs.h"
+#include "input.h"
+#include "output.h"
+#include "trap.h"
+#include "var.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "builtins.h"
+#ifndef NO_HISTORY
+#include "myhistedit.h"
+#endif
+
+char *arg0;                    /* value of $0 */
+struct shparam shellparam;     /* current positional parameters */
+char **argptr;                 /* argument list for builtin commands */
+char *shoptarg;                        /* set by nextopt (like getopt) */
+char *nextopt_optptr;          /* used by nextopt */
+
+char *minusc;                  /* argument to -c option */
+
+
+static void options(int);
+static void minus_o(char *, int);
+static void setoption(int, int);
+static int getopts(char *, char *, char **, char ***, char **);
+
+
+/*
+ * Process the shell command line arguments.
+ */
+
+void
+procargs(int argc, char **argv)
+{
+       int i;
+       char *scriptname;
+
+       argptr = argv;
+       if (argc > 0)
+               argptr++;
+       for (i = 0; i < NOPTS; i++)
+               optlist[i].val = 2;
+       privileged = (getuid() != geteuid() || getgid() != getegid());
+       options(1);
+       if (*argptr == NULL && minusc == NULL)
+               sflag = 1;
+       if (iflag != 0 && sflag == 1 && isatty(0) && isatty(1)) {
+               iflag = 1;
+               if (Eflag == 2)
+                       Eflag = 1;
+       }
+       if (mflag == 2)
+               mflag = iflag;
+       for (i = 0; i < NOPTS; i++)
+               if (optlist[i].val == 2)
+                       optlist[i].val = 0;
+       arg0 = argv[0];
+       if (sflag == 0 && minusc == NULL) {
+               scriptname = *argptr++;
+               setinputfile(scriptname, 0);
+               commandname = arg0 = scriptname;
+       }
+       /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */
+       if (argptr && minusc && *argptr)
+               arg0 = *argptr++;
+
+       shellparam.p = argptr;
+       shellparam.reset = 1;
+       /* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */
+       while (*argptr) {
+               shellparam.nparam++;
+               argptr++;
+       }
+       optschanged();
+}
+
+
+void
+optschanged(void)
+{
+       setinteractive(iflag);
+#ifndef NO_HISTORY
+       histedit();
+#endif
+       setjobctl(mflag);
+}
+
+/*
+ * Process shell options.  The global variable argptr contains a pointer
+ * to the argument list; we advance it past the options.
+ */
+
+static void
+options(int cmdline)
+{
+       char *kp, *p;
+       int val;
+       int c;
+
+       if (cmdline)
+               minusc = NULL;
+       while ((p = *argptr) != NULL) {
+               argptr++;
+               if ((c = *p++) == '-') {
+                       val = 1;
+                       /* A "-" or  "--" terminates options */
+                       if (p[0] == '\0')
+                               goto end_options1;
+                       if (p[0] == '-' && p[1] == '\0')
+                               goto end_options2;
+                       /**
+                        * For the benefit of `#!' lines in shell scripts,
+                        * treat a string of '-- *#.*' the same as '--'.
+                        * This is needed so that a script starting with:
+                        *      #!/bin/sh -- # -*- perl -*-
+                        * will continue to work after a change is made to
+                        * kern/imgact_shell.c to NOT token-ize the options
+                        * specified on a '#!' line.  A bit of a kludge,
+                        * but that trick is recommended in documentation
+                        * for some scripting languages, and we might as
+                        * well continue to support it.
+                        */
+                       if (p[0] == '-') {
+                               kp = p + 1;
+                               while (*kp == ' ' || *kp == '\t')
+                                       kp++;
+                               if (*kp == '#' || *kp == '\0')
+                                       goto end_options2;
+                       }
+               } else if (c == '+') {
+                       val = 0;
+               } else {
+                       argptr--;
+                       break;
+               }
+               while ((c = *p++) != '\0') {
+                       if (c == 'c' && cmdline) {
+                               char *q;
+#ifdef NOHACK  /* removing this code allows sh -ce 'foo' for compat */
+                               if (*p == '\0')
+#endif
+                                       q = *argptr++;
+                               if (q == NULL || minusc != NULL)
+                                       error("Bad -c option");
+                               minusc = q;
+#ifdef NOHACK
+                               break;
+#endif
+                       } else if (c == 'o') {
+                               minus_o(*argptr, val);
+                               if (*argptr)
+                                       argptr++;
+                       } else
+                               setoption(c, val);
+               }
+       }
+       return;
+
+       /* When processing `set', a single "-" means turn off -x and -v */
+end_options1:
+       if (!cmdline) {
+               xflag = vflag = 0;
+               return;
+       }
+
+       /*
+        * When processing `set', a "--" means the remaining arguments
+        * replace the positional parameters in the active shell.  If
+        * there are no remaining options, then all the positional
+        * parameters are cleared (equivalent to doing ``shift $#'').
+        */
+end_options2:
+       if (!cmdline) {
+               if (*argptr == NULL)
+                       setparam(argptr);
+               return;
+       }
+
+       /*
+        * At this point we are processing options given to 'sh' on a command
+        * line.  If an end-of-options marker ("-" or "--") is followed by an
+        * arg of "#", then skip over all remaining arguments.  Some scripting
+        * languages (e.g.: perl) document that /bin/sh will implement this
+        * behavior, and they recommend that users take advantage of it to
+        * solve certain issues that can come up when writing a perl script.
+        * Yes, this feature is in /bin/sh to help users write perl scripts.
+        */
+       p = *argptr;
+       if (p != NULL && p[0] == '#' && p[1] == '\0') {
+               while (*argptr != NULL)
+                       argptr++;
+               /* We need to keep the final argument */
+               argptr--;
+       }
+}
+
+static void
+minus_o(char *name, int val)
+{
+       int i;
+
+       if (name == NULL) {
+               if (val) {
+                       /* "Pretty" output. */
+                       out1str("Current option settings\n");
+                       for (i = 0; i < NOPTS; i++)
+                               out1fmt("%-16s%s\n", optlist[i].name,
+                                       optlist[i].val ? "on" : "off");
+               } else {
+                       /* Output suitable for re-input to shell. */
+                       for (i = 0; i < NOPTS; i++)
+                               out1fmt("%s %co %s%s",
+                                   i % 6 == 0 ? "set" : "",
+                                   optlist[i].val ? '-' : '+',
+                                   optlist[i].name,
+                                   i % 6 == 5 || i == NOPTS - 1 ? "\n" : "");
+               }
+       } else {
+               for (i = 0; i < NOPTS; i++)
+                       if (equal(name, optlist[i].name)) {
+                               setoption(optlist[i].letter, val);
+                               return;
+                       }
+               error("Illegal option -o %s", name);
+       }
+}
+
+
+static void
+setoption(int flag, int val)
+{
+       int i;
+
+       if (flag == 'p' && !val && privileged) {
+               if (setgid(getgid()) == -1)
+                       error("setgid");
+               if (setuid(getuid()) == -1)
+                       error("setuid");
+       }
+       for (i = 0; i < NOPTS; i++)
+               if (optlist[i].letter == flag) {
+                       optlist[i].val = val;
+                       if (val) {
+                               /* #%$ hack for ksh semantics */
+                               if (flag == 'V')
+                                       Eflag = 0;
+                               else if (flag == 'E')
+                                       Vflag = 0;
+                       }
+                       return;
+               }
+       error("Illegal option -%c", flag);
+}
+
+
+/*
+ * Set the shell parameters.
+ */
+
+void
+setparam(char **argv)
+{
+       char **newparam;
+       char **ap;
+       int nparam;
+
+       for (nparam = 0 ; argv[nparam] ; nparam++);
+       ap = newparam = ckmalloc((nparam + 1) * sizeof *ap);
+       while (*argv) {
+               *ap++ = savestr(*argv++);
+       }
+       *ap = NULL;
+       freeparam(&shellparam);
+       shellparam.malloc = 1;
+       shellparam.nparam = nparam;
+       shellparam.p = newparam;
+       shellparam.optp = NULL;
+       shellparam.reset = 1;
+       shellparam.optnext = NULL;
+}
+
+
+/*
+ * Free the list of positional parameters.
+ */
+
+void
+freeparam(struct shparam *param)
+{
+       char **ap;
+
+       if (param->malloc) {
+               for (ap = param->p ; *ap ; ap++)
+                       ckfree(*ap);
+               ckfree(param->p);
+       }
+       if (param->optp) {
+               for (ap = param->optp ; *ap ; ap++)
+                       ckfree(*ap);
+               ckfree(param->optp);
+       }
+}
+
+
+
+/*
+ * The shift builtin command.
+ */
+
+int
+shiftcmd(int argc, char **argv)
+{
+       int n;
+       char **ap1, **ap2;
+
+       n = 1;
+       if (argc > 1)
+               n = number(argv[1]);
+       if (n > shellparam.nparam)
+               return 1;
+       INTOFF;
+       shellparam.nparam -= n;
+       for (ap1 = shellparam.p ; --n >= 0 ; ap1++) {
+               if (shellparam.malloc)
+                       ckfree(*ap1);
+       }
+       ap2 = shellparam.p;
+       while ((*ap2++ = *ap1++) != NULL);
+       shellparam.reset = 1;
+       INTON;
+       return 0;
+}
+
+
+
+/*
+ * The set command builtin.
+ */
+
+int
+setcmd(int argc, char **argv)
+{
+       if (argc == 1)
+               return showvarscmd(argc, argv);
+       INTOFF;
+       options(0);
+       optschanged();
+       if (*argptr != NULL) {
+               setparam(argptr);
+       }
+       INTON;
+       return 0;
+}
+
+
+void
+getoptsreset(const char *value)
+{
+       while (*value == '0')
+               value++;
+       if (strcmp(value, "1") == 0)
+               shellparam.reset = 1;
+}
+
+/*
+ * The getopts builtin.  Shellparam.optnext points to the next argument
+ * to be processed.  Shellparam.optptr points to the next character to
+ * be processed in the current argument.  If shellparam.optnext is NULL,
+ * then it's the first time getopts has been called.
+ */
+
+int
+getoptscmd(int argc, char **argv)
+{
+       char **optbase = NULL, **ap;
+       int i;
+
+       if (argc < 3)
+               error("usage: getopts optstring var [arg]");
+
+       if (shellparam.reset == 1) {
+               INTOFF;
+               if (shellparam.optp) {
+                       for (ap = shellparam.optp ; *ap ; ap++)
+                               ckfree(*ap);
+                       ckfree(shellparam.optp);
+                       shellparam.optp = NULL;
+               }
+               if (argc > 3) {
+                       shellparam.optp = ckmalloc((argc - 2) * sizeof *ap);
+                       memset(shellparam.optp, '\0', (argc - 2) * sizeof *ap);
+                       for (i = 0; i < argc - 3; i++)
+                               shellparam.optp[i] = savestr(argv[i + 3]);
+               }
+               INTON;
+               optbase = argc == 3 ? shellparam.p : shellparam.optp;
+               shellparam.optnext = optbase;
+               shellparam.optptr = NULL;
+               shellparam.reset = 0;
+       } else
+               optbase = shellparam.optp ? shellparam.optp : shellparam.p;
+
+       return getopts(argv[1], argv[2], optbase, &shellparam.optnext,
+                      &shellparam.optptr);
+}
+
+static int
+getopts(char *optstr, char *optvar, char **optfirst, char ***optnext,
+    char **optptr)
+{
+       char *p, *q;
+       char c = '?';
+       int done = 0;
+       int ind = 0;
+       int err = 0;
+       char s[10];
+       const char *newoptarg = NULL;
+
+       if ((p = *optptr) == NULL || *p == '\0') {
+               /* Current word is done, advance */
+               if (*optnext == NULL)
+                       return 1;
+               p = **optnext;
+               if (p == NULL || *p != '-' || *++p == '\0') {
+atend:
+                       ind = *optnext - optfirst + 1;
+                       *optnext = NULL;
+                       p = NULL;
+                       done = 1;
+                       goto out;
+               }
+               (*optnext)++;
+               if (p[0] == '-' && p[1] == '\0')        /* check for "--" */
+                       goto atend;
+       }
+
+       c = *p++;
+       for (q = optstr; *q != c; ) {
+               if (*q == '\0') {
+                       if (optstr[0] == ':') {
+                               s[0] = c;
+                               s[1] = '\0';
+                               newoptarg = s;
+                       }
+                       else
+                               out2fmt_flush("Illegal option -%c\n", c);
+                       c = '?';
+                       goto out;
+               }
+               if (*++q == ':')
+                       q++;
+       }
+
+       if (*++q == ':') {
+               if (*p == '\0' && (p = **optnext) == NULL) {
+                       if (optstr[0] == ':') {
+                               s[0] = c;
+                               s[1] = '\0';
+                               newoptarg = s;
+                               c = ':';
+                       }
+                       else {
+                               out2fmt_flush("No arg for -%c option\n", c);
+                               c = '?';
+                       }
+                       goto out;
+               }
+
+               if (p == **optnext)
+                       (*optnext)++;
+               newoptarg = p;
+               p = NULL;
+       }
+
+out:
+       if (*optnext != NULL)
+               ind = *optnext - optfirst + 1;
+       *optptr = p;
+       if (newoptarg != NULL)
+               err |= setvarsafe("OPTARG", newoptarg, 0);
+       else {
+               INTOFF;
+               err |= unsetvar("OPTARG");
+               INTON;
+       }
+       fmtstr(s, sizeof(s), "%d", ind);
+       err |= setvarsafe("OPTIND", s, VNOFUNC);
+       s[0] = c;
+       s[1] = '\0';
+       err |= setvarsafe(optvar, s, 0);
+       if (err) {
+               *optnext = NULL;
+               *optptr = NULL;
+               flushall();
+               exraise(EXERROR);
+       }
+       return done;
+}
+
+/*
+ * Standard option processing (a la getopt) for builtin routines.  The
+ * only argument that is passed to nextopt is the option string; the
+ * other arguments are unnecessary.  It return the character, or '\0' on
+ * end of input.
+ */
+
+int
+nextopt(const char *optstring)
+{
+       char *p;
+       const char *q;
+       char c;
+
+       if ((p = nextopt_optptr) == NULL || *p == '\0') {
+               p = *argptr;
+               if (p == NULL || *p != '-' || *++p == '\0')
+                       return '\0';
+               argptr++;
+               if (p[0] == '-' && p[1] == '\0')        /* check for "--" */
+                       return '\0';
+       }
+       c = *p++;
+       for (q = optstring ; *q != c ; ) {
+               if (*q == '\0')
+                       error("Illegal option -%c", c);
+               if (*++q == ':')
+                       q++;
+       }
+       if (*++q == ':') {
+               if (*p == '\0' && (p = *argptr++) == NULL)
+                       error("No arg for -%c option", c);
+               shoptarg = p;
+               p = NULL;
+       }
+       nextopt_optptr = p;
+       return c;
+}
diff --git a/sh/options.h b/sh/options.h
new file mode 100644 (file)
index 0000000..2048a35
--- /dev/null
@@ -0,0 +1,114 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)options.h   8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+struct shparam {
+       int nparam;             /* # of positional parameters (without $0) */
+       unsigned char malloc;   /* if parameter list dynamically allocated */
+       unsigned char reset;    /* if getopts has been reset */
+       char **p;               /* parameter list */
+       char **optp;            /* parameter list for getopts */
+       char **optnext;         /* next parameter to be processed by getopts */
+       char *optptr;           /* used by getopts */
+};
+
+
+
+#define eflag optlist[0].val
+#define fflag optlist[1].val
+#define Iflag optlist[2].val
+#define iflag optlist[3].val
+#define mflag optlist[4].val
+#define nflag optlist[5].val
+#define sflag optlist[6].val
+#define xflag optlist[7].val
+#define vflag optlist[8].val
+#define Vflag optlist[9].val
+#define        Eflag optlist[10].val
+#define        Cflag optlist[11].val
+#define        aflag optlist[12].val
+#define        bflag optlist[13].val
+#define        uflag optlist[14].val
+#define        privileged optlist[15].val
+#define        Tflag optlist[16].val
+#define        Pflag optlist[17].val
+#define        hflag optlist[18].val
+
+#define NOPTS  19
+
+struct optent {
+       const char *name;
+       const char letter;
+       char val;
+};
+
+extern struct optent optlist[NOPTS];
+#ifdef DEFINE_OPTIONS
+struct optent optlist[NOPTS] = {
+       { "errexit",    'e',    0 },
+       { "noglob",     'f',    0 },
+       { "ignoreeof",  'I',    0 },
+       { "interactive",'i',    0 },
+       { "monitor",    'm',    0 },
+       { "noexec",     'n',    0 },
+       { "stdin",      's',    0 },
+       { "xtrace",     'x',    0 },
+       { "verbose",    'v',    0 },
+       { "vi",         'V',    0 },
+       { "emacs",      'E',    0 },
+       { "noclobber",  'C',    0 },
+       { "allexport",  'a',    0 },
+       { "notify",     'b',    0 },
+       { "nounset",    'u',    0 },
+       { "privileged", 'p',    0 },
+       { "trapsasync", 'T',    0 },
+       { "physical",   'P',    0 },
+       { "trackall",   'h',    0 },
+};
+#endif
+
+
+extern char *minusc;           /* argument to -c option */
+extern char *arg0;             /* $0 */
+extern struct shparam shellparam;  /* $@ */
+extern char **argptr;          /* argument list for builtin commands */
+extern char *shoptarg;         /* set by nextopt */
+extern char *nextopt_optptr;   /* used by nextopt */
+
+void procargs(int, char **);
+void optschanged(void);
+void setparam(char **);
+void freeparam(struct shparam *);
+int nextopt(const char *);
+void getoptsreset(const char *);
diff --git a/sh/output.c b/sh/output.c
new file mode 100644 (file)
index 0000000..39b722f
--- /dev/null
@@ -0,0 +1,375 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)output.c   8.2 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Shell output routines.  We use our own output routines because:
+ *     When a builtin command is interrupted we have to discard
+ *             any pending output.
+ *     When a builtin command appears in back quotes, we want to
+ *             save the output of the command in a region obtained
+ *             via malloc, rather than doing a fork and reading the
+ *             output of the command via a pipe.
+ */
+
+#include <stdio.h>     /* defines BUFSIZ */
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include "shell.h"
+#include "syntax.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "var.h"
+
+
+#define OUTBUFSIZ BUFSIZ
+#define MEM_OUT -2             /* output to dynamically allocated memory */
+#define OUTPUT_ERR 01          /* error occurred on output */
+
+static int doformat_wr(void *, const char *, int);
+
+struct output output = {NULL, 0, NULL, OUTBUFSIZ, 1, 0};
+struct output errout = {NULL, 0, NULL, 256, 2, 0};
+struct output memout = {NULL, 0, NULL, 0, MEM_OUT, 0};
+struct output *out1 = &output;
+struct output *out2 = &errout;
+
+void
+outcslow(int c, struct output *file)
+{
+       outc(c, file);
+}
+
+void
+out1str(const char *p)
+{
+       outstr(p, out1);
+}
+
+void
+out1qstr(const char *p)
+{
+       outqstr(p, out1);
+}
+
+void
+out2str(const char *p)
+{
+       outstr(p, out2);
+}
+
+void
+out2qstr(const char *p)
+{
+       outqstr(p, out2);
+}
+
+void
+outstr(const char *p, struct output *file)
+{
+       outbin(p, strlen(p), file);
+}
+
+static void
+byteseq(int ch, struct output *file)
+{
+       char seq[4];
+
+       seq[0] = '\\';
+       seq[1] = (ch >> 6 & 0x3) + '0';
+       seq[2] = (ch >> 3 & 0x7) + '0';
+       seq[3] = (ch & 0x7) + '0';
+       outbin(seq, 4, file);
+}
+
+static void
+outdqstr(const char *p, struct output *file)
+{
+       const char *end;
+       mbstate_t mbs;
+       size_t clen;
+       wchar_t wc;
+
+       memset(&mbs, '\0', sizeof(mbs));
+       end = p + strlen(p);
+       outstr("$'", file);
+       while ((clen = mbrtowc(&wc, p, end - p + 1, &mbs)) != 0) {
+               if (clen == (size_t)-2) {
+                       while (p < end)
+                               byteseq(*p++, file);
+                       break;
+               }
+               if (clen == (size_t)-1) {
+                       memset(&mbs, '\0', sizeof(mbs));
+                       byteseq(*p++, file);
+                       continue;
+               }
+               if (wc == L'\n')
+                       outcslow('\n', file), p++;
+               else if (wc == L'\r')
+                       outstr("\\r", file), p++;
+               else if (wc == L'\t')
+                       outstr("\\t", file), p++;
+               else if (!iswprint(wc)) {
+                       for (; clen > 0; clen--)
+                               byteseq(*p++, file);
+               } else {
+                       if (wc == L'\'' || wc == L'\\')
+                               outcslow('\\', file);
+                       outbin(p, clen, file);
+                       p += clen;
+               }
+       }
+       outcslow('\'', file);
+}
+
+/* Like outstr(), but quote for re-input into the shell. */
+void
+outqstr(const char *p, struct output *file)
+{
+       int i;
+
+       if (p[0] == '\0') {
+               outstr("''", file);
+               return;
+       }
+       for (i = 0; p[i] != '\0'; i++) {
+               if ((p[i] > '\0' && p[i] < ' ' && p[i] != '\n') ||
+                   (p[i] & 0x80) != 0 || p[i] == '\'') {
+                       outdqstr(p, file);
+                       return;
+               }
+       }
+
+       if (p[strcspn(p, "|&;<>()$`\\\" \n*?[~#=")] == '\0' ||
+                       strcmp(p, "[") == 0) {
+               outstr(p, file);
+               return;
+       }
+
+       outcslow('\'', file);
+       outstr(p, file);
+       outcslow('\'', file);
+}
+
+void
+outbin(const void *data, size_t len, struct output *file)
+{
+       const char *p;
+
+       p = data;
+       while (len-- > 0)
+               outc(*p++, file);
+}
+
+void
+emptyoutbuf(struct output *dest)
+{
+       int offset;
+
+       if (dest->buf == NULL) {
+               INTOFF;
+               dest->buf = ckmalloc(dest->bufsize);
+               dest->nextc = dest->buf;
+               dest->nleft = dest->bufsize;
+               INTON;
+       } else if (dest->fd == MEM_OUT) {
+               offset = dest->bufsize;
+               INTOFF;
+               dest->bufsize <<= 1;
+               dest->buf = ckrealloc(dest->buf, dest->bufsize);
+               dest->nleft = dest->bufsize - offset;
+               dest->nextc = dest->buf + offset;
+               INTON;
+       } else {
+               flushout(dest);
+       }
+       dest->nleft--;
+}
+
+
+void
+flushall(void)
+{
+       flushout(&output);
+       flushout(&errout);
+}
+
+
+void
+flushout(struct output *dest)
+{
+
+       if (dest->buf == NULL || dest->nextc == dest->buf || dest->fd < 0)
+               return;
+       if (xwrite(dest->fd, dest->buf, dest->nextc - dest->buf) < 0)
+               dest->flags |= OUTPUT_ERR;
+       dest->nextc = dest->buf;
+       dest->nleft = dest->bufsize;
+}
+
+
+void
+freestdout(void)
+{
+       INTOFF;
+       if (output.buf) {
+               ckfree(output.buf);
+               output.buf = NULL;
+               output.nleft = 0;
+       }
+       INTON;
+}
+
+
+int
+outiserror(struct output *file)
+{
+       return (file->flags & OUTPUT_ERR);
+}
+
+
+void
+outclearerror(struct output *file)
+{
+       file->flags &= ~OUTPUT_ERR;
+}
+
+
+void
+outfmt(struct output *file, const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       doformat(file, fmt, ap);
+       va_end(ap);
+}
+
+
+void
+out1fmt(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       doformat(out1, fmt, ap);
+       va_end(ap);
+}
+
+void
+out2fmt_flush(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       doformat(out2, fmt, ap);
+       va_end(ap);
+       flushout(out2);
+}
+
+void
+fmtstr(char *outbuf, int length, const char *fmt, ...)
+{
+       va_list ap;
+
+       INTOFF;
+       va_start(ap, fmt);
+       vsnprintf(outbuf, length, fmt, ap);
+       va_end(ap);
+       INTON;
+}
+
+static int
+doformat_wr(void *cookie, const char *buf, int len)
+{
+       struct output *o;
+
+       o = (struct output *)cookie;
+       outbin(buf, len, o);
+
+       return (len);
+}
+
+void
+doformat(struct output *dest, const char *f, va_list ap)
+{
+       FILE *fp;
+
+       if ((fp = fwopen(dest, doformat_wr)) != NULL) {
+               vfprintf(fp, f, ap);
+               fclose(fp);
+       }
+}
+
+/*
+ * Version of write which resumes after a signal is caught.
+ */
+
+int
+xwrite(int fd, const char *buf, int nbytes)
+{
+       int ntry;
+       int i;
+       int n;
+
+       n = nbytes;
+       ntry = 0;
+       for (;;) {
+               i = write(fd, buf, n);
+               if (i > 0) {
+                       if ((n -= i) <= 0)
+                               return nbytes;
+                       buf += i;
+                       ntry = 0;
+               } else if (i == 0) {
+                       if (++ntry > 10)
+                               return nbytes - n;
+               } else if (errno != EINTR) {
+                       return -1;
+               }
+       }
+}
diff --git a/sh/output.h b/sh/output.h
new file mode 100644 (file)
index 0000000..51974d8
--- /dev/null
@@ -0,0 +1,83 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)output.h    8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+#ifndef OUTPUT_INCL
+
+#include <stdarg.h>
+#include <stddef.h>
+
+struct output {
+       char *nextc;
+       int nleft;
+       char *buf;
+       int bufsize;
+       short fd;
+       short flags;
+};
+
+extern struct output output; /* to fd 1 */
+extern struct output errout; /* to fd 2 */
+extern struct output memout;
+extern struct output *out1; /* &memout if backquote, otherwise &output */
+extern struct output *out2; /* &memout if backquote with 2>&1, otherwise
+                              &errout */
+
+void outcslow(int, struct output *);
+void out1str(const char *);
+void out1qstr(const char *);
+void out2str(const char *);
+void out2qstr(const char *);
+void outstr(const char *, struct output *);
+void outqstr(const char *, struct output *);
+void outbin(const void *, size_t, struct output *);
+void emptyoutbuf(struct output *);
+void flushall(void);
+void flushout(struct output *);
+void freestdout(void);
+int outiserror(struct output *);
+void outclearerror(struct output *);
+void outfmt(struct output *, const char *, ...) __printflike(2, 3);
+void out1fmt(const char *, ...) __printflike(1, 2);
+void out2fmt_flush(const char *, ...) __printflike(1, 2);
+void fmtstr(char *, int, const char *, ...) __printflike(3, 4);
+void doformat(struct output *, const char *, va_list) __printflike(2, 0);
+int xwrite(int, const char *, int);
+
+#define outc(c, file)  (--(file)->nleft < 0? (emptyoutbuf(file), *(file)->nextc++ = (c)) : (*(file)->nextc++ = (c)))
+#define out1c(c)       outc(c, out1);
+#define out2c(c)       outcslow(c, out2);
+
+#define OUTPUT_INCL
+#endif
diff --git a/sh/parser.c b/sh/parser.c
new file mode 100644 (file)
index 0000000..2bba84e
--- /dev/null
@@ -0,0 +1,2072 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)parser.c   8.7 (Berkeley) 5/16/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include "shell.h"
+#include "parser.h"
+#include "nodes.h"
+#include "expand.h"    /* defines rmescapes() */
+#include "syntax.h"
+#include "options.h"
+#include "input.h"
+#include "output.h"
+#include "var.h"
+#include "error.h"
+#include "memalloc.h"
+#include "mystring.h"
+#include "alias.h"
+#include "show.h"
+#include "eval.h"
+#include "exec.h"      /* to check for special builtins */
+#ifndef NO_HISTORY
+#include "myhistedit.h"
+#endif
+
+/*
+ * Shell command parser.
+ */
+
+#define        PROMPTLEN       128
+
+/* values of checkkwd variable */
+#define CHKALIAS       0x1
+#define CHKKWD         0x2
+#define CHKNL          0x4
+
+/* values returned by readtoken */
+#include "token.h"
+
+
+
+struct heredoc {
+       struct heredoc *next;   /* next here document in list */
+       union node *here;               /* redirection node */
+       char *eofmark;          /* string indicating end of input */
+       int striptabs;          /* if set, strip leading tabs */
+};
+
+struct parser_temp {
+       struct parser_temp *next;
+       void *data;
+};
+
+
+static struct heredoc *heredoclist;    /* list of here documents to read */
+static int doprompt;           /* if set, prompt the user */
+static int needprompt;         /* true if interactive and at start of line */
+static int lasttoken;          /* last token read */
+static int tokpushback;                /* last token pushed back */
+static char *wordtext;         /* text of last word returned by readtoken */
+static int checkkwd;
+static struct nodelist *backquotelist;
+static union node *redirnode;
+static struct heredoc *heredoc;
+static int quoteflag;          /* set if (part of) last token was quoted */
+static int startlinno;         /* line # where last token started */
+static int funclinno;          /* line # where the current function started */
+static struct parser_temp *parser_temp;
+
+
+static union node *list(int);
+static union node *andor(void);
+static union node *pipeline(void);
+static union node *command(void);
+static union node *simplecmd(union node **, union node *);
+static union node *makename(void);
+static union node *makebinary(int type, union node *n1, union node *n2);
+static void parsefname(void);
+static void parseheredoc(void);
+static int peektoken(void);
+static int readtoken(void);
+static int xxreadtoken(void);
+static int readtoken1(int, const char *, const char *, int);
+static int noexpand(char *);
+static void consumetoken(int);
+static void synexpect(int) __dead2;
+static void synerror(const char *) __dead2;
+static void setprompt(int);
+static int pgetc_linecont(void);
+
+
+static void *
+parser_temp_alloc(size_t len)
+{
+       struct parser_temp *t;
+
+       INTOFF;
+       t = ckmalloc(sizeof(*t));
+       t->data = NULL;
+       t->next = parser_temp;
+       parser_temp = t;
+       t->data = ckmalloc(len);
+       INTON;
+       return t->data;
+}
+
+
+static void *
+parser_temp_realloc(void *ptr, size_t len)
+{
+       struct parser_temp *t;
+
+       INTOFF;
+       t = parser_temp;
+       if (ptr != t->data)
+               error("bug: parser_temp_realloc misused");
+       t->data = ckrealloc(t->data, len);
+       INTON;
+       return t->data;
+}
+
+
+static void
+parser_temp_free_upto(void *ptr)
+{
+       struct parser_temp *t;
+       int done = 0;
+
+       INTOFF;
+       while (parser_temp != NULL && !done) {
+               t = parser_temp;
+               parser_temp = t->next;
+               done = t->data == ptr;
+               ckfree(t->data);
+               ckfree(t);
+       }
+       INTON;
+       if (!done)
+               error("bug: parser_temp_free_upto misused");
+}
+
+
+static void
+parser_temp_free_all(void)
+{
+       struct parser_temp *t;
+
+       INTOFF;
+       while (parser_temp != NULL) {
+               t = parser_temp;
+               parser_temp = t->next;
+               ckfree(t->data);
+               ckfree(t);
+       }
+       INTON;
+}
+
+
+/*
+ * Read and parse a command.  Returns NEOF on end of file.  (NULL is a
+ * valid parse tree indicating a blank line.)
+ */
+
+union node *
+parsecmd(int interact)
+{
+       int t;
+
+       /* This assumes the parser is not re-entered,
+        * which could happen if we add command substitution on PS1/PS2.
+        */
+       parser_temp_free_all();
+       heredoclist = NULL;
+
+       tokpushback = 0;
+       checkkwd = 0;
+       doprompt = interact;
+       if (doprompt)
+               setprompt(1);
+       else
+               setprompt(0);
+       needprompt = 0;
+       t = readtoken();
+       if (t == TEOF)
+               return NEOF;
+       if (t == TNL)
+               return NULL;
+       tokpushback++;
+       return list(1);
+}
+
+
+static union node *
+list(int nlflag)
+{
+       union node *ntop, *n1, *n2, *n3;
+       int tok;
+
+       checkkwd = CHKNL | CHKKWD | CHKALIAS;
+       if (!nlflag && tokendlist[peektoken()])
+               return NULL;
+       ntop = n1 = NULL;
+       for (;;) {
+               n2 = andor();
+               tok = readtoken();
+               if (tok == TBACKGND) {
+                       if (n2 != NULL && n2->type == NPIPE) {
+                               n2->npipe.backgnd = 1;
+                       } else if (n2 != NULL && n2->type == NREDIR) {
+                               n2->type = NBACKGND;
+                       } else {
+                               n3 = (union node *)stalloc(sizeof (struct nredir));
+                               n3->type = NBACKGND;
+                               n3->nredir.n = n2;
+                               n3->nredir.redirect = NULL;
+                               n2 = n3;
+                       }
+               }
+               if (ntop == NULL)
+                       ntop = n2;
+               else if (n1 == NULL) {
+                       n1 = makebinary(NSEMI, ntop, n2);
+                       ntop = n1;
+               }
+               else {
+                       n3 = makebinary(NSEMI, n1->nbinary.ch2, n2);
+                       n1->nbinary.ch2 = n3;
+                       n1 = n3;
+               }
+               switch (tok) {
+               case TBACKGND:
+               case TSEMI:
+                       tok = readtoken();
+                       /* FALLTHROUGH */
+               case TNL:
+                       if (tok == TNL) {
+                               parseheredoc();
+                               if (nlflag)
+                                       return ntop;
+                       } else if (tok == TEOF && nlflag) {
+                               parseheredoc();
+                               return ntop;
+                       } else {
+                               tokpushback++;
+                       }
+                       checkkwd = CHKNL | CHKKWD | CHKALIAS;
+                       if (!nlflag && tokendlist[peektoken()])
+                               return ntop;
+                       break;
+               case TEOF:
+                       if (heredoclist)
+                               parseheredoc();
+                       else
+                               pungetc();              /* push back EOF on input */
+                       return ntop;
+               default:
+                       if (nlflag)
+                               synexpect(-1);
+                       tokpushback++;
+                       return ntop;
+               }
+       }
+}
+
+
+
+static union node *
+andor(void)
+{
+       union node *n;
+       int t;
+
+       n = pipeline();
+       for (;;) {
+               if ((t = readtoken()) == TAND) {
+                       t = NAND;
+               } else if (t == TOR) {
+                       t = NOR;
+               } else {
+                       tokpushback++;
+                       return n;
+               }
+               n = makebinary(t, n, pipeline());
+       }
+}
+
+
+
+static union node *
+pipeline(void)
+{
+       union node *n1, *n2, *pipenode;
+       struct nodelist *lp, *prev;
+       int negate, t;
+
+       negate = 0;
+       checkkwd = CHKNL | CHKKWD | CHKALIAS;
+       TRACE(("pipeline: entered\n"));
+       while (readtoken() == TNOT)
+               negate = !negate;
+       tokpushback++;
+       n1 = command();
+       if (readtoken() == TPIPE) {
+               pipenode = (union node *)stalloc(sizeof (struct npipe));
+               pipenode->type = NPIPE;
+               pipenode->npipe.backgnd = 0;
+               lp = (struct nodelist *)stalloc(sizeof (struct nodelist));
+               pipenode->npipe.cmdlist = lp;
+               lp->n = n1;
+               do {
+                       prev = lp;
+                       lp = (struct nodelist *)stalloc(sizeof (struct nodelist));
+                       checkkwd = CHKNL | CHKKWD | CHKALIAS;
+                       t = readtoken();
+                       tokpushback++;
+                       if (t == TNOT)
+                               lp->n = pipeline();
+                       else
+                               lp->n = command();
+                       prev->next = lp;
+               } while (readtoken() == TPIPE);
+               lp->next = NULL;
+               n1 = pipenode;
+       }
+       tokpushback++;
+       if (negate) {
+               n2 = (union node *)stalloc(sizeof (struct nnot));
+               n2->type = NNOT;
+               n2->nnot.com = n1;
+               return n2;
+       } else
+               return n1;
+}
+
+
+
+static union node *
+command(void)
+{
+       union node *n1, *n2;
+       union node *ap, **app;
+       union node *cp, **cpp;
+       union node *redir, **rpp;
+       int t;
+       int is_subshell;
+
+       checkkwd = CHKNL | CHKKWD | CHKALIAS;
+       is_subshell = 0;
+       redir = NULL;
+       n1 = NULL;
+       rpp = &redir;
+
+       /* Check for redirection which may precede command */
+       while (readtoken() == TREDIR) {
+               *rpp = n2 = redirnode;
+               rpp = &n2->nfile.next;
+               parsefname();
+       }
+       tokpushback++;
+
+       switch (readtoken()) {
+       case TIF:
+               n1 = (union node *)stalloc(sizeof (struct nif));
+               n1->type = NIF;
+               if ((n1->nif.test = list(0)) == NULL)
+                       synexpect(-1);
+               consumetoken(TTHEN);
+               n1->nif.ifpart = list(0);
+               n2 = n1;
+               while (readtoken() == TELIF) {
+                       n2->nif.elsepart = (union node *)stalloc(sizeof (struct nif));
+                       n2 = n2->nif.elsepart;
+                       n2->type = NIF;
+                       if ((n2->nif.test = list(0)) == NULL)
+                               synexpect(-1);
+                       consumetoken(TTHEN);
+                       n2->nif.ifpart = list(0);
+               }
+               if (lasttoken == TELSE)
+                       n2->nif.elsepart = list(0);
+               else {
+                       n2->nif.elsepart = NULL;
+                       tokpushback++;
+               }
+               consumetoken(TFI);
+               checkkwd = CHKKWD | CHKALIAS;
+               break;
+       case TWHILE:
+       case TUNTIL:
+               t = lasttoken;
+               if ((n1 = list(0)) == NULL)
+                       synexpect(-1);
+               consumetoken(TDO);
+               n1 = makebinary((t == TWHILE)? NWHILE : NUNTIL, n1, list(0));
+               consumetoken(TDONE);
+               checkkwd = CHKKWD | CHKALIAS;
+               break;
+       case TFOR:
+               if (readtoken() != TWORD || quoteflag || ! goodname(wordtext))
+                       synerror("Bad for loop variable");
+               n1 = (union node *)stalloc(sizeof (struct nfor));
+               n1->type = NFOR;
+               n1->nfor.var = wordtext;
+               while (readtoken() == TNL)
+                       ;
+               if (lasttoken == TWORD && ! quoteflag && equal(wordtext, "in")) {
+                       app = &ap;
+                       while (readtoken() == TWORD) {
+                               n2 = makename();
+                               *app = n2;
+                               app = &n2->narg.next;
+                       }
+                       *app = NULL;
+                       n1->nfor.args = ap;
+                       if (lasttoken != TNL && lasttoken != TSEMI)
+                               synexpect(-1);
+               } else {
+                       static char argvars[5] = {
+                               CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0'
+                       };
+                       n2 = (union node *)stalloc(sizeof (struct narg));
+                       n2->type = NARG;
+                       n2->narg.text = argvars;
+                       n2->narg.backquote = NULL;
+                       n2->narg.next = NULL;
+                       n1->nfor.args = n2;
+                       /*
+                        * Newline or semicolon here is optional (but note
+                        * that the original Bourne shell only allowed NL).
+                        */
+                       if (lasttoken != TNL && lasttoken != TSEMI)
+                               tokpushback++;
+               }
+               checkkwd = CHKNL | CHKKWD | CHKALIAS;
+               if ((t = readtoken()) == TDO)
+                       t = TDONE;
+               else if (t == TBEGIN)
+                       t = TEND;
+               else
+                       synexpect(-1);
+               n1->nfor.body = list(0);
+               consumetoken(t);
+               checkkwd = CHKKWD | CHKALIAS;
+               break;
+       case TCASE:
+               n1 = (union node *)stalloc(sizeof (struct ncase));
+               n1->type = NCASE;
+               consumetoken(TWORD);
+               n1->ncase.expr = makename();
+               while (readtoken() == TNL);
+               if (lasttoken != TWORD || ! equal(wordtext, "in"))
+                       synerror("expecting \"in\"");
+               cpp = &n1->ncase.cases;
+               checkkwd = CHKNL | CHKKWD, readtoken();
+               while (lasttoken != TESAC) {
+                       *cpp = cp = (union node *)stalloc(sizeof (struct nclist));
+                       cp->type = NCLIST;
+                       app = &cp->nclist.pattern;
+                       if (lasttoken == TLP)
+                               readtoken();
+                       for (;;) {
+                               *app = ap = makename();
+                               checkkwd = CHKNL | CHKKWD;
+                               if (readtoken() != TPIPE)
+                                       break;
+                               app = &ap->narg.next;
+                               readtoken();
+                       }
+                       ap->narg.next = NULL;
+                       if (lasttoken != TRP)
+                               synexpect(TRP);
+                       cp->nclist.body = list(0);
+
+                       checkkwd = CHKNL | CHKKWD | CHKALIAS;
+                       if ((t = readtoken()) != TESAC) {
+                               if (t == TENDCASE)
+                                       ;
+                               else if (t == TFALLTHRU)
+                                       cp->type = NCLISTFALLTHRU;
+                               else
+                                       synexpect(TENDCASE);
+                               checkkwd = CHKNL | CHKKWD, readtoken();
+                       }
+                       cpp = &cp->nclist.next;
+               }
+               *cpp = NULL;
+               checkkwd = CHKKWD | CHKALIAS;
+               break;
+       case TLP:
+               n1 = (union node *)stalloc(sizeof (struct nredir));
+               n1->type = NSUBSHELL;
+               n1->nredir.n = list(0);
+               n1->nredir.redirect = NULL;
+               consumetoken(TRP);
+               checkkwd = CHKKWD | CHKALIAS;
+               is_subshell = 1;
+               break;
+       case TBEGIN:
+               n1 = list(0);
+               consumetoken(TEND);
+               checkkwd = CHKKWD | CHKALIAS;
+               break;
+       /* A simple command must have at least one redirection or word. */
+       case TBACKGND:
+       case TSEMI:
+       case TAND:
+       case TOR:
+       case TPIPE:
+       case TENDCASE:
+       case TFALLTHRU:
+       case TEOF:
+       case TNL:
+       case TRP:
+               if (!redir)
+                       synexpect(-1);
+       case TWORD:
+               tokpushback++;
+               n1 = simplecmd(rpp, redir);
+               return n1;
+       default:
+               synexpect(-1);
+       }
+
+       /* Now check for redirection which may follow command */
+       while (readtoken() == TREDIR) {
+               *rpp = n2 = redirnode;
+               rpp = &n2->nfile.next;
+               parsefname();
+       }
+       tokpushback++;
+       *rpp = NULL;
+       if (redir) {
+               if (!is_subshell) {
+                       n2 = (union node *)stalloc(sizeof (struct nredir));
+                       n2->type = NREDIR;
+                       n2->nredir.n = n1;
+                       n1 = n2;
+               }
+               n1->nredir.redirect = redir;
+       }
+
+       return n1;
+}
+
+
+static union node *
+simplecmd(union node **rpp, union node *redir)
+{
+       union node *args, **app;
+       union node **orig_rpp = rpp;
+       union node *n = NULL;
+       int special;
+       int savecheckkwd;
+
+       /* If we don't have any redirections already, then we must reset */
+       /* rpp to be the address of the local redir variable.  */
+       if (redir == 0)
+               rpp = &redir;
+
+       args = NULL;
+       app = &args;
+       /*
+        * We save the incoming value, because we need this for shell
+        * functions.  There can not be a redirect or an argument between
+        * the function name and the open parenthesis.
+        */
+       orig_rpp = rpp;
+
+       savecheckkwd = CHKALIAS;
+
+       for (;;) {
+               checkkwd = savecheckkwd;
+               if (readtoken() == TWORD) {
+                       n = makename();
+                       *app = n;
+                       app = &n->narg.next;
+                       if (savecheckkwd != 0 && !isassignment(wordtext))
+                               savecheckkwd = 0;
+               } else if (lasttoken == TREDIR) {
+                       *rpp = n = redirnode;
+                       rpp = &n->nfile.next;
+                       parsefname();   /* read name of redirection file */
+               } else if (lasttoken == TLP && app == &args->narg.next
+                                           && rpp == orig_rpp) {
+                       /* We have a function */
+                       consumetoken(TRP);
+                       funclinno = plinno;
+                       /*
+                        * - Require plain text.
+                        * - Functions with '/' cannot be called.
+                        * - Reject name=().
+                        * - Reject ksh extended glob patterns.
+                        */
+                       if (!noexpand(n->narg.text) || quoteflag ||
+                           strchr(n->narg.text, '/') ||
+                           strchr("!%*+-=?@}~",
+                               n->narg.text[strlen(n->narg.text) - 1]))
+                               synerror("Bad function name");
+                       rmescapes(n->narg.text);
+                       if (find_builtin(n->narg.text, &special) >= 0 &&
+                           special)
+                               synerror("Cannot override a special builtin with a function");
+                       n->type = NDEFUN;
+                       n->narg.next = command();
+                       funclinno = 0;
+                       return n;
+               } else {
+                       tokpushback++;
+                       break;
+               }
+       }
+       *app = NULL;
+       *rpp = NULL;
+       n = (union node *)stalloc(sizeof (struct ncmd));
+       n->type = NCMD;
+       n->ncmd.args = args;
+       n->ncmd.redirect = redir;
+       return n;
+}
+
+static union node *
+makename(void)
+{
+       union node *n;
+
+       n = (union node *)stalloc(sizeof (struct narg));
+       n->type = NARG;
+       n->narg.next = NULL;
+       n->narg.text = wordtext;
+       n->narg.backquote = backquotelist;
+       return n;
+}
+
+static union node *
+makebinary(int type, union node *n1, union node *n2)
+{
+       union node *n;
+
+       n = (union node *)stalloc(sizeof (struct nbinary));
+       n->type = type;
+       n->nbinary.ch1 = n1;
+       n->nbinary.ch2 = n2;
+       return (n);
+}
+
+void
+forcealias(void)
+{
+       checkkwd |= CHKALIAS;
+}
+
+void
+fixredir(union node *n, const char *text, int err)
+{
+       TRACE(("Fix redir %s %d\n", text, err));
+       if (!err)
+               n->ndup.vname = NULL;
+
+       if (is_digit(text[0]) && text[1] == '\0')
+               n->ndup.dupfd = digit_val(text[0]);
+       else if (text[0] == '-' && text[1] == '\0')
+               n->ndup.dupfd = -1;
+       else {
+
+               if (err)
+                       synerror("Bad fd number");
+               else
+                       n->ndup.vname = makename();
+       }
+}
+
+
+static void
+parsefname(void)
+{
+       union node *n = redirnode;
+
+       consumetoken(TWORD);
+       if (n->type == NHERE) {
+               struct heredoc *here = heredoc;
+               struct heredoc *p;
+
+               if (quoteflag == 0)
+                       n->type = NXHERE;
+               TRACE(("Here document %d\n", n->type));
+               if (here->striptabs) {
+                       while (*wordtext == '\t')
+                               wordtext++;
+               }
+               if (! noexpand(wordtext))
+                       synerror("Illegal eof marker for << redirection");
+               rmescapes(wordtext);
+               here->eofmark = wordtext;
+               here->next = NULL;
+               if (heredoclist == NULL)
+                       heredoclist = here;
+               else {
+                       for (p = heredoclist ; p->next ; p = p->next);
+                       p->next = here;
+               }
+       } else if (n->type == NTOFD || n->type == NFROMFD) {
+               fixredir(n, wordtext, 0);
+       } else {
+               n->nfile.fname = makename();
+       }
+}
+
+
+/*
+ * Input any here documents.
+ */
+
+static void
+parseheredoc(void)
+{
+       struct heredoc *here;
+       union node *n;
+
+       while (heredoclist) {
+               here = heredoclist;
+               heredoclist = here->next;
+               if (needprompt) {
+                       setprompt(2);
+                       needprompt = 0;
+               }
+               readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX,
+                               here->eofmark, here->striptabs);
+               n = makename();
+               here->here->nhere.doc = n;
+       }
+}
+
+static int
+peektoken(void)
+{
+       int t;
+
+       t = readtoken();
+       tokpushback++;
+       return (t);
+}
+
+static int
+readtoken(void)
+{
+       int t;
+       struct alias *ap;
+#ifdef DEBUG
+       int alreadyseen = tokpushback;
+#endif
+
+       top:
+       t = xxreadtoken();
+
+       /*
+        * eat newlines
+        */
+       if (checkkwd & CHKNL) {
+               while (t == TNL) {
+                       parseheredoc();
+                       t = xxreadtoken();
+               }
+       }
+
+       /*
+        * check for keywords and aliases
+        */
+       if (t == TWORD && !quoteflag)
+       {
+               const char * const *pp;
+
+               if (checkkwd & CHKKWD)
+                       for (pp = parsekwd; *pp; pp++) {
+                               if (**pp == *wordtext && equal(*pp, wordtext))
+                               {
+                                       lasttoken = t = pp - parsekwd + KWDOFFSET;
+                                       TRACE(("keyword %s recognized\n", tokname[t]));
+                                       goto out;
+                               }
+                       }
+               if (checkkwd & CHKALIAS &&
+                   (ap = lookupalias(wordtext, 1)) != NULL) {
+                       pushstring(ap->val, strlen(ap->val), ap);
+                       goto top;
+               }
+       }
+out:
+       if (t != TNOT)
+               checkkwd = 0;
+
+#ifdef DEBUG
+       if (!alreadyseen)
+           TRACE(("token %s %s\n", tokname[t], t == TWORD ? wordtext : ""));
+       else
+           TRACE(("reread token %s %s\n", tokname[t], t == TWORD ? wordtext : ""));
+#endif
+       return (t);
+}
+
+
+/*
+ * Read the next input token.
+ * If the token is a word, we set backquotelist to the list of cmds in
+ *     backquotes.  We set quoteflag to true if any part of the word was
+ *     quoted.
+ * If the token is TREDIR, then we set redirnode to a structure containing
+ *     the redirection.
+ * In all cases, the variable startlinno is set to the number of the line
+ *     on which the token starts.
+ *
+ * [Change comment:  here documents and internal procedures]
+ * [Readtoken shouldn't have any arguments.  Perhaps we should make the
+ *  word parsing code into a separate routine.  In this case, readtoken
+ *  doesn't need to have any internal procedures, but parseword does.
+ *  We could also make parseoperator in essence the main routine, and
+ *  have parseword (readtoken1?) handle both words and redirection.]
+ */
+
+#define RETURN(token)  return lasttoken = token
+
+static int
+xxreadtoken(void)
+{
+       int c;
+
+       if (tokpushback) {
+               tokpushback = 0;
+               return lasttoken;
+       }
+       if (needprompt) {
+               setprompt(2);
+               needprompt = 0;
+       }
+       startlinno = plinno;
+       for (;;) {      /* until token or start of word found */
+               c = pgetc_macro();
+               switch (c) {
+               case ' ': case '\t':
+                       continue;
+               case '#':
+                       while ((c = pgetc()) != '\n' && c != PEOF);
+                       pungetc();
+                       continue;
+               case '\\':
+                       if (pgetc() == '\n') {
+                               startlinno = ++plinno;
+                               if (doprompt)
+                                       setprompt(2);
+                               else
+                                       setprompt(0);
+                               continue;
+                       }
+                       pungetc();
+                       /* FALLTHROUGH */
+               default:
+                       return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
+               case '\n':
+                       plinno++;
+                       needprompt = doprompt;
+                       RETURN(TNL);
+               case PEOF:
+                       RETURN(TEOF);
+               case '&':
+                       if (pgetc_linecont() == '&')
+                               RETURN(TAND);
+                       pungetc();
+                       RETURN(TBACKGND);
+               case '|':
+                       if (pgetc_linecont() == '|')
+                               RETURN(TOR);
+                       pungetc();
+                       RETURN(TPIPE);
+               case ';':
+                       c = pgetc_linecont();
+                       if (c == ';')
+                               RETURN(TENDCASE);
+                       else if (c == '&')
+                               RETURN(TFALLTHRU);
+                       pungetc();
+                       RETURN(TSEMI);
+               case '(':
+                       RETURN(TLP);
+               case ')':
+                       RETURN(TRP);
+               }
+       }
+#undef RETURN
+}
+
+
+#define MAXNEST_static 8
+struct tokenstate
+{
+       const char *syntax; /* *SYNTAX */
+       int parenlevel; /* levels of parentheses in arithmetic */
+       enum tokenstate_category
+       {
+               TSTATE_TOP,
+               TSTATE_VAR_OLD, /* ${var+-=?}, inherits dquotes */
+               TSTATE_VAR_NEW, /* other ${var...}, own dquote state */
+               TSTATE_ARITH
+       } category;
+};
+
+
+/*
+ * Check to see whether we are at the end of the here document.  When this
+ * is called, c is set to the first character of the next input line.  If
+ * we are at the end of the here document, this routine sets the c to PEOF.
+ * The new value of c is returned.
+ */
+
+static int
+checkend(int c, const char *eofmark, int striptabs)
+{
+       if (striptabs) {
+               while (c == '\t')
+                       c = pgetc();
+       }
+       if (c == *eofmark) {
+               int c2;
+               const char *q;
+
+               for (q = eofmark + 1; c2 = pgetc(), *q != '\0' && c2 == *q; q++)
+                       ;
+               if ((c2 == PEOF || c2 == '\n') && *q == '\0') {
+                       c = PEOF;
+                       if (c2 == '\n') {
+                               plinno++;
+                               needprompt = doprompt;
+                       }
+               } else {
+                       pungetc();
+                       pushstring(eofmark + 1, q - (eofmark + 1), NULL);
+               }
+       }
+       return (c);
+}
+
+
+/*
+ * Parse a redirection operator.  The variable "out" points to a string
+ * specifying the fd to be redirected.  The variable "c" contains the
+ * first character of the redirection operator.
+ */
+
+static void
+parseredir(char *out, int c)
+{
+       char fd = *out;
+       union node *np;
+
+       np = (union node *)stalloc(sizeof (struct nfile));
+       if (c == '>') {
+               np->nfile.fd = 1;
+               c = pgetc_linecont();
+               if (c == '>')
+                       np->type = NAPPEND;
+               else if (c == '&')
+                       np->type = NTOFD;
+               else if (c == '|')
+                       np->type = NCLOBBER;
+               else {
+                       np->type = NTO;
+                       pungetc();
+               }
+       } else {        /* c == '<' */
+               np->nfile.fd = 0;
+               c = pgetc_linecont();
+               if (c == '<') {
+                       if (sizeof (struct nfile) != sizeof (struct nhere)) {
+                               np = (union node *)stalloc(sizeof (struct nhere));
+                               np->nfile.fd = 0;
+                       }
+                       np->type = NHERE;
+                       heredoc = (struct heredoc *)stalloc(sizeof (struct heredoc));
+                       heredoc->here = np;
+                       if ((c = pgetc_linecont()) == '-') {
+                               heredoc->striptabs = 1;
+                       } else {
+                               heredoc->striptabs = 0;
+                               pungetc();
+                       }
+               } else if (c == '&')
+                       np->type = NFROMFD;
+               else if (c == '>')
+                       np->type = NFROMTO;
+               else {
+                       np->type = NFROM;
+                       pungetc();
+               }
+       }
+       if (fd != '\0')
+               np->nfile.fd = digit_val(fd);
+       redirnode = np;
+}
+
+/*
+ * Called to parse command substitutions.
+ */
+
+static char *
+parsebackq(char *out, struct nodelist **pbqlist,
+               int oldstyle, int dblquote, int quoted)
+{
+       struct nodelist **nlpp;
+       union node *n;
+       char *volatile str;
+       struct jmploc jmploc;
+       struct jmploc *const savehandler = handler;
+       size_t savelen;
+       int saveprompt;
+       const int bq_startlinno = plinno;
+       char *volatile ostr = NULL;
+       struct parsefile *const savetopfile = getcurrentfile();
+       struct heredoc *const saveheredoclist = heredoclist;
+       struct heredoc *here;
+
+       str = NULL;
+       if (setjmp(jmploc.loc)) {
+               popfilesupto(savetopfile);
+               if (str)
+                       ckfree(str);
+               if (ostr)
+                       ckfree(ostr);
+               heredoclist = saveheredoclist;
+               handler = savehandler;
+               if (exception == EXERROR) {
+                       startlinno = bq_startlinno;
+                       synerror("Error in command substitution");
+               }
+               longjmp(handler->loc, 1);
+       }
+       INTOFF;
+       savelen = out - stackblock();
+       if (savelen > 0) {
+               str = ckmalloc(savelen);
+               memcpy(str, stackblock(), savelen);
+       }
+       handler = &jmploc;
+       heredoclist = NULL;
+       INTON;
+        if (oldstyle) {
+                /* We must read until the closing backquote, giving special
+                   treatment to some slashes, and then push the string and
+                   reread it as input, interpreting it normally.  */
+                char *oout;
+                int c;
+                int olen;
+
+
+                STARTSTACKSTR(oout);
+               for (;;) {
+                       if (needprompt) {
+                               setprompt(2);
+                               needprompt = 0;
+                       }
+                       CHECKSTRSPACE(2, oout);
+                       c = pgetc_linecont();
+                       if (c == '`')
+                               break;
+                       switch (c) {
+                       case '\\':
+                               c = pgetc();
+                                if (c != '\\' && c != '`' && c != '$'
+                                    && (!dblquote || c != '"'))
+                                        USTPUTC('\\', oout);
+                               break;
+
+                       case '\n':
+                               plinno++;
+                               needprompt = doprompt;
+                               break;
+
+                       case PEOF:
+                               startlinno = plinno;
+                               synerror("EOF in backquote substitution");
+                               break;
+
+                       default:
+                               break;
+                       }
+                       USTPUTC(c, oout);
+                }
+                USTPUTC('\0', oout);
+                olen = oout - stackblock();
+               INTOFF;
+               ostr = ckmalloc(olen);
+               memcpy(ostr, stackblock(), olen);
+               setinputstring(ostr, 1);
+               INTON;
+        }
+       nlpp = pbqlist;
+       while (*nlpp)
+               nlpp = &(*nlpp)->next;
+       *nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist));
+       (*nlpp)->next = NULL;
+
+       if (oldstyle) {
+               saveprompt = doprompt;
+               doprompt = 0;
+       }
+
+       n = list(0);
+
+       if (oldstyle) {
+               if (peektoken() != TEOF)
+                       synexpect(-1);
+               doprompt = saveprompt;
+       } else
+               consumetoken(TRP);
+
+       (*nlpp)->n = n;
+        if (oldstyle) {
+               /*
+                * Start reading from old file again, ignoring any pushed back
+                * tokens left from the backquote parsing
+                */
+                popfile();
+               tokpushback = 0;
+       }
+       STARTSTACKSTR(out);
+       CHECKSTRSPACE(savelen + 1, out);
+       INTOFF;
+       if (str) {
+               memcpy(out, str, savelen);
+               STADJUST(savelen, out);
+               ckfree(str);
+               str = NULL;
+       }
+       if (ostr) {
+               ckfree(ostr);
+               ostr = NULL;
+       }
+       here = saveheredoclist;
+       if (here != NULL) {
+               while (here->next != NULL)
+                       here = here->next;
+               here->next = heredoclist;
+               heredoclist = saveheredoclist;
+       }
+       handler = savehandler;
+       INTON;
+       if (quoted)
+               USTPUTC(CTLBACKQ | CTLQUOTE, out);
+       else
+               USTPUTC(CTLBACKQ, out);
+       return out;
+}
+
+
+/*
+ * Called to parse a backslash escape sequence inside $'...'.
+ * The backslash has already been read.
+ */
+static char *
+readcstyleesc(char *out)
+{
+       int c, v, i, n;
+
+       c = pgetc();
+       switch (c) {
+       case '\0':
+               synerror("Unterminated quoted string");
+       case '\n':
+               plinno++;
+               if (doprompt)
+                       setprompt(2);
+               else
+                       setprompt(0);
+               return out;
+       case '\\':
+       case '\'':
+       case '"':
+               v = c;
+               break;
+       case 'a': v = '\a'; break;
+       case 'b': v = '\b'; break;
+       case 'e': v = '\033'; break;
+       case 'f': v = '\f'; break;
+       case 'n': v = '\n'; break;
+       case 'r': v = '\r'; break;
+       case 't': v = '\t'; break;
+       case 'v': v = '\v'; break;
+       case 'x':
+                 v = 0;
+                 for (;;) {
+                         c = pgetc();
+                         if (c >= '0' && c <= '9')
+                                 v = (v << 4) + c - '0';
+                         else if (c >= 'A' && c <= 'F')
+                                 v = (v << 4) + c - 'A' + 10;
+                         else if (c >= 'a' && c <= 'f')
+                                 v = (v << 4) + c - 'a' + 10;
+                         else
+                                 break;
+                 }
+                 pungetc();
+                 break;
+       case '0': case '1': case '2': case '3':
+       case '4': case '5': case '6': case '7':
+                 v = c - '0';
+                 c = pgetc();
+                 if (c >= '0' && c <= '7') {
+                         v <<= 3;
+                         v += c - '0';
+                         c = pgetc();
+                         if (c >= '0' && c <= '7') {
+                                 v <<= 3;
+                                 v += c - '0';
+                         } else
+                                 pungetc();
+                 } else
+                         pungetc();
+                 break;
+       case 'c':
+                 c = pgetc();
+                 if (c < 0x3f || c > 0x7a || c == 0x60)
+                         synerror("Bad escape sequence");
+                 if (c == '\\' && pgetc() != '\\')
+                         synerror("Bad escape sequence");
+                 if (c == '?')
+                         v = 127;
+                 else
+                         v = c & 0x1f;
+                 break;
+       case 'u':
+       case 'U':
+                 n = c == 'U' ? 8 : 4;
+                 v = 0;
+                 for (i = 0; i < n; i++) {
+                         c = pgetc();
+                         if (c >= '0' && c <= '9')
+                                 v = (v << 4) + c - '0';
+                         else if (c >= 'A' && c <= 'F')
+                                 v = (v << 4) + c - 'A' + 10;
+                         else if (c >= 'a' && c <= 'f')
+                                 v = (v << 4) + c - 'a' + 10;
+                         else
+                                 synerror("Bad escape sequence");
+                 }
+                 if (v == 0 || (v >= 0xd800 && v <= 0xdfff))
+                         synerror("Bad escape sequence");
+                 /* We really need iconv here. */
+                 if (initial_localeisutf8 && v > 127) {
+                         CHECKSTRSPACE(4, out);
+                         /*
+                          * We cannot use wctomb() as the locale may have
+                          * changed.
+                          */
+                         if (v <= 0x7ff) {
+                                 USTPUTC(0xc0 | v >> 6, out);
+                                 USTPUTC(0x80 | (v & 0x3f), out);
+                                 return out;
+                         } else if (v <= 0xffff) {
+                                 USTPUTC(0xe0 | v >> 12, out);
+                                 USTPUTC(0x80 | ((v >> 6) & 0x3f), out);
+                                 USTPUTC(0x80 | (v & 0x3f), out);
+                                 return out;
+                         } else if (v <= 0x10ffff) {
+                                 USTPUTC(0xf0 | v >> 18, out);
+                                 USTPUTC(0x80 | ((v >> 12) & 0x3f), out);
+                                 USTPUTC(0x80 | ((v >> 6) & 0x3f), out);
+                                 USTPUTC(0x80 | (v & 0x3f), out);
+                                 return out;
+                         }
+                 }
+                 if (v > 127)
+                         v = '?';
+                 break;
+       default:
+                 synerror("Bad escape sequence");
+       }
+       v = (char)v;
+       /*
+        * We can't handle NUL bytes.
+        * POSIX says we should skip till the closing quote.
+        */
+       if (v == '\0') {
+               while ((c = pgetc()) != '\'') {
+                       if (c == '\\')
+                               c = pgetc();
+                       if (c == PEOF)
+                               synerror("Unterminated quoted string");
+                       if (c == '\n') {
+                               plinno++;
+                               if (doprompt)
+                                       setprompt(2);
+                               else
+                                       setprompt(0);
+                       }
+               }
+               pungetc();
+               return out;
+       }
+       if (SQSYNTAX[v] == CCTL)
+               USTPUTC(CTLESC, out);
+       USTPUTC(v, out);
+       return out;
+}
+
+
+/*
+ * If eofmark is NULL, read a word or a redirection symbol.  If eofmark
+ * is not NULL, read a here document.  In the latter case, eofmark is the
+ * word which marks the end of the document and striptabs is true if
+ * leading tabs should be stripped from the document.  The argument firstc
+ * is the first character of the input token or document.
+ *
+ * Because C does not have internal subroutines, I have simulated them
+ * using goto's to implement the subroutine linkage.  The following macros
+ * will run code that appears at the end of readtoken1.
+ */
+
+#define PARSESUB()     {goto parsesub; parsesub_return:;}
+#define        PARSEARITH()    {goto parsearith; parsearith_return:;}
+
+static int
+readtoken1(int firstc, char const *initialsyntax, const char *eofmark,
+    int striptabs)
+{
+       int c = firstc;
+       char *out;
+       int len;
+       struct nodelist *bqlist;
+       int quotef;
+       int newvarnest;
+       int level;
+       int synentry;
+       struct tokenstate state_static[MAXNEST_static];
+       int maxnest = MAXNEST_static;
+       struct tokenstate *state = state_static;
+       int sqiscstyle = 0;
+
+       startlinno = plinno;
+       quotef = 0;
+       bqlist = NULL;
+       newvarnest = 0;
+       level = 0;
+       state[level].syntax = initialsyntax;
+       state[level].parenlevel = 0;
+       state[level].category = TSTATE_TOP;
+
+       STARTSTACKSTR(out);
+       loop: { /* for each line, until end of word */
+               if (eofmark)
+                       /* set c to PEOF if at end of here document */
+                       c = checkend(c, eofmark, striptabs);
+               for (;;) {      /* until end of line or end of word */
+                       CHECKSTRSPACE(4, out);  /* permit 4 calls to USTPUTC */
+
+                       synentry = state[level].syntax[c];
+
+                       switch(synentry) {
+                       case CNL:       /* '\n' */
+                               if (state[level].syntax == BASESYNTAX)
+                                       goto endword;   /* exit outer loop */
+                               USTPUTC(c, out);
+                               plinno++;
+                               if (doprompt)
+                                       setprompt(2);
+                               else
+                                       setprompt(0);
+                               c = pgetc();
+                               goto loop;              /* continue outer loop */
+                       case CSBACK:
+                               if (sqiscstyle) {
+                                       out = readcstyleesc(out);
+                                       break;
+                               }
+                               /* FALLTHROUGH */
+                       case CWORD:
+                               USTPUTC(c, out);
+                               break;
+                       case CCTL:
+                               if (eofmark == NULL || initialsyntax != SQSYNTAX)
+                                       USTPUTC(CTLESC, out);
+                               USTPUTC(c, out);
+                               break;
+                       case CBACK:     /* backslash */
+                               c = pgetc();
+                               if (c == PEOF) {
+                                       USTPUTC('\\', out);
+                                       pungetc();
+                               } else if (c == '\n') {
+                                       plinno++;
+                                       if (doprompt)
+                                               setprompt(2);
+                                       else
+                                               setprompt(0);
+                               } else {
+                                       if (state[level].syntax == DQSYNTAX &&
+                                           c != '\\' && c != '`' && c != '$' &&
+                                           (c != '"' || (eofmark != NULL &&
+                                               newvarnest == 0)) &&
+                                           (c != '}' || state[level].category != TSTATE_VAR_OLD))
+                                               USTPUTC('\\', out);
+                                       if ((eofmark == NULL ||
+                                           newvarnest > 0) &&
+                                           state[level].syntax == BASESYNTAX)
+                                               USTPUTC(CTLQUOTEMARK, out);
+                                       if (SQSYNTAX[c] == CCTL)
+                                               USTPUTC(CTLESC, out);
+                                       USTPUTC(c, out);
+                                       if ((eofmark == NULL ||
+                                           newvarnest > 0) &&
+                                           state[level].syntax == BASESYNTAX &&
+                                           state[level].category == TSTATE_VAR_OLD)
+                                               USTPUTC(CTLQUOTEEND, out);
+                                       quotef++;
+                               }
+                               break;
+                       case CSQUOTE:
+                               USTPUTC(CTLQUOTEMARK, out);
+                               state[level].syntax = SQSYNTAX;
+                               sqiscstyle = 0;
+                               break;
+                       case CDQUOTE:
+                               USTPUTC(CTLQUOTEMARK, out);
+                               state[level].syntax = DQSYNTAX;
+                               break;
+                       case CENDQUOTE:
+                               if (eofmark != NULL && newvarnest == 0)
+                                       USTPUTC(c, out);
+                               else {
+                                       if (state[level].category == TSTATE_VAR_OLD)
+                                               USTPUTC(CTLQUOTEEND, out);
+                                       state[level].syntax = BASESYNTAX;
+                                       quotef++;
+                               }
+                               break;
+                       case CVAR:      /* '$' */
+                               PARSESUB();             /* parse substitution */
+                               break;
+                       case CENDVAR:   /* '}' */
+                               if (level > 0 &&
+                                   ((state[level].category == TSTATE_VAR_OLD &&
+                                     state[level].syntax ==
+                                     state[level - 1].syntax) ||
+                                   (state[level].category == TSTATE_VAR_NEW &&
+                                    state[level].syntax == BASESYNTAX))) {
+                                       if (state[level].category == TSTATE_VAR_NEW)
+                                               newvarnest--;
+                                       level--;
+                                       USTPUTC(CTLENDVAR, out);
+                               } else {
+                                       USTPUTC(c, out);
+                               }
+                               break;
+                       case CLP:       /* '(' in arithmetic */
+                               state[level].parenlevel++;
+                               USTPUTC(c, out);
+                               break;
+                       case CRP:       /* ')' in arithmetic */
+                               if (state[level].parenlevel > 0) {
+                                       USTPUTC(c, out);
+                                       --state[level].parenlevel;
+                               } else {
+                                       if (pgetc_linecont() == ')') {
+                                               if (level > 0 &&
+                                                   state[level].category == TSTATE_ARITH) {
+                                                       level--;
+                                                       USTPUTC(CTLENDARI, out);
+                                               } else
+                                                       USTPUTC(')', out);
+                                       } else {
+                                               /*
+                                                * unbalanced parens
+                                                *  (don't 2nd guess - no error)
+                                                */
+                                               pungetc();
+                                               USTPUTC(')', out);
+                                       }
+                               }
+                               break;
+                       case CBQUOTE:   /* '`' */
+                               out = parsebackq(out, &bqlist, 1,
+                                   state[level].syntax == DQSYNTAX &&
+                                   (eofmark == NULL || newvarnest > 0),
+                                   state[level].syntax == DQSYNTAX || state[level].syntax == ARISYNTAX);
+                               break;
+                       case CEOF:
+                               goto endword;           /* exit outer loop */
+                       case CIGN:
+                               break;
+                       default:
+                               if (level == 0)
+                                       goto endword;   /* exit outer loop */
+                               USTPUTC(c, out);
+                       }
+                       c = pgetc_macro();
+               }
+       }
+endword:
+       if (state[level].syntax == ARISYNTAX)
+               synerror("Missing '))'");
+       if (state[level].syntax != BASESYNTAX && eofmark == NULL)
+               synerror("Unterminated quoted string");
+       if (state[level].category == TSTATE_VAR_OLD ||
+           state[level].category == TSTATE_VAR_NEW) {
+               startlinno = plinno;
+               synerror("Missing '}'");
+       }
+       if (state != state_static)
+               parser_temp_free_upto(state);
+       USTPUTC('\0', out);
+       len = out - stackblock();
+       out = stackblock();
+       if (eofmark == NULL) {
+               if ((c == '>' || c == '<')
+                && quotef == 0
+                && len <= 2
+                && (*out == '\0' || is_digit(*out))) {
+                       parseredir(out, c);
+                       return lasttoken = TREDIR;
+               } else {
+                       pungetc();
+               }
+       }
+       quoteflag = quotef;
+       backquotelist = bqlist;
+       grabstackblock(len);
+       wordtext = out;
+       return lasttoken = TWORD;
+/* end of readtoken routine */
+
+
+/*
+ * Parse a substitution.  At this point, we have read the dollar sign
+ * and nothing else.
+ */
+
+parsesub: {
+       char buf[10];
+       int subtype;
+       int typeloc;
+       int flags;
+       char *p;
+       static const char types[] = "}-+?=";
+       int bracketed_name = 0; /* used to handle ${[0-9]*} variables */
+       int linno;
+       int length;
+       int c1;
+
+       c = pgetc_linecont();
+       if (c == '(') { /* $(command) or $((arith)) */
+               if (pgetc_linecont() == '(') {
+                       PARSEARITH();
+               } else {
+                       pungetc();
+                       out = parsebackq(out, &bqlist, 0,
+                           state[level].syntax == DQSYNTAX &&
+                           (eofmark == NULL || newvarnest > 0),
+                           state[level].syntax == DQSYNTAX ||
+                           state[level].syntax == ARISYNTAX);
+               }
+       } else if (c == '{' || is_name(c) || is_special(c)) {
+               USTPUTC(CTLVAR, out);
+               typeloc = out - stackblock();
+               USTPUTC(VSNORMAL, out);
+               subtype = VSNORMAL;
+               flags = 0;
+               if (c == '{') {
+                       bracketed_name = 1;
+                       c = pgetc_linecont();
+                       subtype = 0;
+               }
+varname:
+               if (!is_eof(c) && is_name(c)) {
+                       length = 0;
+                       do {
+                               STPUTC(c, out);
+                               c = pgetc_linecont();
+                               length++;
+                       } while (!is_eof(c) && is_in_name(c));
+                       if (length == 6 &&
+                           strncmp(out - length, "LINENO", length) == 0) {
+                               /* Replace the variable name with the
+                                * current line number. */
+                               linno = plinno;
+                               if (funclinno != 0)
+                                       linno -= funclinno - 1;
+                               snprintf(buf, sizeof(buf), "%d", linno);
+                               STADJUST(-6, out);
+                               STPUTS(buf, out);
+                               flags |= VSLINENO;
+                       }
+               } else if (is_digit(c)) {
+                       if (bracketed_name) {
+                               do {
+                                       STPUTC(c, out);
+                                       c = pgetc_linecont();
+                               } while (is_digit(c));
+                       } else {
+                               STPUTC(c, out);
+                               c = pgetc_linecont();
+                       }
+               } else if (is_special(c)) {
+                       c1 = c;
+                       c = pgetc_linecont();
+                       if (subtype == 0 && c1 == '#') {
+                               subtype = VSLENGTH;
+                               if (strchr(types, c) == NULL && c != ':' &&
+                                   c != '#' && c != '%')
+                                       goto varname;
+                               c1 = c;
+                               c = pgetc_linecont();
+                               if (c1 != '}' && c == '}') {
+                                       pungetc();
+                                       c = c1;
+                                       goto varname;
+                               }
+                               pungetc();
+                               c = c1;
+                               c1 = '#';
+                               subtype = 0;
+                       }
+                       USTPUTC(c1, out);
+               } else {
+                       subtype = VSERROR;
+                       if (c == '}')
+                               pungetc();
+                       else if (c == '\n' || c == PEOF)
+                               synerror("Unexpected end of line in substitution");
+                       else
+                               USTPUTC(c, out);
+               }
+               if (subtype == 0) {
+                       switch (c) {
+                       case ':':
+                               flags |= VSNUL;
+                               c = pgetc_linecont();
+                               /*FALLTHROUGH*/
+                       default:
+                               p = strchr(types, c);
+                               if (p == NULL) {
+                                       if (c == '\n' || c == PEOF)
+                                               synerror("Unexpected end of line in substitution");
+                                       if (flags == VSNUL)
+                                               STPUTC(':', out);
+                                       STPUTC(c, out);
+                                       subtype = VSERROR;
+                               } else
+                                       subtype = p - types + VSNORMAL;
+                               break;
+                       case '%':
+                       case '#':
+                               {
+                                       int cc = c;
+                                       subtype = c == '#' ? VSTRIMLEFT :
+                                                            VSTRIMRIGHT;
+                                       c = pgetc_linecont();
+                                       if (c == cc)
+                                               subtype++;
+                                       else
+                                               pungetc();
+                                       break;
+                               }
+                       }
+               } else if (subtype != VSERROR) {
+                       if (subtype == VSLENGTH && c != '}')
+                               subtype = VSERROR;
+                       pungetc();
+               }
+               STPUTC('=', out);
+               if (state[level].syntax == DQSYNTAX ||
+                   state[level].syntax == ARISYNTAX)
+                       flags |= VSQUOTE;
+               *(stackblock() + typeloc) = subtype | flags;
+               if (subtype != VSNORMAL) {
+                       if (level + 1 >= maxnest) {
+                               maxnest *= 2;
+                               if (state == state_static) {
+                                       state = parser_temp_alloc(
+                                           maxnest * sizeof(*state));
+                                       memcpy(state, state_static,
+                                           MAXNEST_static * sizeof(*state));
+                               } else
+                                       state = parser_temp_realloc(state,
+                                           maxnest * sizeof(*state));
+                       }
+                       level++;
+                       state[level].parenlevel = 0;
+                       if (subtype == VSMINUS || subtype == VSPLUS ||
+                           subtype == VSQUESTION || subtype == VSASSIGN) {
+                               /*
+                                * For operators that were in the Bourne shell,
+                                * inherit the double-quote state.
+                                */
+                               state[level].syntax = state[level - 1].syntax;
+                               state[level].category = TSTATE_VAR_OLD;
+                       } else {
+                               /*
+                                * The other operators take a pattern,
+                                * so go to BASESYNTAX.
+                                * Also, ' and " are now special, even
+                                * in here documents.
+                                */
+                               state[level].syntax = BASESYNTAX;
+                               state[level].category = TSTATE_VAR_NEW;
+                               newvarnest++;
+                       }
+               }
+       } else if (c == '\'' && state[level].syntax == BASESYNTAX) {
+               /* $'cstylequotes' */
+               USTPUTC(CTLQUOTEMARK, out);
+               state[level].syntax = SQSYNTAX;
+               sqiscstyle = 1;
+       } else {
+               USTPUTC('$', out);
+               pungetc();
+       }
+       goto parsesub_return;
+}
+
+
+/*
+ * Parse an arithmetic expansion (indicate start of one and set state)
+ */
+parsearith: {
+
+       if (level + 1 >= maxnest) {
+               maxnest *= 2;
+               if (state == state_static) {
+                       state = parser_temp_alloc(
+                           maxnest * sizeof(*state));
+                       memcpy(state, state_static,
+                           MAXNEST_static * sizeof(*state));
+               } else
+                       state = parser_temp_realloc(state,
+                           maxnest * sizeof(*state));
+       }
+       level++;
+       state[level].syntax = ARISYNTAX;
+       state[level].parenlevel = 0;
+       state[level].category = TSTATE_ARITH;
+       USTPUTC(CTLARI, out);
+       if (state[level - 1].syntax == DQSYNTAX)
+               USTPUTC('"',out);
+       else
+               USTPUTC(' ',out);
+       goto parsearith_return;
+}
+
+} /* end of readtoken */
+
+
+/*
+ * Returns true if the text contains nothing to expand (no dollar signs
+ * or backquotes).
+ */
+
+static int
+noexpand(char *text)
+{
+       char *p;
+       char c;
+
+       p = text;
+       while ((c = *p++) != '\0') {
+               if ( c == CTLQUOTEMARK)
+                       continue;
+               if (c == CTLESC)
+                       p++;
+               else if (BASESYNTAX[(int)c] == CCTL)
+                       return 0;
+       }
+       return 1;
+}
+
+
+/*
+ * Return true if the argument is a legal variable name (a letter or
+ * underscore followed by zero or more letters, underscores, and digits).
+ */
+
+int
+goodname(const char *name)
+{
+       const char *p;
+
+       p = name;
+       if (! is_name(*p))
+               return 0;
+       while (*++p) {
+               if (! is_in_name(*p))
+                       return 0;
+       }
+       return 1;
+}
+
+
+int
+isassignment(const char *p)
+{
+       if (!is_name(*p))
+               return 0;
+       p++;
+       for (;;) {
+               if (*p == '=')
+                       return 1;
+               else if (!is_in_name(*p))
+                       return 0;
+               p++;
+       }
+}
+
+
+static void
+consumetoken(int token)
+{
+       if (readtoken() != token)
+               synexpect(token);
+}
+
+
+/*
+ * Called when an unexpected token is read during the parse.  The argument
+ * is the token that is expected, or -1 if more than one type of token can
+ * occur at this point.
+ */
+
+static void
+synexpect(int token)
+{
+       char msg[64];
+
+       if (token >= 0) {
+               fmtstr(msg, 64, "%s unexpected (expecting %s)",
+                       tokname[lasttoken], tokname[token]);
+       } else {
+               fmtstr(msg, 64, "%s unexpected", tokname[lasttoken]);
+       }
+       synerror(msg);
+}
+
+
+static void
+synerror(const char *msg)
+{
+       if (commandname)
+               outfmt(out2, "%s: %d: ", commandname, startlinno);
+       else if (arg0)
+               outfmt(out2, "%s: ", arg0);
+       outfmt(out2, "Syntax error: %s\n", msg);
+       error((char *)NULL);
+}
+
+static void
+setprompt(int which)
+{
+       whichprompt = which;
+
+#ifndef NO_HISTORY
+       if (!el)
+#endif
+       {
+               out2str(getprompt(NULL));
+               flushout(out2);
+       }
+}
+
+static int
+pgetc_linecont(void)
+{
+       int c;
+
+       while ((c = pgetc_macro()) == '\\') {
+               c = pgetc();
+               if (c == '\n') {
+                       plinno++;
+                       if (doprompt)
+                               setprompt(2);
+                       else
+                               setprompt(0);
+               } else {
+                       pungetc();
+                       /* Allow the backslash to be pushed back. */
+                       pushstring("\\", 1, NULL);
+                       return (pgetc());
+               }
+       }
+       return (c);
+}
+
+/*
+ * called by editline -- any expansions to the prompt
+ *    should be added here.
+ */
+char *
+getprompt(void *unused __unused)
+{
+       static char ps[PROMPTLEN];
+       const char *fmt;
+       const char *pwd;
+       int i, trim;
+       static char internal_error[] = "??";
+
+       /*
+        * Select prompt format.
+        */
+       switch (whichprompt) {
+       case 0:
+               fmt = "";
+               break;
+       case 1:
+               fmt = ps1val();
+               break;
+       case 2:
+               fmt = ps2val();
+               break;
+       default:
+               return internal_error;
+       }
+
+       /*
+        * Format prompt string.
+        */
+       for (i = 0; (i < 127) && (*fmt != '\0'); i++, fmt++)
+               if (*fmt == '\\')
+                       switch (*++fmt) {
+
+                               /*
+                                * Hostname.
+                                *
+                                * \h specifies just the local hostname,
+                                * \H specifies fully-qualified hostname.
+                                */
+                       case 'h':
+                       case 'H':
+                               ps[i] = '\0';
+                               gethostname(&ps[i], PROMPTLEN - i);
+                               /* Skip to end of hostname. */
+                               trim = (*fmt == 'h') ? '.' : '\0';
+                               while ((ps[i+1] != '\0') && (ps[i+1] != trim))
+                                       i++;
+                               break;
+
+                               /*
+                                * Working directory.
+                                *
+                                * \W specifies just the final component,
+                                * \w specifies the entire path.
+                                */
+                       case 'W':
+                       case 'w':
+                               pwd = lookupvar("PWD");
+                               if (pwd == NULL)
+                                       pwd = "?";
+                               if (*fmt == 'W' &&
+                                   *pwd == '/' && pwd[1] != '\0')
+                                       strlcpy(&ps[i], strrchr(pwd, '/') + 1,
+                                           PROMPTLEN - i);
+                               else
+                                       strlcpy(&ps[i], pwd, PROMPTLEN - i);
+                               /* Skip to end of path. */
+                               while (ps[i + 1] != '\0')
+                                       i++;
+                               break;
+
+                               /*
+                                * Superuser status.
+                                *
+                                * '$' for normal users, '#' for root.
+                                */
+                       case '$':
+                               ps[i] = (geteuid() != 0) ? '$' : '#';
+                               break;
+
+                               /*
+                                * A literal \.
+                                */
+                       case '\\':
+                               ps[i] = '\\';
+                               break;
+
+                               /*
+                                * Emit unrecognized formats verbatim.
+                                */
+                       default:
+                               ps[i++] = '\\';
+                               ps[i] = *fmt;
+                               break;
+                       }
+               else
+                       ps[i] = *fmt;
+       ps[i] = '\0';
+       return (ps);
+}
+
+
+const char *
+expandstr(const char *ps)
+{
+       union node n;
+       struct jmploc jmploc;
+       struct jmploc *const savehandler = handler;
+       const int saveprompt = doprompt;
+       struct parsefile *const savetopfile = getcurrentfile();
+       struct parser_temp *const saveparser_temp = parser_temp;
+       const char *result = NULL;
+
+       if (!setjmp(jmploc.loc)) {
+               handler = &jmploc;
+               parser_temp = NULL;
+               setinputstring(ps, 1);
+               doprompt = 0;
+               readtoken1(pgetc(), DQSYNTAX, "", 0);
+               if (backquotelist != NULL)
+                       error("Command substitution not allowed here");
+
+               n.narg.type = NARG;
+               n.narg.next = NULL;
+               n.narg.text = wordtext;
+               n.narg.backquote = backquotelist;
+
+               expandarg(&n, NULL, 0);
+               result = stackblock();
+               INTOFF;
+       }
+       handler = savehandler;
+       doprompt = saveprompt;
+       popfilesupto(savetopfile);
+       if (parser_temp != saveparser_temp) {
+               parser_temp_free_all();
+               parser_temp = saveparser_temp;
+       }
+       if (result != NULL) {
+               INTON;
+       } else if (exception == EXINT)
+               raise(SIGINT);
+       return result;
+}
diff --git a/sh/parser.h b/sh/parser.h
new file mode 100644 (file)
index 0000000..5982594
--- /dev/null
@@ -0,0 +1,84 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)parser.h    8.3 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+/* control characters in argument strings */
+#define CTLESC '\300'
+#define CTLVAR '\301'
+#define CTLENDVAR '\371'
+#define CTLBACKQ '\372'
+#define CTLQUOTE 01            /* ored with CTLBACKQ code if in quotes */
+/*     CTLBACKQ | CTLQUOTE == '\373' */
+#define        CTLARI  '\374'
+#define        CTLENDARI '\375'
+#define        CTLQUOTEMARK '\376'
+#define        CTLQUOTEEND '\377' /* only for ${v+-...} */
+
+/* variable substitution byte (follows CTLVAR) */
+#define VSTYPE         0x0f    /* type of variable substitution */
+#define VSNUL          0x10    /* colon--treat the empty string as unset */
+#define VSLINENO       0x20    /* expansion of $LINENO, the line number \
+                                  follows immediately */
+#define VSQUOTE                0x80    /* inside double quotes--suppress splitting */
+
+/* values of VSTYPE field */
+#define VSNORMAL       0x1             /* normal variable:  $var or ${var} */
+#define VSMINUS                0x2             /* ${var-text} */
+#define VSPLUS         0x3             /* ${var+text} */
+#define VSQUESTION     0x4             /* ${var?message} */
+#define VSASSIGN       0x5             /* ${var=text} */
+#define VSTRIMLEFT     0x6             /* ${var#pattern} */
+#define VSTRIMLEFTMAX  0x7             /* ${var##pattern} */
+#define VSTRIMRIGHT    0x8             /* ${var%pattern} */
+#define VSTRIMRIGHTMAX         0x9             /* ${var%%pattern} */
+#define VSLENGTH       0xa             /* ${#var} */
+#define VSERROR                0xb             /* Syntax error, issue when expanded */
+
+
+/*
+ * NEOF is returned by parsecmd when it encounters an end of file.  It
+ * must be distinct from NULL.
+ */
+#define NEOF ((union node *)-1)
+extern int whichprompt;                /* 1 == PS1, 2 == PS2 */
+extern const char *const parsekwd[];
+
+
+union node *parsecmd(int);
+void forcealias(void);
+void fixredir(union node *, const char *, int);
+int goodname(const char *);
+int isassignment(const char *);
+char *getprompt(void *);
+const char *expandstr(const char *);
diff --git a/sh/redir.c b/sh/redir.c
new file mode 100644 (file)
index 0000000..95d3238
--- /dev/null
@@ -0,0 +1,348 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)redir.c    8.2 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+/*
+ * Code for dealing with input/output redirection.
+ */
+
+#include "shell.h"
+#include "nodes.h"
+#include "jobs.h"
+#include "expand.h"
+#include "redir.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "options.h"
+
+
+#define EMPTY -2               /* marks an unused slot in redirtab */
+#define CLOSED -1              /* fd was not open before redir */
+
+
+struct redirtab {
+       struct redirtab *next;
+       int renamed[10];
+       int fd0_redirected;
+};
+
+
+static struct redirtab *redirlist;
+
+/*
+ * We keep track of whether or not fd0 has been redirected.  This is for
+ * background commands, where we want to redirect fd0 to /dev/null only
+ * if it hasn't already been redirected.
+*/
+static int fd0_redirected = 0;
+
+static void openredirect(union node *, char[10 ]);
+static int openhere(union node *);
+
+
+/*
+ * Process a list of redirection commands.  If the REDIR_PUSH flag is set,
+ * old file descriptors are stashed away so that the redirection can be
+ * undone by calling popredir.  If the REDIR_BACKQ flag is set, then the
+ * standard output, and the standard error if it becomes a duplicate of
+ * stdout, is saved in memory.
+*
+ * We suppress interrupts so that we won't leave open file
+ * descriptors around.  Because the signal handler remains
+ * installed and we do not use system call restart, interrupts
+ * will still abort blocking opens such as fifos (they will fail
+ * with EINTR). There is, however, a race condition if an interrupt
+ * arrives after INTOFF and before open blocks.
+ */
+
+void
+redirect(union node *redir, int flags)
+{
+       union node *n;
+       struct redirtab *sv = NULL;
+       int i;
+       int fd;
+       char memory[10];        /* file descriptors to write to memory */
+
+       INTOFF;
+       for (i = 10 ; --i >= 0 ; )
+               memory[i] = 0;
+       memory[1] = flags & REDIR_BACKQ;
+       if (flags & REDIR_PUSH) {
+               sv = ckmalloc(sizeof (struct redirtab));
+               for (i = 0 ; i < 10 ; i++)
+                       sv->renamed[i] = EMPTY;
+               sv->fd0_redirected = fd0_redirected;
+               sv->next = redirlist;
+               redirlist = sv;
+       }
+       for (n = redir ; n ; n = n->nfile.next) {
+               fd = n->nfile.fd;
+               if (fd == 0)
+                       fd0_redirected = 1;
+               if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) &&
+                   n->ndup.dupfd == fd)
+                       continue; /* redirect from/to same file descriptor */
+
+               if ((flags & REDIR_PUSH) && sv->renamed[fd] == EMPTY) {
+                       INTOFF;
+                       if ((i = fcntl(fd, F_DUPFD_CLOEXEC, 10)) == -1) {
+                               switch (errno) {
+                               case EBADF:
+                                       i = CLOSED;
+                                       break;
+                               default:
+                                       INTON;
+                                       error("%d: %s", fd, strerror(errno));
+                                       break;
+                               }
+                       }
+                       sv->renamed[fd] = i;
+                       INTON;
+               }
+               openredirect(n, memory);
+               INTON;
+               INTOFF;
+       }
+       if (memory[1])
+               out1 = &memout;
+       if (memory[2])
+               out2 = &memout;
+       INTON;
+}
+
+
+static void
+openredirect(union node *redir, char memory[10])
+{
+       struct stat sb;
+       int fd = redir->nfile.fd;
+       const char *fname;
+       int f;
+       int e;
+
+       memory[fd] = 0;
+       switch (redir->nfile.type) {
+       case NFROM:
+               fname = redir->nfile.expfname;
+               if ((f = open(fname, O_RDONLY)) < 0)
+                       error("cannot open %s: %s", fname, strerror(errno));
+               break;
+       case NFROMTO:
+               fname = redir->nfile.expfname;
+               if ((f = open(fname, O_RDWR|O_CREAT, 0666)) < 0)
+                       error("cannot create %s: %s", fname, strerror(errno));
+               break;
+       case NTO:
+               if (Cflag) {
+                       fname = redir->nfile.expfname;
+                       if (stat(fname, &sb) == -1) {
+                               if ((f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0)
+                                       error("cannot create %s: %s", fname, strerror(errno));
+                       } else if (!S_ISREG(sb.st_mode)) {
+                               if ((f = open(fname, O_WRONLY, 0666)) < 0)
+                                       error("cannot create %s: %s", fname, strerror(errno));
+                               if (fstat(f, &sb) != -1 && S_ISREG(sb.st_mode)) {
+                                       close(f);
+                                       error("cannot create %s: %s", fname,
+                                           strerror(EEXIST));
+                               }
+                       } else
+                               error("cannot create %s: %s", fname,
+                                   strerror(EEXIST));
+                       break;
+               }
+               /* FALLTHROUGH */
+       case NCLOBBER:
+               fname = redir->nfile.expfname;
+               if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0)
+                       error("cannot create %s: %s", fname, strerror(errno));
+               break;
+       case NAPPEND:
+               fname = redir->nfile.expfname;
+               if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0)
+                       error("cannot create %s: %s", fname, strerror(errno));
+               break;
+       case NTOFD:
+       case NFROMFD:
+               if (redir->ndup.dupfd >= 0) {   /* if not ">&-" */
+                       if (memory[redir->ndup.dupfd])
+                               memory[fd] = 1;
+                       else {
+                               if (dup2(redir->ndup.dupfd, fd) < 0)
+                                       error("%d: %s", redir->ndup.dupfd,
+                                                       strerror(errno));
+                       }
+               } else {
+                       close(fd);
+               }
+               return;
+       case NHERE:
+       case NXHERE:
+               f = openhere(redir);
+               break;
+       default:
+               abort();
+       }
+       if (f != fd) {
+               if (dup2(f, fd) == -1) {
+                       e = errno;
+                       close(f);
+                       error("%d: %s", fd, strerror(e));
+               }
+               close(f);
+       }
+}
+
+
+/*
+ * Handle here documents.  Normally we fork off a process to write the
+ * data to a pipe.  If the document is short, we can stuff the data in
+ * the pipe without forking.
+ */
+
+static int
+openhere(union node *redir)
+{
+       const char *p;
+       int pip[2];
+       size_t len = 0;
+       int flags;
+       ssize_t written = 0;
+
+       if (pipe(pip) < 0)
+               error("Pipe call failed: %s", strerror(errno));
+
+       if (redir->type == NXHERE)
+               p = redir->nhere.expdoc;
+       else
+               p = redir->nhere.doc->narg.text;
+       len = strlen(p);
+       if (len == 0)
+               goto out;
+       flags = fcntl(pip[1], F_GETFL, 0);
+       if (flags != -1 && fcntl(pip[1], F_SETFL, flags | O_NONBLOCK) != -1) {
+               written = write(pip[1], p, len);
+               if (written < 0)
+                       written = 0;
+               if ((size_t)written == len)
+                       goto out;
+               fcntl(pip[1], F_SETFL, flags);
+       }
+
+       if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) {
+               close(pip[0]);
+               signal(SIGINT, SIG_IGN);
+               signal(SIGQUIT, SIG_IGN);
+               signal(SIGHUP, SIG_IGN);
+               signal(SIGTSTP, SIG_IGN);
+               signal(SIGPIPE, SIG_DFL);
+               xwrite(pip[1], p + written, len - written);
+               _exit(0);
+       }
+out:
+       close(pip[1]);
+       return pip[0];
+}
+
+
+
+/*
+ * Undo the effects of the last redirection.
+ */
+
+void
+popredir(void)
+{
+       struct redirtab *rp = redirlist;
+       int i;
+
+       for (i = 0 ; i < 10 ; i++) {
+               if (rp->renamed[i] != EMPTY) {
+                       if (rp->renamed[i] >= 0) {
+                               dup2(rp->renamed[i], i);
+                               close(rp->renamed[i]);
+                       } else {
+                               close(i);
+                       }
+               }
+       }
+       INTOFF;
+       fd0_redirected = rp->fd0_redirected;
+       redirlist = rp->next;
+       ckfree(rp);
+       INTON;
+}
+
+/* Return true if fd 0 has already been redirected at least once.  */
+int
+fd0_redirected_p(void)
+{
+        return fd0_redirected != 0;
+}
+
+/*
+ * Discard all saved file descriptors.
+ */
+
+void
+clearredir(void)
+{
+       struct redirtab *rp;
+       int i;
+
+       for (rp = redirlist ; rp ; rp = rp->next) {
+               for (i = 0 ; i < 10 ; i++) {
+                       if (rp->renamed[i] >= 0) {
+                               close(rp->renamed[i]);
+                       }
+                       rp->renamed[i] = EMPTY;
+               }
+       }
+}
diff --git a/sh/redir.h b/sh/redir.h
new file mode 100644 (file)
index 0000000..ad44c4e
--- /dev/null
@@ -0,0 +1,45 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)redir.h     8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+/* flags passed to redirect */
+#define REDIR_PUSH 01          /* save previous values of file descriptors */
+#define REDIR_BACKQ 02         /* save the command output in memory */
+
+union node;
+void redirect(union node *, int);
+void popredir(void);
+int fd0_redirected_p(void);
+void clearredir(void);
+
diff --git a/sh/sh.1 b/sh/sh.1
new file mode 100644 (file)
index 0000000..e193d5d
--- /dev/null
+++ b/sh/sh.1
@@ -0,0 +1,2849 @@
+.\"-
+.\" Copyright (c) 1991, 1993
+.\"    The Regents of the University of California.  All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Kenneth Almquist.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\"    from: @(#)sh.1  8.6 (Berkeley) 5/4/95
+.\" $FreeBSD$
+.\"
+.Dd July 11, 2015
+.Dt SH 1
+.Os
+.Sh NAME
+.Nm sh
+.Nd command interpreter (shell)
+.Sh SYNOPSIS
+.Nm
+.Op Fl /+abCEefhIimnPpTuVvx
+.Op Fl /+o Ar longname
+.Oo
+.Ar script
+.Op Ar arg ...
+.Oc
+.Nm
+.Op Fl /+abCEefhIimnPpTuVvx
+.Op Fl /+o Ar longname
+.Fl c Ar string
+.Oo
+.Ar name
+.Op Ar arg ...
+.Oc
+.Nm
+.Op Fl /+abCEefhIimnPpTuVvx
+.Op Fl /+o Ar longname
+.Fl s
+.Op Ar arg ...
+.Sh DESCRIPTION
+The
+.Nm
+utility is the standard command interpreter for the system.
+The current version of
+.Nm
+is close to the
+.St -p1003.1
+specification for the shell.
+It only supports features
+designated by
+.Tn POSIX ,
+plus a few Berkeley extensions.
+This man page is not intended to be a tutorial nor a complete
+specification of the shell.
+.Ss Overview
+The shell is a command that reads lines from
+either a file or the terminal, interprets them, and
+generally executes other commands.
+It is the program that is started when a user logs into the system,
+although a user can select a different shell with the
+.Xr chsh 1
+command.
+The shell
+implements a language that has flow control constructs,
+a macro facility that provides a variety of features in
+addition to data storage, along with built-in history and line
+editing capabilities.
+It incorporates many features to
+aid interactive use and has the advantage that the interpretative
+language is common to both interactive and non-interactive
+use (shell scripts).
+That is, commands can be typed directly
+to the running shell or can be put into a file,
+which can be executed directly by the shell.
+.Ss Invocation
+.\"
+.\" XXX This next sentence is incredibly confusing.
+.\"
+If no arguments are present and if the standard input of the shell
+is connected to a terminal
+(or if the
+.Fl i
+option is set),
+the shell is considered an interactive shell.
+An interactive shell
+generally prompts before each command and handles programming
+and command errors differently (as described below).
+When first starting, the shell inspects argument 0, and
+if it begins with a dash
+.Pq Ql - ,
+the shell is also considered a login shell.
+This is normally done automatically by the system
+when the user first logs in.
+A login shell first reads commands
+from the files
+.Pa /etc/profile
+and then
+.Pa .profile
+in a user's home directory,
+if they exist.
+If the environment variable
+.Ev ENV
+is set on entry to a shell, or is set in the
+.Pa .profile
+of a login shell, the shell then subjects its value to parameter expansion
+and arithmetic expansion and reads commands from the named file.
+Therefore, a user should place commands that are to be executed only
+at login time in the
+.Pa .profile
+file, and commands that are executed for every shell inside the
+.Ev ENV
+file.
+The user can set the
+.Ev ENV
+variable to some file by placing the following line in the file
+.Pa .profile
+in the home directory,
+substituting for
+.Pa .shrc
+the filename desired:
+.Pp
+.Dl "ENV=$HOME/.shrc; export ENV"
+.Pp
+The first non-option argument specified on the command line
+will be treated as the
+name of a file from which to read commands (a shell script), and
+the remaining arguments are set as the positional parameters
+of the shell
+.Li ( $1 , $2 ,
+etc.).
+Otherwise, the shell reads commands
+from its standard input.
+.Pp
+Unlike older versions of
+.Nm
+the
+.Ev ENV
+script is only sourced on invocation of interactive shells.
+This
+closes a well-known, and sometimes easily exploitable security
+hole related to poorly thought out
+.Ev ENV
+scripts.
+.Ss Argument List Processing
+All of the single letter options to
+.Nm
+have a corresponding long name,
+with the exception of
+.Fl c
+and
+.Fl /+o .
+These long names are provided next to the single letter options
+in the descriptions below.
+The long name for an option may be specified as an argument to the
+.Fl /+o
+option of
+.Nm .
+Once the shell is running,
+the long name for an option may be specified as an argument to the
+.Fl /+o
+option of the
+.Ic set
+built-in command
+(described later in the section called
+.Sx Built-in Commands ) .
+Introducing an option with a dash
+.Pq Ql -
+enables the option,
+while using a plus
+.Pq Ql +
+disables the option.
+A
+.Dq Li --
+or plain
+.Ql -
+will stop option processing and will force the remaining
+words on the command line to be treated as arguments.
+The
+.Fl /+o
+and
+.Fl c
+options do not have long names.
+They take arguments and are described after the single letter options.
+.Bl -tag -width indent
+.It Fl a Li allexport
+Flag variables for export when assignments are made to them.
+.It Fl b Li notify
+Enable asynchronous notification of background job
+completion.
+(UNIMPLEMENTED)
+.It Fl C Li noclobber
+Do not overwrite existing files with
+.Ql > .
+.It Fl E Li emacs
+Enable the built-in
+.Xr emacs 1
+command line editor (disables the
+.Fl V
+option if it has been set;
+set automatically when interactive on terminals).
+.It Fl e Li errexit
+Exit immediately if any untested command fails in non-interactive mode.
+The exit status of a command is considered to be
+explicitly tested if the command is part of the list used to control
+an
+.Ic if , elif , while ,
+or
+.Ic until ;
+if the command is the left
+hand operand of an
+.Dq Li &&
+or
+.Dq Li ||
+operator; or if the command is a pipeline preceded by the
+.Ic !\&
+keyword.
+If a shell function is executed and its exit status is explicitly
+tested, all commands of the function are considered to be tested as
+well.
+.Pp
+It is recommended to check for failures explicitly
+instead of relying on
+.Fl e
+because it tends to behave in unexpected ways,
+particularly in larger scripts.
+.It Fl f Li noglob
+Disable pathname expansion.
+.It Fl h Li trackall
+A do-nothing option for
+.Tn POSIX
+compliance.
+.It Fl I Li ignoreeof
+Ignore
+.Dv EOF Ap s
+from input when in interactive mode.
+.It Fl i Li interactive
+Force the shell to behave interactively.
+.It Fl m Li monitor
+Turn on job control (set automatically when interactive).
+A new process group is created for each pipeline (called a job).
+It is possible to suspend jobs or to have them run in the foreground or
+in the background.
+In a non-interactive shell,
+this option can be set even if no terminal is available
+and is useful to place processes in separate process groups.
+.It Fl n Li noexec
+If not interactive, read commands but do not
+execute them.
+This is useful for checking the
+syntax of shell scripts.
+.It Fl P Li physical
+Change the default for the
+.Ic cd
+and
+.Ic pwd
+commands from
+.Fl L
+(logical directory layout)
+to
+.Fl P
+(physical directory layout).
+.It Fl p Li privileged
+Turn on privileged mode.
+This mode is enabled on startup
+if either the effective user or group ID is not equal to the
+real user or group ID.
+Turning this mode off sets the
+effective user and group IDs to the real user and group IDs.
+When this mode is enabled for interactive shells, the file
+.Pa /etc/suid_profile
+is sourced instead of
+.Pa ~/.profile
+after
+.Pa /etc/profile
+is sourced, and the contents of the
+.Ev ENV
+variable are ignored.
+.It Fl s Li stdin
+Read commands from standard input (set automatically
+if no file arguments are present).
+This option has
+no effect when set after the shell has already started
+running (i.e., when set with the
+.Ic set
+command).
+.It Fl T Li trapsasync
+When waiting for a child, execute traps immediately.
+If this option is not set,
+traps are executed after the child exits,
+as specified in
+.St -p1003.2 .
+This nonstandard option is useful for putting guarding shells around
+children that block signals.
+The surrounding shell may kill the child
+or it may just return control to the tty and leave the child alone,
+like this:
+.Bd -literal -offset indent
+sh -T -c "trap 'exit 1' 2 ; some-blocking-program"
+.Ed
+.It Fl u Li nounset
+Write a message to standard error when attempting
+to expand a variable, a positional parameter or
+the special parameter
+.Va \&!
+that is not set, and if the
+shell is not interactive, exit immediately.
+.It Fl V Li vi
+Enable the built-in
+.Xr vi 1
+command line editor (disables
+.Fl E
+if it has been set).
+.It Fl v Li verbose
+The shell writes its input to standard error
+as it is read.
+Useful for debugging.
+.It Fl x Li xtrace
+Write each command
+(preceded by the value of the
+.Va PS4
+variable subjected to parameter expansion and arithmetic expansion)
+to standard error before it is executed.
+Useful for debugging.
+.El
+.Pp
+The
+.Fl c
+option causes the commands to be read from the
+.Ar string
+operand instead of from the standard input.
+Keep in mind that this option only accepts a single string as its
+argument, hence multi-word strings must be quoted.
+.Pp
+The
+.Fl /+o
+option takes as its only argument the long name of an option
+to be enabled or disabled.
+For example, the following two invocations of
+.Nm
+both enable the built-in
+.Xr emacs 1
+command line editor:
+.Bd -literal -offset indent
+set -E
+set -o emacs
+.Ed
+.Pp
+If used without an argument, the
+.Fl o
+option displays the current option settings in a human-readable format.
+If
+.Cm +o
+is used without an argument, the current option settings are output
+in a format suitable for re-input into the shell.
+.Ss Lexical Structure
+The shell reads input in terms of lines from a file and breaks
+it up into words at whitespace (blanks and tabs), and at
+certain sequences of
+characters called
+.Dq operators ,
+which are special to the shell.
+There are two types of operators: control operators and
+redirection operators (their meaning is discussed later).
+The following is a list of valid operators:
+.Bl -tag -width indent
+.It Control operators:
+.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact
+.It Li & Ta Li && Ta Li \&( Ta Li \&) Ta Li \en
+.It Li ;; Ta Li ;& Ta Li \&; Ta Li \&| Ta Li ||
+.El
+.It Redirection operators:
+.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact
+.It Li < Ta Li > Ta Li << Ta Li >> Ta Li <>
+.It Li <& Ta Li >& Ta Li <<- Ta Li >| Ta \&
+.El
+.El
+.Pp
+The character
+.Ql #
+introduces a comment if used at the beginning of a word.
+The word starting with
+.Ql #
+and the rest of the line are ignored.
+.Pp
+.Tn ASCII
+.Dv NUL
+characters (character code 0) are not allowed in shell input.
+.Ss Quoting
+Quoting is used to remove the special meaning of certain characters
+or words to the shell, such as operators, whitespace, keywords,
+or alias names.
+.Pp
+There are four types of quoting: matched single quotes,
+dollar-single quotes,
+matched double quotes, and backslash.
+.Bl -tag -width indent
+.It Single Quotes
+Enclosing characters in single quotes preserves the literal
+meaning of all the characters (except single quotes, making
+it impossible to put single-quotes in a single-quoted string).
+.It Dollar-Single Quotes
+Enclosing characters between
+.Li $'
+and
+.Li '
+preserves the literal meaning of all characters
+except backslashes and single quotes.
+A backslash introduces a C-style escape sequence:
+.Bl -tag -width xUnnnnnnnn
+.It \ea
+Alert (ring the terminal bell)
+.It \eb
+Backspace
+.It \ec Ns Ar c
+The control character denoted by
+.Li ^ Ns Ar c
+in
+.Xr stty 1 .
+If
+.Ar c
+is a backslash, it must be doubled.
+.It \ee
+The ESC character
+.Tn ( ASCII
+0x1b)
+.It \ef
+Formfeed
+.It \en
+Newline
+.It \er
+Carriage return
+.It \et
+Horizontal tab
+.It \ev
+Vertical tab
+.It \e\e
+Literal backslash
+.It \e\&'
+Literal single-quote
+.It \e\&"
+Literal double-quote
+.It \e Ns Ar nnn
+The byte whose octal value is
+.Ar nnn
+(one to three digits)
+.It \ex Ns Ar nn
+The byte whose hexadecimal value is
+.Ar nn
+(one or more digits only the last two of which are used)
+.It \eu Ns Ar nnnn
+The Unicode code point
+.Ar nnnn
+(four hexadecimal digits)
+.It \eU Ns Ar nnnnnnnn
+The Unicode code point
+.Ar nnnnnnnn
+(eight hexadecimal digits)
+.El
+.Pp
+The sequences for Unicode code points are currently only useful with
+UTF-8 locales.
+They reject code point 0 and UTF-16 surrogates.
+.Pp
+If an escape sequence would produce a byte with value 0,
+that byte and the rest of the string until the matching single-quote
+are ignored.
+.Pp
+Any other string starting with a backslash is an error.
+.It Double Quotes
+Enclosing characters within double quotes preserves the literal
+meaning of all characters except dollar sign
+.Pq Ql $ ,
+backquote
+.Pq Ql ` ,
+and backslash
+.Pq Ql \e .
+The backslash inside double quotes is historically weird.
+It remains literal unless it precedes the following characters,
+which it serves to quote:
+.Pp
+.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact
+.It Li $ Ta Li ` Ta Li \&" Ta Li \e Ta Li \en
+.El
+.It Backslash
+A backslash preserves the literal meaning of the following
+character, with the exception of the newline character
+.Pq Ql \en .
+A backslash preceding a newline is treated as a line continuation.
+.El
+.Ss Keywords
+Keywords or reserved words are words that have special meaning to the
+shell and are recognized at the beginning of a line and
+after a control operator.
+The following are keywords:
+.Bl -column "doneXX" "elifXX" "elseXX" "untilXX" "whileX" -offset center
+.It Li \&! Ta { Ta } Ta Ic case Ta Ic do
+.It Ic done Ta Ic elif Ta Ic else Ta Ic esac Ta Ic fi
+.It Ic for Ta Ic if Ta Ic then Ta Ic until Ta Ic while
+.El
+.Ss Aliases
+An alias is a name and corresponding value set using the
+.Ic alias
+built-in command.
+Wherever the command word of a simple command may occur,
+and after checking for keywords if a keyword may occur, the shell
+checks the word to see if it matches an alias.
+If it does, it replaces it in the input stream with its value.
+For example, if there is an alias called
+.Dq Li lf
+with the value
+.Dq Li "ls -F" ,
+then the input
+.Pp
+.Dl "lf foobar"
+.Pp
+would become
+.Pp
+.Dl "ls -F foobar"
+.Pp
+Aliases are also recognized after an alias
+whose value ends with a space or tab.
+For example, if there is also an alias called
+.Dq Li nohup
+with the value
+.Dq Li "nohup " ,
+then the input
+.Pp
+.Dl "nohup lf foobar"
+.Pp
+would become
+.Pp
+.Dl "nohup ls -F foobar"
+.Pp
+Aliases provide a convenient way for naive users to
+create shorthands for commands without having to learn how
+to create functions with arguments.
+Using aliases in scripts is discouraged
+because the command that defines them must be executed
+before the code that uses them is parsed.
+This is fragile and not portable.
+.Pp
+An alias name may be escaped in a command line, so that it is not
+replaced by its alias value, by using quoting characters within or
+adjacent to the alias name.
+This is most often done by prefixing
+an alias name with a backslash to execute a function, built-in, or
+normal program with the same name.
+See the
+.Sx Quoting
+subsection.
+.Ss Commands
+The shell interprets the words it reads according to a
+language, the specification of which is outside the scope
+of this man page (refer to the BNF in the
+.St -p1003.2
+document).
+Essentially though, a line is read and if
+the first word of the line (or after a control operator)
+is not a keyword, then the shell has recognized a
+simple command.
+Otherwise, a complex command or some
+other special construct may have been recognized.
+.Ss Simple Commands
+If a simple command has been recognized, the shell performs
+the following actions:
+.Bl -enum
+.It
+Leading words of the form
+.Dq Li name=value
+are stripped off and assigned to the environment of
+the simple command
+(they do not affect expansions).
+Redirection operators and
+their arguments (as described below) are stripped
+off and saved for processing.
+.It
+The remaining words are expanded as described in
+the section called
+.Sx Word Expansions ,
+and the first remaining word is considered the command
+name and the command is located.
+The remaining
+words are considered the arguments of the command.
+If no command name resulted, then the
+.Dq Li name=value
+variable assignments recognized in 1) affect the
+current shell.
+.It
+Redirections are performed as described in
+the next section.
+.El
+.Ss Redirections
+Redirections are used to change where a command reads its input
+or sends its output.
+In general, redirections open, close, or
+duplicate an existing reference to a file.
+The overall format
+used for redirection is:
+.Pp
+.D1 Oo Ar n Oc Ar redir-op file
+.Pp
+The
+.Ar redir-op
+is one of the redirection operators mentioned
+previously.
+The following gives some examples of how these
+operators can be used.
+Note that stdin and stdout are commonly used abbreviations
+for standard input and standard output respectively.
+.Bl -tag -width "1234567890XX" -offset indent
+.It Oo Ar n Oc Ns Li > Ar file
+redirect stdout (or file descriptor
+.Ar n )
+to
+.Ar file
+.It Oo Ar n Oc Ns Li >| Ar file
+same as above, but override the
+.Fl C
+option
+.It Oo Ar n Oc Ns Li >> Ar file
+append stdout (or file descriptor
+.Ar n )
+to
+.Ar file
+.It Oo Ar n Oc Ns Li < Ar file
+redirect stdin (or file descriptor
+.Ar n )
+from
+.Ar file
+.It Oo Ar n Oc Ns Li <> Ar file
+redirect stdin (or file descriptor
+.Ar n )
+to and from
+.Ar file
+.It Oo Ar n1 Oc Ns Li <& Ns Ar n2
+duplicate stdin (or file descriptor
+.Ar n1 )
+from file descriptor
+.Ar n2
+.It Oo Ar n Oc Ns Li <&-
+close stdin (or file descriptor
+.Ar n )
+.It Oo Ar n1 Oc Ns Li >& Ns Ar n2
+duplicate stdout (or file descriptor
+.Ar n1 )
+to file descriptor
+.Ar n2
+.It Oo Ar n Oc Ns Li >&-
+close stdout (or file descriptor
+.Ar n )
+.El
+.Pp
+The following redirection is often called a
+.Dq here-document .
+.Bd -unfilled -offset indent
+.Oo Ar n Oc Ns Li << Ar delimiter
+.Ar here-doc-text
+.Ar ...
+.Ar delimiter
+.Ed
+.Pp
+All the text on successive lines up to the delimiter is
+saved away and made available to the command on standard
+input, or file descriptor
+.Ar n
+if it is specified.
+If the
+.Ar delimiter
+as specified on the initial line is quoted, then the
+.Ar here-doc-text
+is treated literally, otherwise the text is subjected to
+parameter expansion, command substitution, and arithmetic
+expansion (as described in the section on
+.Sx Word Expansions ) .
+If the operator is
+.Dq Li <<-
+instead of
+.Dq Li << ,
+then leading tabs
+in the
+.Ar here-doc-text
+are stripped.
+.Ss Search and Execution
+There are three types of commands: shell functions,
+built-in commands, and normal programs.
+The command is searched for (by name) in that order.
+The three types of commands are all executed in a different way.
+.Pp
+When a shell function is executed, all of the shell positional
+parameters (except
+.Li $0 ,
+which remains unchanged) are
+set to the arguments of the shell function.
+The variables which are explicitly placed in the environment of
+the command (by placing assignments to them before the
+function name) are made local to the function and are set
+to the values given.
+Then the command given in the function definition is executed.
+The positional parameters are restored to their original values
+when the command completes.
+This all occurs within the current shell.
+.Pp
+Shell built-in commands are executed internally to the shell, without
+spawning a new process.
+There are two kinds of built-in commands: regular and special.
+Assignments before special builtins persist after they finish
+executing and assignment errors, redirection errors and certain
+operand errors cause a script to be aborted.
+Special builtins cannot be overridden with a function.
+Both regular and special builtins can affect the shell in ways
+normal programs cannot.
+.Pp
+Otherwise, if the command name does not match a function
+or built-in command, the command is searched for as a normal
+program in the file system (as described in the next section).
+When a normal program is executed, the shell runs the program,
+passing the arguments and the environment to the program.
+If the program is not a normal executable file
+(i.e., if it does not begin with the
+.Dq "magic number"
+whose
+.Tn ASCII
+representation is
+.Dq Li #! ,
+resulting in an
+.Er ENOEXEC
+return value from
+.Xr execve 2 )
+but appears to be a text file,
+the shell will run a new instance of
+.Nm
+to interpret it.
+.Pp
+Note that previous versions of this document
+and the source code itself misleadingly and sporadically
+refer to a shell script without a magic number
+as a
+.Dq "shell procedure" .
+.Ss Path Search
+When locating a command, the shell first looks to see if
+it has a shell function by that name.
+Then it looks for a
+built-in command by that name.
+If a built-in command is not found,
+one of two things happen:
+.Bl -enum
+.It
+Command names containing a slash are simply executed without
+performing any searches.
+.It
+The shell searches each entry in the
+.Va PATH
+variable
+in turn for the command.
+The value of the
+.Va PATH
+variable should be a series of
+entries separated by colons.
+Each entry consists of a
+directory name.
+The current directory
+may be indicated implicitly by an empty directory name,
+or explicitly by a single period.
+.El
+.Ss Command Exit Status
+Each command has an exit status that can influence the behavior
+of other shell commands.
+The paradigm is that a command exits
+with zero for normal or success, and non-zero for failure,
+error, or a false indication.
+The man page for each command
+should indicate the various exit codes and what they mean.
+Additionally, the built-in commands return exit codes, as does
+an executed shell function.
+.Pp
+If a command is terminated by a signal, its exit status is greater than 128.
+The signal name can be found by passing the exit status to
+.Li kill -l .
+.Pp
+If there is no command word,
+the exit status is the exit status of the last command substitution executed,
+or zero if the command does not contain any command substitutions.
+.Ss Complex Commands
+Complex commands are combinations of simple commands
+with control operators or keywords, together creating a larger complex
+command.
+More generally, a command is one of the following:
+.Bl -item -offset indent
+.It
+simple command
+.It
+pipeline
+.It
+list or compound-list
+.It
+compound command
+.It
+function definition
+.El
+.Pp
+Unless otherwise stated, the exit status of a command is
+that of the last simple command executed by the command,
+or zero if no simple command was executed.
+.Ss Pipelines
+A pipeline is a sequence of one or more commands separated
+by the control operator
+.Ql \&| .
+The standard output of all but
+the last command is connected to the standard input
+of the next command.
+The standard output of the last
+command is inherited from the shell, as usual.
+.Pp
+The format for a pipeline is:
+.Pp
+.D1 Oo Li \&! Oc Ar command1 Op Li \&| Ar command2 ...
+.Pp
+The standard output of
+.Ar command1
+is connected to the standard input of
+.Ar command2 .
+The standard input, standard output, or
+both of a command is considered to be assigned by the
+pipeline before any redirection specified by redirection
+operators that are part of the command.
+.Pp
+Note that unlike some other shells,
+.Nm
+executes each process in a pipeline with more than one command
+in a subshell environment and as a child of the
+.Nm
+process.
+.Pp
+If the pipeline is not in the background (discussed later),
+the shell waits for all commands to complete.
+.Pp
+If the keyword
+.Ic !\&
+does not precede the pipeline, the
+exit status is the exit status of the last command specified
+in the pipeline.
+Otherwise, the exit status is the logical
+NOT of the exit status of the last command.
+That is, if
+the last command returns zero, the exit status is 1; if
+the last command returns greater than zero, the exit status
+is zero.
+.Pp
+Because pipeline assignment of standard input or standard
+output or both takes place before redirection, it can be
+modified by redirection.
+For example:
+.Pp
+.Dl "command1 2>&1 | command2"
+.Pp
+sends both the standard output and standard error of
+.Ar command1
+to the standard input of
+.Ar command2 .
+.Pp
+A
+.Ql \&;
+or newline terminator causes the preceding
+AND-OR-list
+(described below in the section called
+.Sx Short-Circuit List Operators )
+to be executed sequentially;
+an
+.Ql &
+causes asynchronous execution of the preceding AND-OR-list.
+.Ss Background Commands (&)
+If a command is terminated by the control operator ampersand
+.Pq Ql & ,
+the shell executes the command in a subshell environment (see
+.Sx Grouping Commands Together
+below) and asynchronously;
+the shell does not wait for the command to finish
+before executing the next command.
+.Pp
+The format for running a command in background is:
+.Pp
+.D1 Ar command1 Li & Op Ar command2 Li & Ar ...
+.Pp
+If the shell is not interactive, the standard input of an
+asynchronous command is set to
+.Pa /dev/null .
+.Pp
+The exit status is zero.
+.Ss Lists (Generally Speaking)
+A list is a sequence of zero or more commands separated by
+newlines, semicolons, or ampersands,
+and optionally terminated by one of these three characters.
+The commands in a
+list are executed in the order they are written.
+If command is followed by an ampersand, the shell starts the
+command and immediately proceeds onto the next command;
+otherwise it waits for the command to terminate before
+proceeding to the next one.
+.Ss Short-Circuit List Operators
+.Dq Li &&
+and
+.Dq Li ||
+are AND-OR list operators.
+.Dq Li &&
+executes the first command, and then executes the second command
+if the exit status of the first command is zero.
+.Dq Li ||
+is similar, but executes the second command if the exit
+status of the first command is nonzero.
+.Dq Li &&
+and
+.Dq Li ||
+both have the same priority.
+.Ss Flow-Control Constructs (if, while, for, case)
+The syntax of the
+.Ic if
+command is:
+.Bd -unfilled -offset indent -compact
+.Ic if Ar list
+.Ic then Ar list
+.Oo Ic elif Ar list
+.Ic then Ar list Oc Ar ...
+.Op Ic else Ar list
+.Ic fi
+.Ed
+.Pp
+The exit status is that of selected
+.Ic then
+or
+.Ic else
+list,
+or zero if no list was selected.
+.Pp
+The syntax of the
+.Ic while
+command is:
+.Bd -unfilled -offset indent -compact
+.Ic while Ar list
+.Ic do Ar list
+.Ic done
+.Ed
+.Pp
+The two lists are executed repeatedly while the exit status of the
+first list is zero.
+The
+.Ic until
+command is similar, but has the word
+.Ic until
+in place of
+.Ic while ,
+which causes it to
+repeat until the exit status of the first list is zero.
+.Pp
+The exit status is that of the last execution of the second list,
+or zero if it was never executed.
+.Pp
+The syntax of the
+.Ic for
+command is:
+.Bd -unfilled -offset indent -compact
+.Ic for Ar variable Op Ic in Ar word ...
+.Ic do Ar list
+.Ic done
+.Ed
+.Pp
+If
+.Ic in
+and the following words are omitted,
+.Ic in Li \&"$@\&"
+is used instead.
+The words are expanded, and then the list is executed
+repeatedly with the variable set to each word in turn.
+The
+.Ic do
+and
+.Ic done
+commands may be replaced with
+.Ql {
+and
+.Ql } .
+.Pp
+The syntax of the
+.Ic break
+and
+.Ic continue
+commands is:
+.D1 Ic break Op Ar num
+.D1 Ic continue Op Ar num
+.Pp
+The
+.Ic break
+command terminates the
+.Ar num
+innermost
+.Ic for
+or
+.Ic while
+loops.
+The
+.Ic continue
+command continues with the next iteration of the innermost loop.
+These are implemented as special built-in commands.
+.Pp
+The syntax of the
+.Ic case
+command is:
+.Bd -unfilled -offset indent -compact
+.Ic case Ar word Ic in
+.Ar pattern Ns Li ) Ar list Li ;;
+.Ar ...
+.Ic esac
+.Ed
+.Pp
+The pattern can actually be one or more patterns
+(see
+.Sx Shell Patterns
+described later),
+separated by
+.Ql \&|
+characters.
+Tilde expansion, parameter expansion, command substitution,
+arithmetic expansion and quote removal are applied to the word.
+Then, each pattern is expanded in turn using tilde expansion,
+parameter expansion, command substitution and arithmetic expansion and
+the expanded form of the word is checked against it.
+If a match is found, the corresponding list is executed.
+If the selected list is terminated by the control operator
+.Ql ;&
+instead of
+.Ql ;; ,
+execution continues with the next list,
+continuing until a list terminated with
+.Ql ;;
+or the end of the
+.Ic case
+command.
+.Ss Grouping Commands Together
+Commands may be grouped by writing either
+.Pp
+.D1 Li \&( Ns Ar list Ns Li \%)
+.Pp
+or
+.Pp
+.D1 Li { Ar list Ns Li \&; }
+.Pp
+The first form executes the commands in a subshell environment.
+A subshell environment has its own copy of:
+.Bl -enum
+.It
+The current working directory as set by
+.Ic cd .
+.It
+The file creation mask as set by
+.Ic umask .
+.It
+Resource limits as set by
+.Ic ulimit .
+.It
+References to open files.
+.It
+Traps as set by
+.Ic trap .
+.It
+Known jobs.
+.It
+Positional parameters and variables.
+.It
+Shell options.
+.It
+Shell functions.
+.It
+Shell aliases.
+.El
+.Pp
+These are copied from the parent shell environment,
+except that trapped (but not ignored) signals are reset to the default action
+and known jobs are cleared.
+Any changes do not affect the parent shell environment.
+.Pp
+A subshell environment may be implemented as a child process or differently.
+If job control is enabled in an interactive shell,
+commands grouped in parentheses can be suspended and continued as a unit.
+.Pp
+For compatibility with other shells,
+two open parentheses in sequence should be separated by whitespace.
+.Pp
+The second form never forks another shell,
+so it is slightly more efficient.
+Grouping commands together this way allows the user to
+redirect their output as though they were one program:
+.Bd -literal -offset indent
+{ echo -n "hello"; echo " world"; } > greeting
+.Ed
+.Ss Functions
+The syntax of a function definition is
+.Pp
+.D1 Ar name Li \&( \&) Ar command
+.Pp
+A function definition is an executable statement; when
+executed it installs a function named
+.Ar name
+and returns an
+exit status of zero.
+The
+.Ar command
+is normally a list
+enclosed between
+.Ql {
+and
+.Ql } .
+.Pp
+Variables may be declared to be local to a function by
+using the
+.Ic local
+command.
+This should appear as the first statement of a function,
+and the syntax is:
+.Pp
+.D1 Ic local Oo Ar variable ... Oc Op Fl
+.Pp
+The
+.Ic local
+command is implemented as a built-in command.
+The exit status is zero
+unless the command is not in a function or a variable name is invalid.
+.Pp
+When a variable is made local, it inherits the initial
+value and exported and readonly flags from the variable
+with the same name in the surrounding scope, if there is
+one.
+Otherwise, the variable is initially unset.
+The shell
+uses dynamic scoping, so that if the variable
+.Va x
+is made local to function
+.Em f ,
+which then calls function
+.Em g ,
+references to the variable
+.Va x
+made inside
+.Em g
+will refer to the variable
+.Va x
+declared inside
+.Em f ,
+not to the global variable named
+.Va x .
+.Pp
+The only special parameter that can be made local is
+.Ql - .
+Making
+.Ql -
+local causes any shell options that are
+changed via the
+.Ic set
+command inside the function to be
+restored to their original values when the function
+returns.
+.Pp
+The syntax of the
+.Ic return
+command is
+.Pp
+.D1 Ic return Op Ar exitstatus
+.Pp
+It terminates the current executional scope, returning from the closest
+nested function or sourced script;
+if no function or sourced script is being executed,
+it exits the shell instance.
+The
+.Ic return
+command is implemented as a special built-in command.
+.Ss Variables and Parameters
+The shell maintains a set of parameters.
+A parameter
+denoted by a name
+(consisting solely
+of alphabetics, numerics, and underscores,
+and starting with an alphabetic or an underscore)
+is called a variable.
+When starting up,
+the shell turns all environment variables with valid names into shell
+variables.
+New variables can be set using the form
+.Pp
+.D1 Ar name Ns = Ns Ar value
+.Pp
+A parameter can also be denoted by a number
+or a special character as explained below.
+.Pp
+Assignments are expanded differently from other words:
+tilde expansion is also performed after the equals sign and after any colon
+and usernames are also terminated by colons,
+and field splitting and pathname expansion are not performed.
+.Pp
+This special expansion applies not only to assignments that form a simple
+command by themselves or precede a command word,
+but also to words passed to the
+.Ic export ,
+.Ic local
+or
+.Ic readonly
+built-in commands that have this form.
+For this, the builtin's name must be literal
+(not the result of an expansion)
+and may optionally be preceded by one or more literal instances of
+.Ic command
+without options.
+.Ss Positional Parameters
+A positional parameter is a parameter denoted by a number greater than zero.
+The shell sets these initially to the values of its command line
+arguments that follow the name of the shell script.
+The
+.Ic set
+built-in command can also be used to set or reset them.
+.Ss Special Parameters
+Special parameters are parameters denoted by a single special character
+or the digit zero.
+They are shown in the following list, exactly as they would appear in input
+typed by the user or in the source of a shell script.
+.Bl -hang
+.It Li $*
+Expands to the positional parameters, starting from one.
+When
+the expansion occurs within a double-quoted string
+it expands to a single field with the value of each parameter
+separated by the first character of the
+.Va IFS
+variable,
+or by a space if
+.Va IFS
+is unset.
+.It Li $@
+Expands to the positional parameters, starting from one.
+When
+the expansion occurs within double-quotes, each positional
+parameter expands as a separate argument.
+If there are no positional parameters, the
+expansion of
+.Li @
+generates zero arguments, even when
+.Li @
+is double-quoted.
+What this basically means, for example, is
+if
+.Li $1
+is
+.Dq Li abc
+and
+.Li $2
+is
+.Dq Li "def ghi" ,
+then
+.Li \&"$@\&"
+expands to
+the two arguments:
+.Bd -literal -offset indent
+"abc"   "def ghi"
+.Ed
+.It Li $#
+Expands to the number of positional parameters.
+.It Li $?
+Expands to the exit status of the most recent pipeline.
+.It Li $-
+(hyphen) Expands to the current option flags (the single-letter
+option names concatenated into a string) as specified on
+invocation, by the
+.Ic set
+built-in command, or implicitly
+by the shell.
+.It Li $$
+Expands to the process ID of the invoked shell.
+A subshell
+retains the same value of
+.Va $
+as its parent.
+.It Li $!
+Expands to the process ID of the most recent background
+command executed from the current shell.
+For a
+pipeline, the process ID is that of the last command in the
+pipeline.
+If this parameter is referenced, the shell will remember
+the process ID and its exit status until the
+.Ic wait
+built-in command reports completion of the process.
+.It Li $0
+(zero) Expands to the name of the shell script if passed on the command line,
+the
+.Ar name
+operand if given (with
+.Fl c )
+or otherwise argument 0 passed to the shell.
+.El
+.Ss Special Variables
+The following variables are set by the shell or
+have special meaning to it:
+.Bl -tag -width ".Va HISTSIZE"
+.It Va CDPATH
+The search path used with the
+.Ic cd
+built-in.
+.It Va EDITOR
+The fallback editor used with the
+.Ic fc
+built-in.
+If not set, the default editor is
+.Xr ed 1 .
+.It Va FCEDIT
+The default editor used with the
+.Ic fc
+built-in.
+.It Va HISTSIZE
+The number of previous commands that are accessible.
+.It Va HOME
+The user's home directory,
+used in tilde expansion and as a default directory for the
+.Ic cd
+built-in.
+.It Va IFS
+Input Field Separators.
+The default value is
+.Aq space ,
+.Aq tab ,
+and
+.Aq newline
+in that order.
+This default also applies if
+.Va IFS
+is unset, but not if it is set to the empty string.
+See the
+.Sx White Space Splitting
+section for more details.
+.It Va LINENO
+The current line number in the script or function.
+.It Va MAIL
+The name of a mail file, that will be checked for the arrival of new
+mail.
+Overridden by
+.Va MAILPATH .
+.It Va MAILPATH
+A colon
+.Pq Ql \&:
+separated list of file names, for the shell to check for incoming
+mail.
+This variable overrides the
+.Va MAIL
+setting.
+There is a maximum of 10 mailboxes that can be monitored at once.
+.It Va OPTIND
+The index of the next argument to be processed by
+.Ic getopts .
+This is initialized to 1 at startup.
+.It Va PATH
+The default search path for executables.
+See the
+.Sx Path Search
+section for details.
+.It Va PPID
+The parent process ID of the invoked shell.
+This is set at startup
+unless this variable is in the environment.
+A later change of parent process ID is not reflected.
+A subshell retains the same value of
+.Va PPID .
+.It Va PS1
+The primary prompt string, which defaults to
+.Dq Li "$ " ,
+unless you are the superuser, in which case it defaults to
+.Dq Li "# " .
+.Va PS1
+may include any of the following formatting sequences,
+which are replaced by the given information:
+.Bl -tag -width indent
+.It Li \eH
+The local hostname.
+.It Li \eh
+The fully-qualified hostname.
+.It Li \eW
+The final component of the current working directory.
+.It Li \ew
+The entire path of the current working directory.
+.It Li \e$
+Superuser status.
+.Dq Li "$ "
+for normal users and
+.Dq Li "# "
+for superusers.
+.It Li \e\e
+A literal backslash.
+.El
+.It Va PS2
+The secondary prompt string, which defaults to
+.Dq Li "> " .
+.Va PS2
+may include any of the formatting sequences from
+.Va PS1 .
+.It Va PS4
+The prefix for the trace output (if
+.Fl x
+is active).
+The default is
+.Dq Li "+ " .
+.El
+.Ss Word Expansions
+This clause describes the various expansions that are
+performed on words.
+Not all expansions are performed on
+every word, as explained later.
+.Pp
+Tilde expansions, parameter expansions, command substitutions,
+arithmetic expansions, and quote removals that occur within
+a single word expand to a single field.
+It is only field
+splitting or pathname expansion that can create multiple
+fields from a single word.
+The single exception to this rule is
+the expansion of the special parameter
+.Va @
+within double-quotes,
+as was described above.
+.Pp
+The order of word expansion is:
+.Bl -enum
+.It
+Tilde Expansion, Parameter Expansion, Command Substitution,
+Arithmetic Expansion (these all occur at the same time).
+.It
+Field Splitting is performed on fields generated by step (1)
+unless the
+.Va IFS
+variable is null.
+.It
+Pathname Expansion (unless the
+.Fl f
+option is in effect).
+.It
+Quote Removal.
+.El
+.Pp
+The
+.Ql $
+character is used to introduce parameter expansion, command
+substitution, or arithmetic expansion.
+.Ss Tilde Expansion (substituting a user's home directory)
+A word beginning with an unquoted tilde character
+.Pq Ql ~
+is
+subjected to tilde expansion.
+All the characters up to a slash
+.Pq Ql /
+or the end of the word are treated as a username
+and are replaced with the user's home directory.
+If the
+username is missing (as in
+.Pa ~/foobar ) ,
+the tilde is replaced with the value of the
+.Va HOME
+variable (the current user's home directory).
+.Ss Parameter Expansion
+The format for parameter expansion is as follows:
+.Pp
+.D1 Li ${ Ns Ar expression Ns Li }
+.Pp
+where
+.Ar expression
+consists of all characters until the matching
+.Ql } .
+Any
+.Ql }
+escaped by a backslash or within a single-quoted or double-quoted
+string, and characters in
+embedded arithmetic expansions, command substitutions, and variable
+expansions, are not examined in determining the matching
+.Ql } .
+If the variants with
+.Ql + ,
+.Ql - ,
+.Ql =
+or
+.Ql ?\&
+occur within a double-quoted string,
+as an extension there may be unquoted parts
+(via double-quotes inside the expansion);
+.Ql }
+within such parts are also not examined in determining the matching
+.Ql } .
+.Pp
+The simplest form for parameter expansion is:
+.Pp
+.D1 Li ${ Ns Ar parameter Ns Li }
+.Pp
+The value, if any, of
+.Ar parameter
+is substituted.
+.Pp
+The parameter name or symbol can be enclosed in braces, which are
+optional except for positional parameters with more than one digit or
+when parameter is followed by a character that could be interpreted as
+part of the name.
+If a parameter expansion occurs inside double-quotes:
+.Bl -enum
+.It
+Field splitting is not performed on the results of the
+expansion, with the exception of the special parameter
+.Va @ .
+.It
+Pathname expansion is not performed on the results of the
+expansion.
+.El
+.Pp
+In addition, a parameter expansion can be modified by using one of the
+following formats.
+.Bl -tag -width indent
+.It Li ${ Ns Ar parameter Ns Li :- Ns Ar word Ns Li }
+Use Default Values.
+If
+.Ar parameter
+is unset or null, the expansion of
+.Ar word
+is substituted; otherwise, the value of
+.Ar parameter
+is substituted.
+.It Li ${ Ns Ar parameter Ns Li := Ns Ar word Ns Li }
+Assign Default Values.
+If
+.Ar parameter
+is unset or null, the expansion of
+.Ar word
+is assigned to
+.Ar parameter .
+In all cases, the
+final value of
+.Ar parameter
+is substituted.
+Quoting inside
+.Ar word
+does not prevent field splitting or pathname expansion.
+Only variables, not positional
+parameters or special parameters, can be
+assigned in this way.
+.It Li ${ Ns Ar parameter Ns Li :? Ns Oo Ar word Oc Ns Li }
+Indicate Error if Null or Unset.
+If
+.Ar parameter
+is unset or null, the expansion of
+.Ar word
+(or a message indicating it is unset if
+.Ar word
+is omitted) is written to standard
+error and the shell exits with a nonzero
+exit status.
+Otherwise, the value of
+.Ar parameter
+is substituted.
+An
+interactive shell need not exit.
+.It Li ${ Ns Ar parameter Ns Li :+ Ns Ar word Ns Li }
+Use Alternate Value.
+If
+.Ar parameter
+is unset or null, null is substituted;
+otherwise, the expansion of
+.Ar word
+is substituted.
+.El
+.Pp
+In the parameter expansions shown previously, use of the colon in the
+format results in a test for a parameter that is unset or null; omission
+of the colon results in a test for a parameter that is only unset.
+.Pp
+The
+.Ar word
+inherits the type of quoting
+(unquoted, double-quoted or here-document)
+from the surroundings,
+with the exception that a backslash that quotes a closing brace is removed
+during quote removal.
+.Bl -tag -width indent
+.It Li ${# Ns Ar parameter Ns Li }
+String Length.
+The length in characters of
+the value of
+.Ar parameter .
+.El
+.Pp
+The following four varieties of parameter expansion provide for substring
+processing.
+In each case, pattern matching notation
+(see
+.Sx Shell Patterns ) ,
+rather than regular expression notation,
+is used to evaluate the patterns.
+If parameter is one of the special parameters
+.Va *
+or
+.Va @ ,
+the result of the expansion is unspecified.
+Enclosing the full parameter expansion string in double-quotes does not
+cause the following four varieties of pattern characters to be quoted,
+whereas quoting characters within the braces has this effect.
+.Bl -tag -width indent
+.It Li ${ Ns Ar parameter Ns Li % Ns Ar word Ns Li }
+Remove Smallest Suffix Pattern.
+The
+.Ar word
+is expanded to produce a pattern.
+The
+parameter expansion then results in
+.Ar parameter ,
+with the smallest portion of the
+suffix matched by the pattern deleted.
+.It Li ${ Ns Ar parameter Ns Li %% Ns Ar word Ns Li }
+Remove Largest Suffix Pattern.
+The
+.Ar word
+is expanded to produce a pattern.
+The
+parameter expansion then results in
+.Ar parameter ,
+with the largest portion of the
+suffix matched by the pattern deleted.
+.It Li ${ Ns Ar parameter Ns Li # Ns Ar word Ns Li }
+Remove Smallest Prefix Pattern.
+The
+.Ar word
+is expanded to produce a pattern.
+The
+parameter expansion then results in
+.Ar parameter ,
+with the smallest portion of the
+prefix matched by the pattern deleted.
+.It Li ${ Ns Ar parameter Ns Li ## Ns Ar word Ns Li }
+Remove Largest Prefix Pattern.
+The
+.Ar word
+is expanded to produce a pattern.
+The
+parameter expansion then results in
+.Ar parameter ,
+with the largest portion of the
+prefix matched by the pattern deleted.
+.El
+.Ss Command Substitution
+Command substitution allows the output of a command to be substituted in
+place of the command name itself.
+Command substitution occurs when
+the command is enclosed as follows:
+.Pp
+.D1 Li $( Ns Ar command Ns Li )\&
+.Pp
+or the backquoted version:
+.Pp
+.D1 Li ` Ns Ar command Ns Li `
+.Pp
+The shell expands the command substitution by executing command
+and replacing the command substitution
+with the standard output of the command,
+removing sequences of one or more newlines at the end of the substitution.
+Embedded newlines before the end of the output are not removed;
+however, during field splitting, they may be translated into spaces
+depending on the value of
+.Va IFS
+and the quoting that is in effect.
+The command is executed in a subshell environment,
+except that the built-in commands
+.Ic jobid ,
+.Ic jobs ,
+and
+.Ic trap
+return information about the parent shell environment
+and
+.Ic times
+returns information about the same process
+if they are the only command in a command substitution.
+.Pp
+If a command substitution of the
+.Li $(
+form begins with a subshell,
+the
+.Li $(
+and
+.Li (\&
+must be separated by whitespace
+to avoid ambiguity with arithmetic expansion.
+.Ss Arithmetic Expansion
+Arithmetic expansion provides a mechanism for evaluating an arithmetic
+expression and substituting its value.
+The format for arithmetic expansion is as follows:
+.Pp
+.D1 Li $(( Ns Ar expression Ns Li ))
+.Pp
+The
+.Ar expression
+is treated as if it were in double-quotes, except
+that a double-quote inside the expression is not treated specially.
+The
+shell expands all tokens in the
+.Ar expression
+for parameter expansion,
+command substitution,
+arithmetic expansion
+and quote removal.
+.Pp
+The allowed expressions are a subset of C expressions,
+summarized below.
+.Bl -tag -width "Variables" -offset indent
+.It Values
+All values are of type
+.Ft intmax_t .
+.It Constants
+Decimal, octal (starting with
+.Li 0 )
+and hexadecimal (starting with
+.Li 0x )
+integer constants.
+.It Variables
+Shell variables can be read and written
+and contain integer constants.
+.It Unary operators
+.Li "! ~ + -"
+.It Binary operators
+.Li "* / % + - << >> < <= > >= == != & ^ | && ||"
+.It Assignment operators
+.Li "= += -= *= /= %= <<= >>= &= ^= |="
+.It Conditional operator
+.Li "? :"
+.El
+.Pp
+The result of the expression is substituted in decimal.
+.Ss White Space Splitting (Field Splitting)
+In certain contexts,
+after parameter expansion, command substitution, and
+arithmetic expansion the shell scans the results of
+expansions and substitutions that did not occur in double-quotes for
+field splitting and multiple fields can result.
+.Pp
+Characters in
+.Va IFS
+that are whitespace
+.Po
+.Aq space ,
+.Aq tab ,
+and
+.Aq newline
+.Pc
+are treated differently from other characters in
+.Va IFS .
+.Pp
+Whitespace in
+.Va IFS
+at the beginning or end of a word is discarded.
+.Pp
+Subsequently, a field is delimited by either
+.Bl -enum
+.It
+a non-whitespace character in
+.Va IFS
+with any whitespace in
+.Va IFS
+surrounding it, or
+.It
+one or more whitespace characters in
+.Va IFS .
+.El
+.Pp
+If a word ends with a non-whitespace character in
+.Va IFS ,
+there is no empty field after this character.
+.Pp
+If no field is delimited, the word is discarded.
+In particular, if a word consists solely of an unquoted substitution
+and the result of the substitution is null,
+it is removed by field splitting even if
+.Va IFS
+is null.
+.Ss Pathname Expansion (File Name Generation)
+Unless the
+.Fl f
+option is set,
+file name generation is performed
+after word splitting is complete.
+Each word is
+viewed as a series of patterns, separated by slashes.
+The
+process of expansion replaces the word with the names of
+all existing files whose names can be formed by replacing
+each pattern with a string that matches the specified pattern.
+There are two restrictions on this: first, a pattern cannot match
+a string containing a slash, and second,
+a pattern cannot match a string starting with a period
+unless the first character of the pattern is a period.
+The next section describes the patterns used for
+Pathname Expansion,
+the four varieties of parameter expansion for substring processing and the
+.Ic case
+command.
+.Ss Shell Patterns
+A pattern consists of normal characters, which match themselves,
+and meta-characters.
+The meta-characters are
+.Ql * ,
+.Ql \&? ,
+and
+.Ql \&[ .
+These characters lose their special meanings if they are quoted.
+When command or variable substitution is performed and the dollar sign
+or back quotes are not double-quoted, the value of the
+variable or the output of the command is scanned for these
+characters and they are turned into meta-characters.
+.Pp
+An asterisk
+.Pq Ql *
+matches any string of characters.
+A question mark
+.Pq Ql \&?
+matches any single character.
+A left bracket
+.Pq Ql \&[
+introduces a character class.
+The end of the character class is indicated by a
+.Ql \&] ;
+if the
+.Ql \&]
+is missing then the
+.Ql \&[
+matches a
+.Ql \&[
+rather than introducing a character class.
+A character class matches any of the characters between the square brackets.
+A locale-dependent range of characters may be specified using a minus sign.
+A named class of characters (see
+.Xr wctype 3 )
+may be specified by surrounding the name with
+.Ql \&[:
+and
+.Ql :\&] .
+For example,
+.Ql \&[\&[:alpha:\&]\&]
+is a shell pattern that matches a single letter.
+The character class may be complemented by making an exclamation point
+.Pq Ql !\&
+the first character of the character class.
+A caret
+.Pq Ql ^
+has the same effect but is non-standard.
+.Pp
+To include a
+.Ql \&]
+in a character class, make it the first character listed
+(after the
+.Ql \&!
+or
+.Ql ^ ,
+if any).
+To include a
+.Ql - ,
+make it the first or last character listed.
+.Ss Built-in Commands
+This section lists the built-in commands.
+.Bl -tag -width indent
+.It Ic \&:
+A null command that returns a 0 (true) exit value.
+.It Ic \&. Ar file
+The commands in the specified file are read and executed by the shell.
+The
+.Ic return
+command may be used to return to the
+.Ic \&.
+command's caller.
+If
+.Ar file
+contains any
+.Ql /
+characters, it is used as is.
+Otherwise, the shell searches the
+.Va PATH
+for the file.
+If it is not found in the
+.Va PATH ,
+it is sought in the current working directory.
+.It Ic \&[
+A built-in equivalent of
+.Xr test 1 .
+.It Ic alias Oo Ar name Ns Oo = Ns Ar string Oc ... Oc
+If
+.Ar name Ns = Ns Ar string
+is specified, the shell defines the alias
+.Ar name
+with value
+.Ar string .
+If just
+.Ar name
+is specified, the value of the alias
+.Ar name
+is printed.
+With no arguments, the
+.Ic alias
+built-in command prints the names and values of all defined aliases
+(see
+.Ic unalias ) .
+Alias values are written with appropriate quoting so that they are
+suitable for re-input to the shell.
+Also see the
+.Sx Aliases
+subsection.
+.It Ic bg Op Ar job ...
+Continue the specified jobs
+(or the current job if no jobs are given)
+in the background.
+.It Ic bind Oo Fl aeklrsv Oc Oo Ar key Oo Ar command Oc Oc
+List or alter key bindings for the line editor.
+This command is documented in
+.Xr editrc 5 .
+.It Ic break Op Ar num
+See the
+.Sx Flow-Control Constructs
+subsection.
+.It Ic builtin Ar cmd Op Ar arg ...
+Execute the specified built-in command,
+.Ar cmd .
+This is useful when the user wishes to override a shell function
+with the same name as a built-in command.
+.It Ic cd Oo Fl L | P Oc Oo Fl e Oc Op Ar directory
+Switch to the specified
+.Ar directory ,
+or to the directory specified in the
+.Va HOME
+environment variable if no
+.Ar directory
+is specified.
+If
+.Ar directory
+does not begin with
+.Pa / , \&. ,
+or
+.Pa .. ,
+then the directories listed in the
+.Va CDPATH
+variable will be
+searched for the specified
+.Ar directory .
+If
+.Va CDPATH
+is unset, the current directory is searched.
+The format of
+.Va CDPATH
+is the same as that of
+.Va PATH .
+In an interactive shell,
+the
+.Ic cd
+command will print out the name of the directory
+that it actually switched to
+if this is different from the name that the user gave.
+These may be different either because the
+.Va CDPATH
+mechanism was used or because a symbolic link was crossed.
+.Pp
+If the
+.Fl P
+option is specified,
+.Pa ..
+is handled physically and symbolic links are resolved before
+.Pa ..
+components are processed.
+If the
+.Fl L
+option is specified,
+.Pa ..
+is handled logically.
+This is the default.
+.Pp
+The
+.Fl e
+option causes
+.Ic cd
+to return exit status 1 if the full pathname of the new directory
+cannot be determined reliably or at all.
+Normally this is not considered an error,
+although a warning is printed.
+.It Ic chdir
+A synonym for the
+.Ic cd
+built-in command.
+.It Ic command Oo Fl p Oc Op Ar utility Op Ar argument ...
+.It Ic command Oo Fl p Oc Fl v Ar utility
+.It Ic command Oo Fl p Oc Fl V Ar utility
+The first form of invocation executes the specified
+.Ar utility ,
+ignoring shell functions in the search.
+If
+.Ar utility
+is a special builtin,
+it is executed as if it were a regular builtin.
+.Pp
+If the
+.Fl p
+option is specified, the command search is performed using a
+default value of
+.Va PATH
+that is guaranteed to find all of the standard utilities.
+.Pp
+If the
+.Fl v
+option is specified,
+.Ar utility
+is not executed but a description of its interpretation by the shell is
+printed.
+For ordinary commands the output is the path name; for shell built-in
+commands, shell functions and keywords only the name is written.
+Aliases are printed as
+.Dq Ic alias Ar name Ns = Ns Ar value .
+.Pp
+The
+.Fl V
+option is identical to
+.Fl v
+except for the output.
+It prints
+.Dq Ar utility Ic is Ar description
+where
+.Ar description
+is either
+the path name to
+.Ar utility ,
+a special shell builtin,
+a shell builtin,
+a shell function,
+a shell keyword
+or
+an alias for
+.Ar value .
+.It Ic continue Op Ar num
+See the
+.Sx Flow-Control Constructs
+subsection.
+.It Ic echo Oo Fl e | n Oc Op Ar string ...
+Print a space-separated list of the arguments to the standard output
+and append a newline character.
+.Bl -tag -width indent
+.It Fl n
+Suppress the output of the trailing newline.
+.It Fl e
+Process C-style backslash escape sequences.
+The
+.Ic echo
+command understands the following character escapes:
+.Bl -tag -width indent
+.It \ea
+Alert (ring the terminal bell)
+.It \eb
+Backspace
+.It \ec
+Suppress the trailing newline (this has the side-effect of truncating the
+line if it is not the last character)
+.It \ee
+The ESC character
+.Tn ( ASCII
+0x1b)
+.It \ef
+Formfeed
+.It \en
+Newline
+.It \er
+Carriage return
+.It \et
+Horizontal tab
+.It \ev
+Vertical tab
+.It \e\e
+Literal backslash
+.It \e0nnn
+(Zero) The character whose octal value is
+.Ar nnn
+.El
+.Pp
+If
+.Ar string
+is not enclosed in quotes then the backslash itself must be escaped
+with a backslash to protect it from the shell.
+For example
+.Bd -literal -offset indent
+$ echo -e "a\evb"
+a
+ b
+$ echo -e a\e\evb
+a
+ b
+$ echo -e "a\e\eb"
+a\eb
+$ echo -e a\e\e\e\eb
+a\eb
+.Ed
+.El
+.Pp
+Only one of the
+.Fl e
+and
+.Fl n
+options may be specified.
+.It Ic eval Ar string ...
+Concatenate all the arguments with spaces.
+Then re-parse and execute the command.
+.It Ic exec Op Ar command Op arg ...
+Unless
+.Ar command
+is omitted,
+the shell process is replaced with the specified program
+(which must be a real program, not a shell built-in command or function).
+Any redirections on the
+.Ic exec
+command are marked as permanent,
+so that they are not undone when the
+.Ic exec
+command finishes.
+.It Ic exit Op Ar exitstatus
+Terminate the shell process.
+If
+.Ar exitstatus
+is given
+it is used as the exit status of the shell.
+Otherwise, if the shell is executing an
+.Cm EXIT
+trap, the exit status of the last command before the trap is used;
+if the shell is executing a trap for a signal,
+the shell exits by resending the signal to itself.
+Otherwise, the exit status of the preceding command is used.
+The exit status should be an integer between 0 and 255.
+.It Ic export Ar name ...
+.It Ic export Op Fl p
+The specified names are exported so that they will
+appear in the environment of subsequent commands.
+The only way to un-export a variable is to
+.Ic unset
+it.
+The shell allows the value of a variable to be set
+at the same time as it is exported by writing
+.Pp
+.D1 Ic export Ar name Ns = Ns Ar value
+.Pp
+With no arguments the
+.Ic export
+command lists the names
+of all exported variables.
+If the
+.Fl p
+option is specified, the exported variables are printed as
+.Dq Ic export Ar name Ns = Ns Ar value
+lines, suitable for re-input to the shell.
+.It Ic false
+A null command that returns a non-zero (false) exit value.
+.It Ic fc Oo Fl e Ar editor Oc Op Ar first Op Ar last
+.It Ic fc Fl l Oo Fl nr Oc Op Ar first Op Ar last
+.It Ic fc Fl s Oo Ar old Ns = Ns Ar new Oc Op Ar first
+The
+.Ic fc
+built-in command lists, or edits and re-executes,
+commands previously entered to an interactive shell.
+.Bl -tag -width indent
+.It Fl e Ar editor
+Use the editor named by
+.Ar editor
+to edit the commands.
+The
+.Ar editor
+string is a command name,
+subject to search via the
+.Va PATH
+variable.
+The value in the
+.Va FCEDIT
+variable is used as a default when
+.Fl e
+is not specified.
+If
+.Va FCEDIT
+is null or unset, the value of the
+.Va EDITOR
+variable is used.
+If
+.Va EDITOR
+is null or unset,
+.Xr ed 1
+is used as the editor.
+.It Fl l No (ell)
+List the commands rather than invoking
+an editor on them.
+The commands are written in the
+sequence indicated by the
+.Ar first
+and
+.Ar last
+operands, as affected by
+.Fl r ,
+with each command preceded by the command number.
+.It Fl n
+Suppress command numbers when listing with
+.Fl l .
+.It Fl r
+Reverse the order of the commands listed
+(with
+.Fl l )
+or edited
+(with neither
+.Fl l
+nor
+.Fl s ) .
+.It Fl s
+Re-execute the command without invoking an editor.
+.It Ar first
+.It Ar last
+Select the commands to list or edit.
+The number of previous commands that can be accessed
+are determined by the value of the
+.Va HISTSIZE
+variable.
+The value of
+.Ar first
+or
+.Ar last
+or both are one of the following:
+.Bl -tag -width indent
+.It Oo Cm + Oc Ns Ar num
+A positive number representing a command number;
+command numbers can be displayed with the
+.Fl l
+option.
+.It Fl Ar num
+A negative decimal number representing the
+command that was executed
+.Ar num
+of
+commands previously.
+For example, \-1 is the immediately previous command.
+.It Ar string
+A string indicating the most recently entered command
+that begins with that string.
+If the
+.Ar old Ns = Ns Ar new
+operand is not also specified with
+.Fl s ,
+the string form of the first operand cannot contain an embedded equal sign.
+.El
+.El
+.Pp
+The following variables affect the execution of
+.Ic fc :
+.Bl -tag -width ".Va HISTSIZE"
+.It Va FCEDIT
+Name of the editor to use for history editing.
+.It Va HISTSIZE
+The number of previous commands that are accessible.
+.El
+.It Ic fg Op Ar job
+Move the specified
+.Ar job
+or the current job to the foreground.
+.It Ic getopts Ar optstring var
+The
+.Tn POSIX
+.Ic getopts
+command.
+The
+.Ic getopts
+command deprecates the older
+.Xr getopt 1
+command.
+The first argument should be a series of letters, each possibly
+followed by a colon which indicates that the option takes an argument.
+The specified variable is set to the parsed option.
+The index of
+the next argument is placed into the shell variable
+.Va OPTIND .
+If an option takes an argument, it is placed into the shell variable
+.Va OPTARG .
+If an invalid option is encountered,
+.Ar var
+is set to
+.Ql \&? .
+It returns a false value (1) when it encounters the end of the options.
+A new set of arguments may be parsed by assigning
+.Li OPTIND=1 .
+.It Ic hash Oo Fl rv Oc Op Ar command ...
+The shell maintains a hash table which remembers the locations of commands.
+With no arguments whatsoever, the
+.Ic hash
+command prints out the contents of this table.
+.Pp
+With arguments, the
+.Ic hash
+command removes each specified
+.Ar command
+from the hash table (unless they are functions) and then locates it.
+With the
+.Fl v
+option,
+.Ic hash
+prints the locations of the commands as it finds them.
+The
+.Fl r
+option causes the
+.Ic hash
+command to delete all the entries in the hash table except for functions.
+.It Ic jobid Op Ar job
+Print the process IDs of the processes in the specified
+.Ar job .
+If the
+.Ar job
+argument is omitted, use the current job.
+.It Ic jobs Oo Fl lps Oc Op Ar job ...
+Print information about the specified jobs, or all jobs if no
+.Ar job
+argument is given.
+The information printed includes job ID, status and command name.
+.Pp
+If the
+.Fl l
+option is specified, the PID of each job is also printed.
+If the
+.Fl p
+option is specified, only the process IDs for the process group leaders
+are printed, one per line.
+If the
+.Fl s
+option is specified, only the PIDs of the job commands are printed, one per
+line.
+.It Ic kill
+A built-in equivalent of
+.Xr kill 1
+that additionally supports sending signals to jobs.
+.It Ic local Oo Ar variable ... Oc Op Fl
+See the
+.Sx Functions
+subsection.
+.It Ic printf
+A built-in equivalent of
+.Xr printf 1 .
+.It Ic pwd Op Fl L | P
+Print the path of the current directory.
+The built-in command may
+differ from the program of the same name because the
+built-in command remembers what the current directory
+is rather than recomputing it each time.
+This makes
+it faster.
+However, if the current directory is
+renamed,
+the built-in version of
+.Xr pwd 1
+will continue to print the old name for the directory.
+.Pp
+If the
+.Fl P
+option is specified, symbolic links are resolved.
+If the
+.Fl L
+option is specified, the shell's notion of the current directory
+is printed (symbolic links are not resolved).
+This is the default.
+.It Ic read Oo Fl p Ar prompt Oc Oo
+.Fl t Ar timeout Oc Oo Fl er Oc Ar variable ...
+The
+.Ar prompt
+is printed if the
+.Fl p
+option is specified
+and the standard input is a terminal.
+Then a line is
+read from the standard input.
+The trailing newline
+is deleted from the line and the line is split as
+described in the section on
+.Sx White Space Splitting (Field Splitting)
+above, and
+the pieces are assigned to the variables in order.
+If there are more pieces than variables, the remaining
+pieces (along with the characters in
+.Va IFS
+that separated them)
+are assigned to the last variable.
+If there are more variables than pieces, the remaining
+variables are assigned the null string.
+.Pp
+Backslashes are treated specially, unless the
+.Fl r
+option is
+specified.
+If a backslash is followed by
+a newline, the backslash and the newline will be
+deleted.
+If a backslash is followed by any other
+character, the backslash will be deleted and the following
+character will be treated as though it were not in
+.Va IFS ,
+even if it is.
+.Pp
+If the
+.Fl t
+option is specified and the
+.Ar timeout
+elapses before a complete line of input is supplied,
+the
+.Ic read
+command will return an exit status as if terminated by
+.Dv SIGALRM
+without assigning any values.
+The
+.Ar timeout
+value may optionally be followed by one of
+.Ql s ,
+.Ql m
+or
+.Ql h
+to explicitly specify seconds, minutes or hours.
+If none is supplied,
+.Ql s
+is assumed.
+.Pp
+The
+.Fl e
+option exists only for backward compatibility with older scripts.
+.Pp
+The exit status is 0 on success, 1 on end of file,
+between 2 and 128 if an error occurs
+and greater than 128 if a trapped signal interrupts
+.Ic read .
+.It Ic readonly Oo Fl p Oc Op Ar name ...
+Each specified
+.Ar name
+is marked as read only,
+so that it cannot be subsequently modified or unset.
+The shell allows the value of a variable to be set
+at the same time as it is marked read only
+by using the following form:
+.Pp
+.D1 Ic readonly Ar name Ns = Ns Ar value
+.Pp
+With no arguments the
+.Ic readonly
+command lists the names of all read only variables.
+If the
+.Fl p
+option is specified, the read-only variables are printed as
+.Dq Ic readonly Ar name Ns = Ns Ar value
+lines, suitable for re-input to the shell.
+.It Ic return Op Ar exitstatus
+See the
+.Sx Functions
+subsection.
+.It Ic set Oo Fl /+abCEefIimnpTuVvx Oc Oo Fl /+o Ar longname Oc Oo
+.Fl c Ar string Oc Op Fl - Ar arg ...
+The
+.Ic set
+command performs three different functions:
+.Bl -item
+.It
+With no arguments, it lists the values of all shell variables.
+.It
+If options are given,
+either in short form or using the long
+.Dq Fl /+o Ar longname
+form,
+it sets or clears the specified options as described in the section called
+.Sx Argument List Processing .
+.It
+If the
+.Dq Fl -
+option is specified,
+.Ic set
+will replace the shell's positional parameters with the subsequent
+arguments.
+If no arguments follow the
+.Dq Fl -
+option,
+all the positional parameters will be cleared,
+which is equivalent to executing the command
+.Dq Li "shift $#" .
+The
+.Dq Fl -
+flag may be omitted when specifying arguments to be used
+as positional replacement parameters.
+This is not recommended,
+because the first argument may begin with a dash
+.Pq Ql -
+or a plus
+.Pq Ql + ,
+which the
+.Ic set
+command will interpret as a request to enable or disable options.
+.El
+.It Ic setvar Ar variable value
+Assigns the specified
+.Ar value
+to the specified
+.Ar variable .
+The
+.Ic setvar
+command is intended to be used in functions that
+assign values to variables whose names are passed as parameters.
+In general it is better to write
+.Dq Ar variable Ns = Ns Ar value
+rather than using
+.Ic setvar .
+.It Ic shift Op Ar n
+Shift the positional parameters
+.Ar n
+times, or once if
+.Ar n
+is not specified.
+A shift sets the value of
+.Li $1
+to the value of
+.Li $2 ,
+the value of
+.Li $2
+to the value of
+.Li $3 ,
+and so on,
+decreasing the value of
+.Li $#
+by one.
+For portability, shifting if there are zero positional parameters
+should be avoided, since the shell may abort.
+.It Ic test
+A built-in equivalent of
+.Xr test 1 .
+.It Ic times
+Print the amount of time spent executing the shell process and its children.
+The first output line shows the user and system times for the shell process
+itself, the second one contains the user and system times for the
+children.
+.It Ic trap Oo Ar action Oc Ar signal ...
+.It Ic trap Fl l
+Cause the shell to parse and execute
+.Ar action
+when any specified
+.Ar signal
+is received.
+The signals are specified by name or number.
+In addition, the pseudo-signal
+.Cm EXIT
+may be used to specify an
+.Ar action
+that is performed when the shell terminates.
+The
+.Ar action
+may be an empty string or a dash
+.Pq Ql - ;
+the former causes the specified signal to be ignored
+and the latter causes the default action to be taken.
+Omitting the
+.Ar action
+and using only signal numbers is another way to request the default action.
+In a subshell or utility environment,
+the shell resets trapped (but not ignored) signals to the default action.
+The
+.Ic trap
+command has no effect on signals that were ignored on entry to the shell.
+.Pp
+Option
+.Fl l
+causes the
+.Ic trap
+command to display a list of valid signal names.
+.It Ic true
+A null command that returns a 0 (true) exit value.
+.It Ic type Op Ar name ...
+Interpret each
+.Ar name
+as a command and print the resolution of the command search.
+Possible resolutions are:
+shell keyword, alias, special shell builtin, shell builtin, command,
+tracked alias
+and not found.
+For aliases the alias expansion is printed;
+for commands and tracked aliases
+the complete pathname of the command is printed.
+.It Ic ulimit Oo Fl HSabcdfklmnpstuvw Oc Op Ar limit
+Set or display resource limits (see
+.Xr getrlimit 2 ) .
+If
+.Ar limit
+is specified, the named resource will be set;
+otherwise the current resource value will be displayed.
+.Pp
+If
+.Fl H
+is specified, the hard limits will be set or displayed.
+While everybody is allowed to reduce a hard limit,
+only the superuser can increase it.
+The
+.Fl S
+option
+specifies the soft limits instead.
+When displaying limits,
+only one of
+.Fl S
+or
+.Fl H
+can be given.
+The default is to display the soft limits,
+and to set both the hard and the soft limits.
+.Pp
+Option
+.Fl a
+causes the
+.Ic ulimit
+command to display all resources.
+The parameter
+.Ar limit
+is not acceptable in this mode.
+.Pp
+The remaining options specify which resource value is to be
+displayed or modified.
+They are mutually exclusive.
+.Bl -tag -width indent
+.It Fl b Ar sbsize
+The maximum size of socket buffer usage, in bytes.
+.It Fl c Ar coredumpsize
+The maximal size of core dump files, in 512-byte blocks.
+.It Fl d Ar datasize
+The maximal size of the data segment of a process, in kilobytes.
+.It Fl f Ar filesize
+The maximal size of a file, in 512-byte blocks.
+.It Fl k Ar kqueues
+The maximal number of kqueues
+(see
+.Xr kqueue 2 )
+for this user ID.
+.It Fl l Ar lockedmem
+The maximal size of memory that can be locked by a process, in
+kilobytes.
+.It Fl m Ar memoryuse
+The maximal resident set size of a process, in kilobytes.
+.It Fl n Ar nofiles
+The maximal number of descriptors that could be opened by a process.
+.It Fl p Ar pseudoterminals
+The maximal number of pseudo-terminals for this user ID.
+.It Fl s Ar stacksize
+The maximal size of the stack segment, in kilobytes.
+.It Fl t Ar time
+The maximal amount of CPU time to be used by each process, in seconds.
+.It Fl u Ar userproc
+The maximal number of simultaneous processes for this user ID.
+.It Fl v Ar virtualmem
+The maximal virtual size of a process, in kilobytes.
+.It Fl w Ar swapuse
+The maximum amount of swap space reserved or used for this user ID,
+in kilobytes.
+.El
+.It Ic umask Oo Fl S Oc Op Ar mask
+Set the file creation mask (see
+.Xr umask 2 )
+to the octal or symbolic (see
+.Xr chmod 1 )
+value specified by
+.Ar mask .
+If the argument is omitted, the current mask value is printed.
+If the
+.Fl S
+option is specified, the output is symbolic, otherwise the output is octal.
+.It Ic unalias Oo Fl a Oc Op Ar name ...
+The specified alias names are removed.
+If
+.Fl a
+is specified, all aliases are removed.
+.It Ic unset Oo Fl fv Oc Ar name ...
+The specified variables or functions are unset and unexported.
+If the
+.Fl v
+option is specified or no options are given, the
+.Ar name
+arguments are treated as variable names.
+If the
+.Fl f
+option is specified, the
+.Ar name
+arguments are treated as function names.
+.It Ic wait Op Ar job ...
+Wait for each specified
+.Ar job
+to complete and return the exit status of the last process in the
+last specified
+.Ar job .
+If any
+.Ar job
+specified is unknown to the shell, it is treated as if it
+were a known job that exited with exit status 127.
+If no operands are given, wait for all jobs to complete
+and return an exit status of zero.
+.El
+.Ss Commandline Editing
+When
+.Nm
+is being used interactively from a terminal, the current command
+and the command history
+(see
+.Ic fc
+in
+.Sx Built-in Commands )
+can be edited using
+.Nm vi Ns -mode
+command line editing.
+This mode uses commands similar
+to a subset of those described in the
+.Xr vi 1
+man page.
+The command
+.Dq Li "set -o vi"
+(or
+.Dq Li "set -V" )
+enables
+.Nm vi Ns -mode
+editing and places
+.Nm
+into
+.Nm vi
+insert mode.
+With
+.Nm vi Ns -mode
+enabled,
+.Nm
+can be switched between insert mode and command mode by typing
+.Aq ESC .
+Hitting
+.Aq return
+while in command mode will pass the line to the shell.
+.Pp
+Similarly, the
+.Dq Li "set -o emacs"
+(or
+.Dq Li "set -E" )
+command can be used to enable a subset of
+.Nm emacs Ns -style
+command line editing features.
+.Sh ENVIRONMENT
+The following environment variables affect the execution of
+.Nm :
+.Bl -tag -width ".Ev LANGXXXXXX"
+.It Ev ENV
+Initialization file for interactive shells.
+.It Ev LANG , Ev LC_*
+Locale settings.
+These are inherited by children of the shell,
+and is used in a limited manner by the shell itself.
+.It Ev PWD
+An absolute pathname for the current directory,
+possibly containing symbolic links.
+This is used and updated by the shell.
+.It Ev TERM
+The default terminal setting for the shell.
+This is inherited by children of the shell, and is used in the history
+editing modes.
+.El
+.Pp
+Additionally, environment variables are turned into shell variables
+at startup,
+which may affect the shell as described under
+.Sx Special Variables .
+.Sh FILES
+.Bl -tag -width "/etc/suid_profileXX" -compact
+.It Pa ~/.profile
+User's login profile.
+.It Pa /etc/profile
+System login profile.
+.It Pa /etc/shells
+Shell database.
+.It Pa /etc/suid_profile
+Privileged shell profile.
+.El
+.Sh EXIT STATUS
+Errors that are detected by the shell, such as a syntax error, will
+cause the shell to exit with a non-zero exit status.
+If the shell is not an interactive shell, the execution of the shell
+file will be aborted.
+Otherwise the shell will return the exit status of the last command
+executed, or if the
+.Ic exit
+builtin is used with a numeric argument, it
+will return the argument.
+.Sh SEE ALSO
+.Xr builtin 1 ,
+.Xr chsh 1 ,
+.Xr echo 1 ,
+.Xr ed 1 ,
+.Xr emacs 1 ,
+.Xr kill 1 ,
+.Xr printf 1 ,
+.Xr pwd 1 ,
+.Xr test 1 ,
+.Xr vi 1 ,
+.Xr execve 2 ,
+.Xr getrlimit 2 ,
+.Xr umask 2 ,
+.Xr wctype 3 ,
+.Xr editrc 5 ,
+.Xr shells 5
+.Sh HISTORY
+A
+.Nm
+command, the Thompson shell, appeared in
+.At v1 .
+It was superseded in
+.At v7
+by the Bourne shell, which inherited the name
+.Nm .
+.Pp
+This version of
+.Nm
+was rewritten in 1989 under the
+.Bx
+license after the Bourne shell from
+.At V.4 .
+.Sh AUTHORS
+This version of
+.Nm
+was originally written by
+.An Kenneth Almquist .
+.Sh BUGS
+The
+.Nm
+utility does not recognize multibyte characters other than UTF-8.
+Splitting using
+.Va IFS
+does not recognize multibyte characters.
diff --git a/sh/sh.plist.part b/sh/sh.plist.part
new file mode 100644 (file)
index 0000000..f1fb688
--- /dev/null
@@ -0,0 +1,22 @@
+       <dict>
+               <key>OpenSourceProject</key>
+               <string>sh</string>
+               <key>OpenSourceVersion</key>
+               <string>2015-07-11</string>
+               <key>OpenSourceWebsiteURL</key>
+               <string>http://svnweb.freebsd.org/base/head/bin/sh/</string>
+               <key>OpenSourceSCM</key>
+               <string>svn co http://svn.freebsd.org/base/head/bin/sh/</string>
+               <key>OpenSourceImportDate</key>
+               <string>2015-08-13</string>
+               <key>OpenSourceModifications</key>
+               <array>
+                   <string>exec.c: eaccess</string>
+                   <string>input.c: remove multiple definition</string>
+                   <string>miscbltin.c: warning fix</string>
+                   <string>mknodes.c: __printf0like</string>
+                   <string>trap.c: sys_nsig</string>
+               </array>
+               <key>OpenSourceLicense</key>
+               <string>bsd</string>
+       </dict>
diff --git a/sh/shell.h b/sh/shell.h
new file mode 100644 (file)
index 0000000..679efc7
--- /dev/null
@@ -0,0 +1,77 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)shell.h     8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+#ifndef SHELL_H_
+#define SHELL_H_
+
+#include <inttypes.h>
+
+/*
+ * The follow should be set to reflect the type of system you have:
+ *     JOBS -> 1 if you have Berkeley job control, 0 otherwise.
+ *     define DEBUG=1 to compile in debugging (set global "debug" to turn on)
+ *     define DEBUG=2 to compile in and turn on debugging.
+ *
+ * When debugging is on, debugging info will be written to ./trace and
+ * a quit signal will generate a core dump.
+ */
+
+
+#define        JOBS 1
+/* #define DEBUG 1 */
+
+/*
+ * Type of used arithmetics. SUSv3 requires us to have at least signed long.
+ */
+typedef intmax_t arith_t;
+#define        ARITH_FORMAT_STR  "%" PRIdMAX
+#define        atoarith_t(arg)  strtoimax(arg, NULL, 0)
+#define        strtoarith_t(nptr, endptr, base)  strtoimax(nptr, endptr, base)
+#define        ARITH_MIN INTMAX_MIN
+#define        ARITH_MAX INTMAX_MAX
+
+typedef void *pointer;
+
+#include <sys/cdefs.h>
+
+extern char nullstr[1];                /* null string */
+
+#ifdef DEBUG
+#define TRACE(param)  sh_trace param
+#else
+#define TRACE(param)
+#endif
+
+#endif /* !SHELL_H_ */
diff --git a/sh/show.c b/sh/show.c
new file mode 100644 (file)
index 0000000..db18ae9
--- /dev/null
+++ b/sh/show.c
@@ -0,0 +1,408 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)show.c     8.3 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include "shell.h"
+#include "parser.h"
+#include "nodes.h"
+#include "mystring.h"
+#include "show.h"
+
+
+#ifdef DEBUG
+static void shtree(union node *, int, char *, FILE*);
+static void shcmd(union node *, FILE *);
+static void sharg(union node *, FILE *);
+static void indent(int, char *, FILE *);
+static void trstring(char *);
+
+
+void
+showtree(union node *n)
+{
+       trputs("showtree called\n");
+       shtree(n, 1, NULL, stdout);
+}
+
+
+static void
+shtree(union node *n, int ind, char *pfx, FILE *fp)
+{
+       struct nodelist *lp;
+       char *s;
+
+       if (n == NULL)
+               return;
+
+       indent(ind, pfx, fp);
+       switch(n->type) {
+       case NSEMI:
+               s = "; ";
+               goto binop;
+       case NAND:
+               s = " && ";
+               goto binop;
+       case NOR:
+               s = " || ";
+binop:
+               shtree(n->nbinary.ch1, ind, NULL, fp);
+          /*    if (ind < 0) */
+                       fputs(s, fp);
+               shtree(n->nbinary.ch2, ind, NULL, fp);
+               break;
+       case NCMD:
+               shcmd(n, fp);
+               if (ind >= 0)
+                       putc('\n', fp);
+               break;
+       case NPIPE:
+               for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+                       shcmd(lp->n, fp);
+                       if (lp->next)
+                               fputs(" | ", fp);
+               }
+               if (n->npipe.backgnd)
+                       fputs(" &", fp);
+               if (ind >= 0)
+                       putc('\n', fp);
+               break;
+       default:
+               fprintf(fp, "<node type %d>", n->type);
+               if (ind >= 0)
+                       putc('\n', fp);
+               break;
+       }
+}
+
+
+
+static void
+shcmd(union node *cmd, FILE *fp)
+{
+       union node *np;
+       int first;
+       char *s;
+       int dftfd;
+
+       first = 1;
+       for (np = cmd->ncmd.args ; np ; np = np->narg.next) {
+               if (! first)
+                       putchar(' ');
+               sharg(np, fp);
+               first = 0;
+       }
+       for (np = cmd->ncmd.redirect ; np ; np = np->nfile.next) {
+               if (! first)
+                       putchar(' ');
+               switch (np->nfile.type) {
+                       case NTO:       s = ">";  dftfd = 1; break;
+                       case NAPPEND:   s = ">>"; dftfd = 1; break;
+                       case NTOFD:     s = ">&"; dftfd = 1; break;
+                       case NCLOBBER:  s = ">|"; dftfd = 1; break;
+                       case NFROM:     s = "<";  dftfd = 0; break;
+                       case NFROMTO:   s = "<>"; dftfd = 0; break;
+                       case NFROMFD:   s = "<&"; dftfd = 0; break;
+                       case NHERE:     s = "<<"; dftfd = 0; break;
+                       case NXHERE:    s = "<<"; dftfd = 0; break;
+                       default:        s = "*error*"; dftfd = 0; break;
+               }
+               if (np->nfile.fd != dftfd)
+                       fprintf(fp, "%d", np->nfile.fd);
+               fputs(s, fp);
+               if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) {
+                       if (np->ndup.dupfd >= 0)
+                               fprintf(fp, "%d", np->ndup.dupfd);
+                       else
+                               fprintf(fp, "-");
+               } else if (np->nfile.type == NHERE) {
+                               fprintf(fp, "HERE");
+               } else if (np->nfile.type == NXHERE) {
+                               fprintf(fp, "XHERE");
+               } else {
+                       sharg(np->nfile.fname, fp);
+               }
+               first = 0;
+       }
+}
+
+
+
+static void
+sharg(union node *arg, FILE *fp)
+{
+       char *p;
+       struct nodelist *bqlist;
+       int subtype;
+
+       if (arg->type != NARG) {
+               printf("<node type %d>\n", arg->type);
+               fflush(stdout);
+               abort();
+       }
+       bqlist = arg->narg.backquote;
+       for (p = arg->narg.text ; *p ; p++) {
+               switch (*p) {
+               case CTLESC:
+                       putc(*++p, fp);
+                       break;
+               case CTLVAR:
+                       putc('$', fp);
+                       putc('{', fp);
+                       subtype = *++p;
+                       if (subtype == VSLENGTH)
+                               putc('#', fp);
+
+                       while (*p != '=')
+                               putc(*p++, fp);
+
+                       if (subtype & VSNUL)
+                               putc(':', fp);
+
+                       switch (subtype & VSTYPE) {
+                       case VSNORMAL:
+                               putc('}', fp);
+                               break;
+                       case VSMINUS:
+                               putc('-', fp);
+                               break;
+                       case VSPLUS:
+                               putc('+', fp);
+                               break;
+                       case VSQUESTION:
+                               putc('?', fp);
+                               break;
+                       case VSASSIGN:
+                               putc('=', fp);
+                               break;
+                       case VSTRIMLEFT:
+                               putc('#', fp);
+                               break;
+                       case VSTRIMLEFTMAX:
+                               putc('#', fp);
+                               putc('#', fp);
+                               break;
+                       case VSTRIMRIGHT:
+                               putc('%', fp);
+                               break;
+                       case VSTRIMRIGHTMAX:
+                               putc('%', fp);
+                               putc('%', fp);
+                               break;
+                       case VSLENGTH:
+                               break;
+                       default:
+                               printf("<subtype %d>", subtype);
+                       }
+                       break;
+               case CTLENDVAR:
+                    putc('}', fp);
+                    break;
+               case CTLBACKQ:
+               case CTLBACKQ|CTLQUOTE:
+                       putc('$', fp);
+                       putc('(', fp);
+                       shtree(bqlist->n, -1, NULL, fp);
+                       putc(')', fp);
+                       break;
+               default:
+                       putc(*p, fp);
+                       break;
+               }
+       }
+}
+
+
+static void
+indent(int amount, char *pfx, FILE *fp)
+{
+       int i;
+
+       for (i = 0 ; i < amount ; i++) {
+               if (pfx && i == amount - 1)
+                       fputs(pfx, fp);
+               putc('\t', fp);
+       }
+}
+
+
+/*
+ * Debugging stuff.
+ */
+
+
+FILE *tracefile;
+
+#if DEBUG >= 2
+int debug = 1;
+#else
+int debug = 0;
+#endif
+
+
+void
+trputc(int c)
+{
+       if (tracefile == NULL)
+               return;
+       putc(c, tracefile);
+       if (c == '\n')
+               fflush(tracefile);
+}
+
+
+void
+sh_trace(const char *fmt, ...)
+{
+       va_list va;
+       va_start(va, fmt);
+       if (tracefile != NULL) {
+               (void) vfprintf(tracefile, fmt, va);
+               if (strchr(fmt, '\n'))
+                       (void) fflush(tracefile);
+       }
+       va_end(va);
+}
+
+
+void
+trputs(const char *s)
+{
+       if (tracefile == NULL)
+               return;
+       fputs(s, tracefile);
+       if (strchr(s, '\n'))
+               fflush(tracefile);
+}
+
+
+static void
+trstring(char *s)
+{
+       char *p;
+       char c;
+
+       if (tracefile == NULL)
+               return;
+       putc('"', tracefile);
+       for (p = s ; *p ; p++) {
+               switch (*p) {
+               case '\n':  c = 'n';  goto backslash;
+               case '\t':  c = 't';  goto backslash;
+               case '\r':  c = 'r';  goto backslash;
+               case '"':  c = '"';  goto backslash;
+               case '\\':  c = '\\';  goto backslash;
+               case CTLESC:  c = 'e';  goto backslash;
+               case CTLVAR:  c = 'v';  goto backslash;
+               case CTLVAR+CTLQUOTE:  c = 'V';  goto backslash;
+               case CTLBACKQ:  c = 'q';  goto backslash;
+               case CTLBACKQ+CTLQUOTE:  c = 'Q';  goto backslash;
+backslash:       putc('\\', tracefile);
+                       putc(c, tracefile);
+                       break;
+               default:
+                       if (*p >= ' ' && *p <= '~')
+                               putc(*p, tracefile);
+                       else {
+                               putc('\\', tracefile);
+                               putc(*p >> 6 & 03, tracefile);
+                               putc(*p >> 3 & 07, tracefile);
+                               putc(*p & 07, tracefile);
+                       }
+                       break;
+               }
+       }
+       putc('"', tracefile);
+}
+
+
+void
+trargs(char **ap)
+{
+       if (tracefile == NULL)
+               return;
+       while (*ap) {
+               trstring(*ap++);
+               if (*ap)
+                       putc(' ', tracefile);
+               else
+                       putc('\n', tracefile);
+       }
+       fflush(tracefile);
+}
+
+
+void
+opentrace(void)
+{
+       char s[100];
+       int flags;
+
+       if (!debug)
+               return;
+#ifdef not_this_way
+       {
+               char *p;
+               if ((p = getenv("HOME")) == NULL) {
+                       if (geteuid() == 0)
+                               p = "/";
+                       else
+                               p = "/tmp";
+               }
+               strcpy(s, p);
+               strcat(s, "/trace");
+       }
+#else
+       strcpy(s, "./trace");
+#endif /* not_this_way */
+       if ((tracefile = fopen(s, "a")) == NULL) {
+               fprintf(stderr, "Can't open %s: %s\n", s, strerror(errno));
+               return;
+       }
+       if ((flags = fcntl(fileno(tracefile), F_GETFL, 0)) >= 0)
+               fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND);
+       fputs("\nTracing started.\n", tracefile);
+       fflush(tracefile);
+}
+#endif /* DEBUG */
diff --git a/sh/show.h b/sh/show.h
new file mode 100644 (file)
index 0000000..4528fb1
--- /dev/null
+++ b/sh/show.h
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (c) 1995
+ *      The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)show.h      1.1 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+void showtree(union node *);
+#ifdef DEBUG
+void sh_trace(const char *, ...) __printflike(1, 2);
+void trargs(char **);
+void trputc(int);
+void trputs(const char *);
+void opentrace(void);
+#endif
diff --git a/sh/tests/Makefile b/sh/tests/Makefile
new file mode 100644 (file)
index 0000000..c092962
--- /dev/null
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+.include <bsd.own.mk>
+
+TESTSDIR=      ${TESTSBASE}/bin/sh
+
+TESTS_SUBDIRS+=        builtins
+TESTS_SUBDIRS+=        errors
+TESTS_SUBDIRS+=        execution
+TESTS_SUBDIRS+=        expansion
+TESTS_SUBDIRS+=        parameters
+TESTS_SUBDIRS+=        parser
+TESTS_SUBDIRS+=        set-e
+
+.include <bsd.test.mk>
diff --git a/sh/tests/builtins/Makefile b/sh/tests/builtins/Makefile
new file mode 100644 (file)
index 0000000..ad39aac
--- /dev/null
@@ -0,0 +1,167 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+TESTSDIR=      ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH=  functional_test
+
+FILESDIR=      ${TESTSDIR}
+
+FILES=         alias.0 alias.0.stdout
+FILES+=                alias.1 alias.1.stderr
+FILES+=                alias3.0 alias3.0.stdout
+FILES+=                alias4.0
+FILES+=                break1.0
+FILES+=                break2.0 break2.0.stdout
+FILES+=                break3.0
+FILES+=                break4.4
+FILES+=                break5.4
+FILES+=                break6.0
+FILES+=                builtin1.0
+FILES+=                case1.0
+FILES+=                case2.0
+FILES+=                case3.0
+FILES+=                case4.0
+FILES+=                case5.0
+FILES+=                case6.0
+FILES+=                case7.0
+FILES+=                case8.0
+FILES+=                case9.0
+FILES+=                case10.0
+FILES+=                case11.0
+FILES+=                case12.0
+FILES+=                case13.0
+FILES+=                case14.0
+FILES+=                case15.0
+FILES+=                case16.0
+FILES+=                case17.0
+FILES+=                case18.0
+FILES+=                case19.0
+FILES+=                cd1.0
+FILES+=                cd2.0
+FILES+=                cd3.0
+FILES+=                cd4.0
+FILES+=                cd5.0
+FILES+=                cd6.0
+FILES+=                cd7.0
+FILES+=                cd8.0
+FILES+=                command1.0
+FILES+=                command2.0
+FILES+=                command3.0
+FILES+=                command3.0.stdout
+FILES+=                command4.0
+FILES+=                command5.0
+FILES+=                command5.0.stdout
+FILES+=                command6.0
+FILES+=                command6.0.stdout
+FILES+=                command7.0
+FILES+=                command8.0
+FILES+=                command9.0
+FILES+=                command10.0
+FILES+=                command11.0
+FILES+=                command12.0
+FILES+=                dot1.0
+FILES+=                dot2.0
+FILES+=                dot3.0
+FILES+=                dot4.0
+FILES+=                eval1.0
+FILES+=                eval2.0
+FILES+=                eval3.0
+FILES+=                eval4.0
+FILES+=                eval5.0
+FILES+=                eval6.0
+FILES+=                eval7.0
+FILES+=                eval8.7
+FILES+=                exec1.0
+FILES+=                exec2.0
+FILES+=                exit1.0
+FILES+=                exit2.8
+FILES+=                exit3.0
+FILES+=                export1.0
+FILES+=                fc1.0
+FILES+=                fc2.0
+FILES+=                for1.0
+FILES+=                for2.0
+FILES+=                for3.0
+FILES+=                getopts1.0 getopts1.0.stdout
+FILES+=                getopts2.0 getopts2.0.stdout
+FILES+=                getopts3.0
+FILES+=                getopts4.0
+FILES+=                getopts5.0
+FILES+=                getopts6.0
+FILES+=                getopts7.0
+FILES+=                getopts8.0 getopts8.0.stdout
+FILES+=                hash1.0 hash1.0.stdout
+FILES+=                hash2.0 hash2.0.stdout
+FILES+=                hash3.0 hash3.0.stdout
+FILES+=                hash4.0
+FILES+=                jobid1.0
+FILES+=                jobid2.0
+FILES+=                kill1.0 kill2.0
+FILES+=                lineno.0 lineno.0.stdout
+FILES+=                lineno2.0
+FILES+=                lineno3.0 lineno3.0.stdout
+FILES+=                local1.0
+FILES+=                local2.0
+FILES+=                local3.0
+FILES+=                local4.0
+.if ${MK_NLS} != "no"
+FILES+=                locale1.0
+.endif
+FILES+=                printf1.0
+FILES+=                printf2.0
+FILES+=                printf3.0
+FILES+=                printf4.0
+FILES+=                read1.0 read1.0.stdout
+FILES+=                read2.0
+FILES+=                read3.0 read3.0.stdout
+FILES+=                read4.0 read4.0.stdout
+FILES+=                read5.0
+FILES+=                read6.0
+FILES+=                read7.0
+FILES+=                return1.0
+FILES+=                return2.1
+FILES+=                return3.1
+FILES+=                return4.0
+FILES+=                return5.0
+FILES+=                return6.4
+FILES+=                return7.4
+FILES+=                return8.0
+FILES+=                set1.0
+FILES+=                set2.0
+FILES+=                trap1.0
+FILES+=                trap10.0
+FILES+=                trap11.0
+FILES+=                trap12.0
+FILES+=                trap13.0
+FILES+=                trap14.0
+FILES+=                trap15.0
+FILES+=                trap16.0
+FILES+=                trap2.0
+FILES+=                trap3.0
+FILES+=                trap4.0
+FILES+=                trap5.0
+FILES+=                trap6.0
+FILES+=                trap7.0
+FILES+=                trap8.0
+FILES+=                trap9.0
+FILES+=                type1.0 type1.0.stderr
+FILES+=                type2.0
+FILES+=                type3.0
+FILES+=                unalias.0
+FILES+=                var-assign.0
+FILES+=                var-assign2.0
+FILES+=                wait1.0
+FILES+=                wait2.0
+FILES+=                wait3.0
+FILES+=                wait4.0
+FILES+=                wait5.0
+FILES+=                wait6.0
+FILES+=                wait7.0
+FILES+=                wait8.0
+FILES+=                wait9.127
+FILES+=                wait10.0
+
+.include <bsd.test.mk>
diff --git a/sh/tests/builtins/alias.0 b/sh/tests/builtins/alias.0
new file mode 100644 (file)
index 0000000..d9b2796
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+set -e
+
+unalias -a
+alias foo=bar
+alias bar=
+alias quux="1 2 3"
+alias
+alias foo
diff --git a/sh/tests/builtins/alias.0.stdout b/sh/tests/builtins/alias.0.stdout
new file mode 100644 (file)
index 0000000..52efaf0
--- /dev/null
@@ -0,0 +1,4 @@
+bar=''
+foo=bar
+quux='1 2 3'
+foo=bar
diff --git a/sh/tests/builtins/alias.1 b/sh/tests/builtins/alias.1
new file mode 100644 (file)
index 0000000..31403dc
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+unalias -a
+alias foo
diff --git a/sh/tests/builtins/alias.1.stderr b/sh/tests/builtins/alias.1.stderr
new file mode 100644 (file)
index 0000000..c9f4011
--- /dev/null
@@ -0,0 +1 @@
+alias: foo: not found
diff --git a/sh/tests/builtins/alias3.0 b/sh/tests/builtins/alias3.0
new file mode 100644 (file)
index 0000000..fe65e31
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+set -e
+
+unalias -a
+alias foo=bar
+alias bar=
+alias quux="1 2 3"
+alias foo=bar
+alias bar=
+alias quux="1 2 3"
+alias
+alias foo
diff --git a/sh/tests/builtins/alias3.0.stdout b/sh/tests/builtins/alias3.0.stdout
new file mode 100644 (file)
index 0000000..52efaf0
--- /dev/null
@@ -0,0 +1,4 @@
+bar=''
+foo=bar
+quux='1 2 3'
+foo=bar
diff --git a/sh/tests/builtins/alias4.0 b/sh/tests/builtins/alias4.0
new file mode 100644 (file)
index 0000000..3d5efec
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+unalias -a
+alias --
diff --git a/sh/tests/builtins/break1.0 b/sh/tests/builtins/break1.0
new file mode 100644 (file)
index 0000000..ba0cbb4
--- /dev/null
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+if [ "$1" != nested ]; then
+       while :; do
+               set -- nested
+               . "$0"
+               echo bad2
+               exit 2
+       done
+       exit 0
+fi
+# To trigger the bug, the following commands must be at the top level,
+# with newlines in between.
+break
+echo bad1
+exit 1
diff --git a/sh/tests/builtins/break2.0 b/sh/tests/builtins/break2.0
new file mode 100644 (file)
index 0000000..ff52dd3
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+# It is not immediately obvious that this should work, and someone probably
+# relies on it.
+
+while :; do
+       trap 'break' USR1
+       kill -USR1 $$
+       echo bad
+       exit 1
+done
+echo good
diff --git a/sh/tests/builtins/break2.0.stdout b/sh/tests/builtins/break2.0.stdout
new file mode 100644 (file)
index 0000000..12799cc
--- /dev/null
@@ -0,0 +1 @@
+good
diff --git a/sh/tests/builtins/break3.0 b/sh/tests/builtins/break3.0
new file mode 100644 (file)
index 0000000..10a5ca8
--- /dev/null
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+# We accept this and people might rely on it.
+# However, various other shells do not accept it.
+
+f() {
+       break
+       echo bad1
+}
+
+while :; do
+       f
+       echo bad2
+       exit 2
+done
diff --git a/sh/tests/builtins/break4.4 b/sh/tests/builtins/break4.4
new file mode 100644 (file)
index 0000000..d52ff52
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+# Although this is not specified by POSIX, some configure scripts (gawk 4.1.0)
+# appear to depend on it.
+
+break
+exit 4
diff --git a/sh/tests/builtins/break5.4 b/sh/tests/builtins/break5.4
new file mode 100644 (file)
index 0000000..7df8e18
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+# Although this is not specified by POSIX, some configure scripts (gawk 4.1.0)
+# appear to depend on it.
+# In some uncommitted code, the subshell environment corrupted the outer
+# shell environment's state.
+
+(for i in a b c; do
+       exit 3
+done)
+break
+exit 4
diff --git a/sh/tests/builtins/break6.0 b/sh/tests/builtins/break6.0
new file mode 100644 (file)
index 0000000..09fc0d8
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+# Per POSIX, this need only work if LONG_MAX > 4294967295.
+
+while :; do
+       break 4294967296
+       echo bad
+       exit 3
+done
diff --git a/sh/tests/builtins/builtin1.0 b/sh/tests/builtins/builtin1.0
new file mode 100644 (file)
index 0000000..b608385
--- /dev/null
@@ -0,0 +1,31 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+builtin : || echo "Bad return code at $LINENO"
+builtin true || echo "Bad return code at $LINENO"
+builtin ls 2>/dev/null && echo "Bad return code at $LINENO"
+check '"$(builtin pwd)" = "$(pwd)"'
+check '-z "$(builtin :)"'
+check '-z "$(builtin true)"'
+check '-z "$( (builtin nosuchtool) 2>/dev/null)"'
+check '-z "$(builtin nosuchtool 2>/dev/null)"'
+check '-z "$(builtin nosuchtool 2>/dev/null; :)"'
+check '-z "$( (builtin ls) 2>/dev/null)"'
+check '-z "$(builtin ls 2>/dev/null)"'
+check '-z "$(builtin ls 2>/dev/null; :)"'
+check '-n "$( (builtin nosuchtool) 2>&1)"'
+check '-n "$(builtin nosuchtool 2>&1)"'
+check '-n "$(builtin nosuchtool 2>&1; :)"'
+check '-n "$( (builtin ls) 2>&1)"'
+check '-n "$(builtin ls 2>&1)"'
+check '-n "$(builtin ls 2>&1; :)"'
+
+exit $((failures > 0))
diff --git a/sh/tests/builtins/case1.0 b/sh/tests/builtins/case1.0
new file mode 100644 (file)
index 0000000..860fc67
--- /dev/null
@@ -0,0 +1,13 @@
+#$FreeBSD$
+f()
+{
+       false
+       case $1 in
+       foo) true ;;
+       bar) false ;;
+       esac
+}
+
+f foo || exit 1
+f bar && exit 1
+f quux || exit 1
diff --git a/sh/tests/builtins/case10.0 b/sh/tests/builtins/case10.0
new file mode 100644 (file)
index 0000000..a627b5c
--- /dev/null
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+case ! in
+[\!!]) ;;
+*) echo Failed at $LINENO ;;
+esac
+
+case ! in
+['!'!]) ;;
+*) echo Failed at $LINENO ;;
+esac
+
+case ! in
+["!"!]) ;;
+*) echo Failed at $LINENO ;;
+esac
diff --git a/sh/tests/builtins/case11.0 b/sh/tests/builtins/case11.0
new file mode 100644 (file)
index 0000000..0e66e11
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+false
+case x in
+*)
+esac
diff --git a/sh/tests/builtins/case12.0 b/sh/tests/builtins/case12.0
new file mode 100644 (file)
index 0000000..2a442ba
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+false
+case x in
+y)
+esac
diff --git a/sh/tests/builtins/case13.0 b/sh/tests/builtins/case13.0
new file mode 100644 (file)
index 0000000..78f4e9b
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+case ^ in
+[\^^]) ;;
+*) echo Failed at $LINENO ;;
+esac
+
+case s in
+[\^^]) echo Failed at $LINENO ;;
+[s\]]) ;;
+*) echo Failed at $LINENO ;;
+esac
diff --git a/sh/tests/builtins/case14.0 b/sh/tests/builtins/case14.0
new file mode 100644 (file)
index 0000000..0338e8a
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+case `false` in
+no) exit 3 ;;
+esac
diff --git a/sh/tests/builtins/case15.0 b/sh/tests/builtins/case15.0
new file mode 100644 (file)
index 0000000..09b0e11
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+case x in
+`false`) exit 3 ;;
+esac
diff --git a/sh/tests/builtins/case16.0 b/sh/tests/builtins/case16.0
new file mode 100644 (file)
index 0000000..2430302
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+f() { return 42; }
+f
+case x in
+x) [ $? = 42 ] ;;
+esac
diff --git a/sh/tests/builtins/case17.0 b/sh/tests/builtins/case17.0
new file mode 100644 (file)
index 0000000..ed1d25f
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+! case x in x) false ;& y) esac
diff --git a/sh/tests/builtins/case18.0 b/sh/tests/builtins/case18.0
new file mode 100644 (file)
index 0000000..470253f
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+case x$(false) in
+x)     ;&
+y)     [ $? != 0 ] ;;
+z)     false ;;
+esac
diff --git a/sh/tests/builtins/case19.0 b/sh/tests/builtins/case19.0
new file mode 100644 (file)
index 0000000..215066a
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+[ "`case x in
+x)     false ;&
+y)     ;&
+z)     echo $? ;;
+esac`" != 0 ]
diff --git a/sh/tests/builtins/case2.0 b/sh/tests/builtins/case2.0
new file mode 100644 (file)
index 0000000..e319148
--- /dev/null
@@ -0,0 +1,106 @@
+# Generated by ./test-fnmatch -s 1, do not edit.
+# $FreeBSD$
+failures=
+failed() { printf '%s\n' "Failed: $1 '$2' '$3'"; failures=x$failures; }
+testmatch() { eval "case \$2 in ''$1) ;; *) failed testmatch \"\$@\";; esac"; }
+testnomatch() { eval "case \$2 in ''$1) failed testnomatch \"\$@\";; esac"; }
+testmatch '' ''
+testmatch 'a' 'a'
+testnomatch 'a' 'b'
+testnomatch 'a' 'A'
+testmatch '*' 'a'
+testmatch '*' 'aa'
+testmatch '*a' 'a'
+testnomatch '*a' 'b'
+testnomatch '*a*' 'b'
+testmatch '*a*b*' 'ab'
+testmatch '*a*b*' 'qaqbq'
+testmatch '*a*bb*' 'qaqbqbbq'
+testmatch '*a*bc*' 'qaqbqbcq'
+testmatch '*a*bb*' 'qaqbqbb'
+testmatch '*a*bc*' 'qaqbqbc'
+testmatch '*a*bb' 'qaqbqbb'
+testmatch '*a*bc' 'qaqbqbc'
+testnomatch '*a*bb' 'qaqbqbbq'
+testnomatch '*a*bc' 'qaqbqbcq'
+testnomatch '*a*a*a*a*a*a*a*a*a*a*' 'aaaaaaaaa'
+testmatch '*a*a*a*a*a*a*a*a*a*a*' 'aaaaaaaaaa'
+testmatch '*a*a*a*a*a*a*a*a*a*a*' 'aaaaaaaaaaa'
+testnomatch '.*.*.*.*.*.*.*.*.*.*' '.........'
+testmatch '.*.*.*.*.*.*.*.*.*.*' '..........'
+testmatch '.*.*.*.*.*.*.*.*.*.*' '...........'
+testnomatch '*?*?*?*?*?*?*?*?*?*?*' '123456789'
+testnomatch '??????????*' '123456789'
+testnomatch '*??????????' '123456789'
+testmatch '*?*?*?*?*?*?*?*?*?*?*' '1234567890'
+testmatch '??????????*' '1234567890'
+testmatch '*??????????' '1234567890'
+testmatch '*?*?*?*?*?*?*?*?*?*?*' '12345678901'
+testmatch '??????????*' '12345678901'
+testmatch '*??????????' '12345678901'
+testmatch '[x]' 'x'
+testmatch '[*]' '*'
+testmatch '[?]' '?'
+testmatch '[' '['
+testmatch '[[]' '['
+testnomatch '[[]' 'x'
+testnomatch '[*]' ''
+testnomatch '[*]' 'x'
+testnomatch '[?]' 'x'
+testmatch '*[*]*' 'foo*foo'
+testnomatch '*[*]*' 'foo'
+testmatch '[0-9]' '0'
+testmatch '[0-9]' '5'
+testmatch '[0-9]' '9'
+testnomatch '[0-9]' '/'
+testnomatch '[0-9]' ':'
+testnomatch '[0-9]' '*'
+testnomatch '[!0-9]' '0'
+testnomatch '[!0-9]' '5'
+testnomatch '[!0-9]' '9'
+testmatch '[!0-9]' '/'
+testmatch '[!0-9]' ':'
+testmatch '[!0-9]' '*'
+testmatch '*[0-9]' 'a0'
+testmatch '*[0-9]' 'a5'
+testmatch '*[0-9]' 'a9'
+testnomatch '*[0-9]' 'a/'
+testnomatch '*[0-9]' 'a:'
+testnomatch '*[0-9]' 'a*'
+testnomatch '*[!0-9]' 'a0'
+testnomatch '*[!0-9]' 'a5'
+testnomatch '*[!0-9]' 'a9'
+testmatch '*[!0-9]' 'a/'
+testmatch '*[!0-9]' 'a:'
+testmatch '*[!0-9]' 'a*'
+testmatch '*[0-9]' 'a00'
+testmatch '*[0-9]' 'a55'
+testmatch '*[0-9]' 'a99'
+testmatch '*[0-9]' 'a0a0'
+testmatch '*[0-9]' 'a5a5'
+testmatch '*[0-9]' 'a9a9'
+testmatch '\*' '*'
+testmatch '\?' '?'
+testmatch '\[x]' '[x]'
+testmatch '\[' '['
+testmatch '\\' '\'
+testmatch '*\**' 'foo*foo'
+testnomatch '*\**' 'foo'
+testmatch '*\\*' 'foo\foo'
+testnomatch '*\\*' 'foo'
+testmatch '\(' '('
+testmatch '\a' 'a'
+testnomatch '\*' 'a'
+testnomatch '\?' 'a'
+testnomatch '\*' '\*'
+testnomatch '\?' '\?'
+testnomatch '\[x]' '\[x]'
+testnomatch '\[x]' '\x'
+testnomatch '\[' '\['
+testnomatch '\(' '\('
+testnomatch '\a' '\a'
+testmatch '.*' '.'
+testmatch '.*' '..'
+testmatch '.*' '.a'
+testmatch 'a*' 'a.'
+[ -z "$failures" ]
diff --git a/sh/tests/builtins/case3.0 b/sh/tests/builtins/case3.0
new file mode 100644 (file)
index 0000000..42e53d6
--- /dev/null
@@ -0,0 +1,95 @@
+# Generated by ./test-fnmatch -s 2, do not edit.
+# $FreeBSD$
+failures=
+failed() { printf '%s\n' "Failed: $1 '$2' '$3'"; failures=x$failures; }
+# We do not treat a backslash specially in this case,
+# but this is not the case in all shells.
+netestmatch() { case $2 in $1) ;; *) failed netestmatch "$@";; esac; }
+netestnomatch() { case $2 in $1) failed netestnomatch "$@";; esac; }
+netestmatch '' ''
+netestmatch 'a' 'a'
+netestnomatch 'a' 'b'
+netestnomatch 'a' 'A'
+netestmatch '*' 'a'
+netestmatch '*' 'aa'
+netestmatch '*a' 'a'
+netestnomatch '*a' 'b'
+netestnomatch '*a*' 'b'
+netestmatch '*a*b*' 'ab'
+netestmatch '*a*b*' 'qaqbq'
+netestmatch '*a*bb*' 'qaqbqbbq'
+netestmatch '*a*bc*' 'qaqbqbcq'
+netestmatch '*a*bb*' 'qaqbqbb'
+netestmatch '*a*bc*' 'qaqbqbc'
+netestmatch '*a*bb' 'qaqbqbb'
+netestmatch '*a*bc' 'qaqbqbc'
+netestnomatch '*a*bb' 'qaqbqbbq'
+netestnomatch '*a*bc' 'qaqbqbcq'
+netestnomatch '*a*a*a*a*a*a*a*a*a*a*' 'aaaaaaaaa'
+netestmatch '*a*a*a*a*a*a*a*a*a*a*' 'aaaaaaaaaa'
+netestmatch '*a*a*a*a*a*a*a*a*a*a*' 'aaaaaaaaaaa'
+netestnomatch '.*.*.*.*.*.*.*.*.*.*' '.........'
+netestmatch '.*.*.*.*.*.*.*.*.*.*' '..........'
+netestmatch '.*.*.*.*.*.*.*.*.*.*' '...........'
+netestnomatch '*?*?*?*?*?*?*?*?*?*?*' '123456789'
+netestnomatch '??????????*' '123456789'
+netestnomatch '*??????????' '123456789'
+netestmatch '*?*?*?*?*?*?*?*?*?*?*' '1234567890'
+netestmatch '??????????*' '1234567890'
+netestmatch '*??????????' '1234567890'
+netestmatch '*?*?*?*?*?*?*?*?*?*?*' '12345678901'
+netestmatch '??????????*' '12345678901'
+netestmatch '*??????????' '12345678901'
+netestmatch '[x]' 'x'
+netestmatch '[*]' '*'
+netestmatch '[?]' '?'
+netestmatch '[' '['
+netestmatch '[[]' '['
+netestnomatch '[[]' 'x'
+netestnomatch '[*]' ''
+netestnomatch '[*]' 'x'
+netestnomatch '[?]' 'x'
+netestmatch '*[*]*' 'foo*foo'
+netestnomatch '*[*]*' 'foo'
+netestmatch '[0-9]' '0'
+netestmatch '[0-9]' '5'
+netestmatch '[0-9]' '9'
+netestnomatch '[0-9]' '/'
+netestnomatch '[0-9]' ':'
+netestnomatch '[0-9]' '*'
+netestnomatch '[!0-9]' '0'
+netestnomatch '[!0-9]' '5'
+netestnomatch '[!0-9]' '9'
+netestmatch '[!0-9]' '/'
+netestmatch '[!0-9]' ':'
+netestmatch '[!0-9]' '*'
+netestmatch '*[0-9]' 'a0'
+netestmatch '*[0-9]' 'a5'
+netestmatch '*[0-9]' 'a9'
+netestnomatch '*[0-9]' 'a/'
+netestnomatch '*[0-9]' 'a:'
+netestnomatch '*[0-9]' 'a*'
+netestnomatch '*[!0-9]' 'a0'
+netestnomatch '*[!0-9]' 'a5'
+netestnomatch '*[!0-9]' 'a9'
+netestmatch '*[!0-9]' 'a/'
+netestmatch '*[!0-9]' 'a:'
+netestmatch '*[!0-9]' 'a*'
+netestmatch '*[0-9]' 'a00'
+netestmatch '*[0-9]' 'a55'
+netestmatch '*[0-9]' 'a99'
+netestmatch '*[0-9]' 'a0a0'
+netestmatch '*[0-9]' 'a5a5'
+netestmatch '*[0-9]' 'a9a9'
+netestmatch '\*' '\*'
+netestmatch '\?' '\?'
+netestmatch '\' '\'
+netestnomatch '\\' '\'
+netestmatch '\\' '\\'
+netestmatch '*\*' 'foo\foo'
+netestnomatch '*\*' 'foo'
+netestmatch '.*' '.'
+netestmatch '.*' '..'
+netestmatch '.*' '.a'
+netestmatch 'a*' 'a.'
+[ -z "$failures" ]
diff --git a/sh/tests/builtins/case4.0 b/sh/tests/builtins/case4.0
new file mode 100644 (file)
index 0000000..18219f5
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+set -- "*"
+case x in
+"$1") echo failed ;;
+esac
diff --git a/sh/tests/builtins/case5.0 b/sh/tests/builtins/case5.0
new file mode 100644 (file)
index 0000000..8c6db5a
--- /dev/null
@@ -0,0 +1,57 @@
+# $FreeBSD$
+
+unset LC_ALL
+LC_CTYPE=en_US.UTF-8
+export LC_CTYPE
+
+c1=e
+# a umlaut
+c2=$(printf '\303\244')
+# euro sign
+c3=$(printf '\342\202\254')
+# some sort of 't' outside BMP
+c4=$(printf '\360\235\225\245')
+
+ok=0
+case $c1$c2$c3$c4 in
+*) ok=1 ;;
+esac
+if [ $ok = 0 ]; then
+       echo wrong at $LINENO
+       exit 3
+fi
+
+case $c1$c2$c3$c4 in
+$c1$c2$c3$c4) ;;
+*) echo wrong at $LINENO ;;
+esac
+
+case $c1$c2$c3$c4 in
+"$c1$c2$c3$c4") ;;
+*) echo wrong at $LINENO ;;
+esac
+
+case $c1$c2$c3$c4 in
+????) ;;
+*) echo wrong at $LINENO ;;
+esac
+
+case $c1.$c2.$c3.$c4 in
+?.?.?.?) ;;
+*) echo wrong at $LINENO ;;
+esac
+
+case $c1$c2$c3$c4 in
+[!a][!b][!c][!d]) ;;
+*) echo wrong at $LINENO ;;
+esac
+
+case $c1$c2$c3$c4 in
+[$c1][$c2][$c3][$c4]) ;;
+*) echo wrong at $LINENO ;;
+esac
+
+case $c1$c2$c3$c4 in
+["$c1"]["$c2"]["$c3"]["$c4"]) ;;
+*) echo wrong at $LINENO ;;
+esac
diff --git a/sh/tests/builtins/case6.0 b/sh/tests/builtins/case6.0
new file mode 100644 (file)
index 0000000..8d79183
--- /dev/null
@@ -0,0 +1,52 @@
+# $FreeBSD$
+
+unset LC_ALL
+LC_CTYPE=de_DE.ISO8859-1
+export LC_CTYPE
+
+c1=e
+# o umlaut
+c2=$(printf '\366')
+# non-break space
+c3=$(printf '\240')
+c4=$(printf '\240')
+# $c2$c3$c4 form one utf-8 character
+
+ok=0
+case $c1$c2$c3$c4 in
+*) ok=1 ;;
+esac
+if [ $ok = 0 ]; then
+       echo wrong at $LINENO
+       exit 3
+fi
+
+case $c1$c2$c3$c4 in
+$c1$c2$c3$c4) ;;
+*) echo wrong at $LINENO ;;
+esac
+
+case $c1$c2$c3$c4 in
+"$c1$c2$c3$c4") ;;
+*) echo wrong at $LINENO ;;
+esac
+
+case $c1$c2$c3$c4 in
+????) ;;
+*) echo wrong at $LINENO ;;
+esac
+
+case $c1$c2$c3$c4 in
+[!$c2][!b][!c][!d]) ;;
+*) echo wrong at $LINENO ;;
+esac
+
+case $c1$c2$c3$c4 in
+[$c1][$c2][$c3][$c4]) ;;
+*) echo wrong at $LINENO ;;
+esac
+
+case $c1$c2$c3$c4 in
+["$c1"]["$c2"]["$c3"]["$c4"]) ;;
+*) echo wrong at $LINENO ;;
+esac
diff --git a/sh/tests/builtins/case7.0 b/sh/tests/builtins/case7.0
new file mode 100644 (file)
index 0000000..96b9de6
--- /dev/null
@@ -0,0 +1,24 @@
+# $FreeBSD$
+
+# Character ranges in a locale other than the POSIX locale, not specified
+# by POSIX.
+
+unset LC_ALL
+LC_CTYPE=de_DE.ISO8859-1
+export LC_CTYPE
+LC_COLLATE=de_DE.ISO8859-1
+export LC_COLLATE
+
+c1=e
+# o umlaut
+c2=$(printf '\366')
+
+case $c1$c2 in
+[a-z][a-z]) ;;
+*) echo wrong at $LINENO ;;
+esac
+
+case $c1$c2 in
+[a-f][n-p]) ;;
+*) echo wrong at $LINENO ;;
+esac
diff --git a/sh/tests/builtins/case8.0 b/sh/tests/builtins/case8.0
new file mode 100644 (file)
index 0000000..8d9f8b6
--- /dev/null
@@ -0,0 +1,32 @@
+# $FreeBSD$
+
+case aZ_ in
+[[:alpha:]_][[:upper:]_][[:alpha:]_]) ;;
+*) echo Failed at $LINENO ;;
+esac
+
+case ' ' in
+[[:alpha:][:digit:]]) echo Failed at $LINENO ;;
+[![:alpha:][:digit:]]) ;;
+*) echo Failed at $LINENO ;;
+esac
+
+case '.X.' in
+*[[:lower:]]*) echo Failed at $LINENO ;;
+*[[:upper:]]*) ;;
+*) echo Failed at $LINENO ;;
+esac
+
+case ' ' in
+[![:print:]]) echo Failed at $LINENO ;;
+[![:alnum:][:punct:]]) ;;
+*) echo Failed at $LINENO ;;
+esac
+
+case '
+' in
+[[:print:]]) echo Failed at $LINENO ;;
+['
+'[:digit:]]) ;;
+*) echo Failed at $LINENO ;;
+esac
diff --git a/sh/tests/builtins/case9.0 b/sh/tests/builtins/case9.0
new file mode 100644 (file)
index 0000000..476caec
--- /dev/null
@@ -0,0 +1,39 @@
+# $FreeBSD$
+
+errors=0
+
+f() {
+       result=
+       case $1 in
+       a) result=${result}a ;;
+       b) result=${result}b ;&
+       c) result=${result}c ;&
+       d) result=${result}d ;;
+       e) result=${result}e ;&
+       esac
+}
+
+check() {
+       f "$1"
+       if [ "$result" != "$2" ]; then
+               printf "For %s, expected %s got %s\n" "$1" "$2" "$result"
+               errors=$((errors + 1))
+       fi
+}
+
+check '' ''
+check a a
+check b bcd
+check c cd
+check d d
+check e e
+
+if ! (case 1 in
+       1) false ;&
+       2) true ;;
+esac) then
+       echo "Subshell bad"
+       errors=$((errors + 1))
+fi
+
+exit $((errors != 0))
diff --git a/sh/tests/builtins/cd1.0 b/sh/tests/builtins/cd1.0
new file mode 100644 (file)
index 0000000..bc5108e
--- /dev/null
@@ -0,0 +1,30 @@
+# $FreeBSD$
+set -e
+
+P=${TMPDIR:-/tmp}
+cd $P
+T=$(mktemp -d sh-test.XXXXXX)
+
+chmod 0 $T
+if [ `id -u` -ne 0 ]; then
+       # Root can always cd, regardless of directory permissions.
+       cd -L $T 2>/dev/null && exit 1
+       [ "$PWD" = "$P" ]
+       [ "$(pwd)" = "$P" ]
+       cd -P $T 2>/dev/null && exit 1
+       [ "$PWD" = "$P" ]
+       [ "$(pwd)" = "$P" ]
+fi
+
+chmod 755 $T
+cd $T
+mkdir -p 1/2/3
+ln -s 1/2 link1
+ln -s 2/3 1/link2
+(cd -L 1/../1 && [ "$(pwd -L)" = "$P/$T/1" ])
+(cd -L link1 && [ "$(pwd -L)" = "$P/$T/link1" ])
+(cd -L link1 && [ "$(pwd -P)" = "$P/$T/1/2" ])
+(cd -P link1 && [ "$(pwd -L)" = "$P/$T/1/2" ])
+(cd -P link1 && [ "$(pwd -P)" = "$P/$T/1/2" ])
+
+rm -rf ${P}/${T}
diff --git a/sh/tests/builtins/cd2.0 b/sh/tests/builtins/cd2.0
new file mode 100644 (file)
index 0000000..f2b6416
--- /dev/null
@@ -0,0 +1,16 @@
+# $FreeBSD$
+set -e
+
+L=$(getconf PATH_MAX / 2>/dev/null) || L=4096
+[ "$L" -lt 100000 ] 2>/dev/null || L=4096
+L=$((L+100))
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX)
+trap 'rm -rf ${T}' 0
+cd $T
+D=$T
+while [ ${#D} -lt $L ]; do
+       mkdir veryverylongdirectoryname
+       cd veryverylongdirectoryname
+       D=$D/veryverylongdirectoryname
+done
+[ $(pwd | wc -c) -eq $((${#D} + 1)) ] # +\n
diff --git a/sh/tests/builtins/cd3.0 b/sh/tests/builtins/cd3.0
new file mode 100644 (file)
index 0000000..7729c54
--- /dev/null
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+# If fully successful, cd -Pe must be like cd -P.
+
+set -e
+
+cd "${TMPDIR:-/tmp}"
+cd -Pe /
+[ "$PWD" = / ]
+[ "$(pwd)" = / ]
+cd "${TMPDIR:-/tmp}"
+cd -eP /
+[ "$PWD" = / ]
+[ "$(pwd)" = / ]
+
+set +e
+
+# If cd -Pe cannot chdir, the exit status must be greater than 1.
+
+v=$( (cd -Pe /var/empty/nonexistent) 2>&1 >/dev/null)
+[ $? -gt 1 ] && [ -n "$v" ]
diff --git a/sh/tests/builtins/cd4.0 b/sh/tests/builtins/cd4.0
new file mode 100644 (file)
index 0000000..df3a9a4
--- /dev/null
@@ -0,0 +1,38 @@
+# $FreeBSD$
+
+# This test assumes that whatever mechanism cd -P uses to determine the
+# pathname to the current directory if it is longer than PATH_MAX requires
+# read permission on all parent directories. It also works if this
+# requirement always applies.
+
+set -e
+L=$(getconf PATH_MAX / 2>/dev/null) || L=4096
+[ "$L" -lt 100000 ] 2>/dev/null || L=4096
+L=$((L+100))
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX)
+trap 'chmod u+r ${T}; rm -rf ${T}' 0
+cd -Pe $T
+D=$(pwd)
+chmod u-r "$D"
+if [ -r "$D" ]; then
+       # Running as root, cannot test.
+       exit 0
+fi
+set +e
+while [ ${#D} -lt $L ]; do
+       mkdir veryverylongdirectoryname || exit
+       cd -Pe veryverylongdirectoryname 2>/dev/null
+       r=$?
+       [ $r -gt 1 ] && exit $r
+       if [ $r -eq 1 ]; then
+               # Verify that the directory was changed correctly.
+               cd -Pe .. || exit
+               [ "$(pwd)" = "$D" ] || exit
+               # Verify that omitting -e results in success.
+               cd -P veryverylongdirectoryname 2>/dev/null || exit
+               exit 0
+       fi
+       D=$D/veryverylongdirectoryname
+done
+echo "cd -Pe never returned 1"
+exit 0
diff --git a/sh/tests/builtins/cd5.0 b/sh/tests/builtins/cd5.0
new file mode 100644 (file)
index 0000000..3dff604
--- /dev/null
@@ -0,0 +1,23 @@
+# $FreeBSD$
+
+set -e
+T=$(mktemp -d "${TMPDIR:-/tmp}/sh-test.XXXXXX")
+trap 'rm -rf "$T"' 0
+
+cd -P "$T"
+D=$(pwd)
+
+mkdir a a/1 b b/1 b/2
+
+CDPATH=$D/a:
+# Basic test.
+cd 1 >/dev/null
+[ "$(pwd)" = "$D/a/1" ]
+# Test that the current directory is not checked before CDPATH.
+cd "$D/b"
+cd 1 >/dev/null
+[ "$(pwd)" = "$D/a/1" ]
+# Test not using a CDPATH entry.
+cd "$D/b"
+cd 2
+[ "$(pwd)" = "$D/b/2" ]
diff --git a/sh/tests/builtins/cd6.0 b/sh/tests/builtins/cd6.0
new file mode 100644 (file)
index 0000000..083a061
--- /dev/null
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+set -e
+cd -P /bin
+d=$PWD
+CDPATH=/:
+cd -P .
+[ "$d" = "$PWD" ]
+cd -P ./
+[ "$d" = "$PWD" ]
diff --git a/sh/tests/builtins/cd7.0 b/sh/tests/builtins/cd7.0
new file mode 100644 (file)
index 0000000..9adda86
--- /dev/null
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+set -e
+cd /usr/bin
+[ "$PWD" = /usr/bin ]
+CDPATH=/:
+cd .
+[ "$PWD" = /usr/bin ]
+cd ./
+[ "$PWD" = /usr/bin ]
+cd ..
+[ "$PWD" = /usr ]
+cd /usr/bin
+cd ../
+[ "$PWD" = /usr ]
diff --git a/sh/tests/builtins/cd8.0 b/sh/tests/builtins/cd8.0
new file mode 100644 (file)
index 0000000..a68f77f
--- /dev/null
@@ -0,0 +1,26 @@
+# $FreeBSD$
+
+# The exact wording of the error message is not standardized, but giving
+# a description of the errno is useful.
+
+LC_ALL=C
+export LC_ALL
+r=0
+
+t() {
+       exec 3>&1
+       errmsg=`cd "$1" 2>&1 >&3 3>&-`
+       exec 3>&-
+       case $errmsg in
+       *[Nn]ot\ a\ directory*)
+               ;;
+       *)
+               printf "Wrong error message for %s: %s\n" "$1" "$errmsg"
+               r=3
+               ;;
+       esac
+}
+
+t /dev/tty
+t /dev/tty/x
+exit $r
diff --git a/sh/tests/builtins/command1.0 b/sh/tests/builtins/command1.0
new file mode 100644 (file)
index 0000000..fd0afdf
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+true() {
+       false
+}
+command true
diff --git a/sh/tests/builtins/command10.0 b/sh/tests/builtins/command10.0
new file mode 100644 (file)
index 0000000..2c1adf1
--- /dev/null
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+check '"$(f() { shift x; }; { command eval f 2>/dev/null; } >/dev/null; echo hi)" = hi'
+
+exit $((failures > 0))
diff --git a/sh/tests/builtins/command11.0 b/sh/tests/builtins/command11.0
new file mode 100644 (file)
index 0000000..10c8647
--- /dev/null
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+check '"$({ command eval \{ shift x\; \} 2\>/dev/null; } >/dev/null; echo hi)" = hi'
+
+exit $((failures > 0))
diff --git a/sh/tests/builtins/command12.0 b/sh/tests/builtins/command12.0
new file mode 100644 (file)
index 0000000..f981db3
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+alias aa=echo\ \'\"\'
+cmd=$(command -v aa)
+alias aa=echo\ bad
+eval "$cmd"
+[ "$(eval aa)" = \" ]
diff --git a/sh/tests/builtins/command2.0 b/sh/tests/builtins/command2.0
new file mode 100644 (file)
index 0000000..ff7b5f2
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+PATH=
+command -p cat < /dev/null
diff --git a/sh/tests/builtins/command3.0 b/sh/tests/builtins/command3.0
new file mode 100644 (file)
index 0000000..9d4ae89
--- /dev/null
@@ -0,0 +1,14 @@
+# $FreeBSD$
+command -v ls
+command -v true
+command -v /bin/ls
+
+fun() {
+       :
+}
+command -v fun
+command -v break
+command -v if
+
+alias foo=bar
+command -v foo
diff --git a/sh/tests/builtins/command3.0.stdout b/sh/tests/builtins/command3.0.stdout
new file mode 100644 (file)
index 0000000..67b8691
--- /dev/null
@@ -0,0 +1,7 @@
+/bin/ls
+true
+/bin/ls
+fun
+break
+if
+alias foo=bar
diff --git a/sh/tests/builtins/command4.0 b/sh/tests/builtins/command4.0
new file mode 100644 (file)
index 0000000..3e49613
--- /dev/null
@@ -0,0 +1,2 @@
+# $FreeBSD$
+! command -v nonexisting
diff --git a/sh/tests/builtins/command5.0 b/sh/tests/builtins/command5.0
new file mode 100644 (file)
index 0000000..13b3fe1
--- /dev/null
@@ -0,0 +1,15 @@
+# $FreeBSD$
+command -V ls
+command -V true
+command -V /bin/ls
+
+fun() {
+       :
+}
+command -V fun
+command -V break
+command -V if
+command -V {
+
+alias foo=bar
+command -V foo
diff --git a/sh/tests/builtins/command5.0.stdout b/sh/tests/builtins/command5.0.stdout
new file mode 100644 (file)
index 0000000..523f7b2
--- /dev/null
@@ -0,0 +1,8 @@
+ls is /bin/ls
+true is a shell builtin
+/bin/ls is /bin/ls
+fun is a shell function
+break is a special shell builtin
+if is a shell keyword
+{ is a shell keyword
+foo is an alias for bar
diff --git a/sh/tests/builtins/command6.0 b/sh/tests/builtins/command6.0
new file mode 100644 (file)
index 0000000..5b63bfe
--- /dev/null
@@ -0,0 +1,22 @@
+# $FreeBSD$
+PATH=/var/empty
+case $(command -pV ls) in
+*/var/empty/ls*)
+       echo "Failed: \$(command -pV ls) should not match */var/empty/ls*" ;;
+"ls is"*" "/*/ls) ;;
+*)
+       echo "Failed: \$(command -pV ls) match \"ls is\"*\" \"/*/ls" ;;
+esac
+command -pV true
+command -pV /bin/ls
+
+fun() {
+       :
+}
+command -pV fun
+command -pV break
+command -pV if
+command -pV {
+
+alias foo=bar
+command -pV foo
diff --git a/sh/tests/builtins/command6.0.stdout b/sh/tests/builtins/command6.0.stdout
new file mode 100644 (file)
index 0000000..3180207
--- /dev/null
@@ -0,0 +1,7 @@
+true is a shell builtin
+/bin/ls is /bin/ls
+fun is a shell function
+break is a special shell builtin
+if is a shell keyword
+{ is a shell keyword
+foo is an alias for bar
diff --git a/sh/tests/builtins/command7.0 b/sh/tests/builtins/command7.0
new file mode 100644 (file)
index 0000000..fc652f2
--- /dev/null
@@ -0,0 +1,34 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+check '"$(PATH=/libexec command -V ld-elf.so.1)" = "ld-elf.so.1 is /libexec/ld-elf.so.1"'
+check '"$(PATH=/libexec command -V ld-elf.so.1; :)" = "ld-elf.so.1 is /libexec/ld-elf.so.1"'
+check '"$(PATH=/libexec command -pv ld-elf.so.1)" = ""'
+check '"$(PATH=/libexec command -pv ld-elf.so.1; :)" = ""'
+
+PATH=/libexec:$PATH
+
+check '"$(command -V ld-elf.so.1)" = "ld-elf.so.1 is /libexec/ld-elf.so.1"'
+check '"$(command -V ld-elf.so.1; :)" = "ld-elf.so.1 is /libexec/ld-elf.so.1"'
+check '"$(command -pv ld-elf.so.1)" = ""'
+check '"$(command -pv ld-elf.so.1; :)" = ""'
+
+PATH=/libexec
+
+check '"$(command -v ls)" = ""'
+case $(command -pv ls) in
+/*/ls) ;;
+*)
+       echo "Failed: \$(command -pv ls) match /*/ls"
+       : $((failures += 1)) ;;
+esac
+
+exit $((failures > 0))
diff --git a/sh/tests/builtins/command8.0 b/sh/tests/builtins/command8.0
new file mode 100644 (file)
index 0000000..9e3a2b6
--- /dev/null
@@ -0,0 +1,45 @@
+# $FreeBSD$
+IFS=,
+
+SPECIAL="break,\
+       :,\
+       continue,\
+       . /dev/null,\
+       eval,\
+       exec,\
+       export -p,\
+       readonly -p,\
+       set,\
+       shift 0,\
+       times,\
+       trap,\
+       unset foo"
+
+set -e
+
+# Check that special builtins can be executed via "command".
+
+set -- ${SPECIAL}
+for cmd in "$@"
+do
+       ${SH} -c "v=:; while \$v; do v=false; command ${cmd}; done" >/dev/null
+done
+
+while :; do
+       command break
+       echo Error on line $LINENO
+done
+
+set p q r
+command shift 2
+if [ $# -ne 1 ]; then
+       echo Error on line $LINENO
+fi
+
+(
+       command exec >/dev/null
+       echo Error on line $LINENO
+)
+
+set +e
+! command shift 2 2>/dev/null
diff --git a/sh/tests/builtins/command9.0 b/sh/tests/builtins/command9.0
new file mode 100644 (file)
index 0000000..212e52a
--- /dev/null
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+check '"$({ command eval shift x 2>/dev/null; } >/dev/null; echo hi)" = hi'
+
+exit $((failures > 0))
diff --git a/sh/tests/builtins/dot1.0 b/sh/tests/builtins/dot1.0
new file mode 100644 (file)
index 0000000..43eab0d
--- /dev/null
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+failures=
+failure() {
+       echo "Error at line $1" >&2
+       failures=x$failures
+}
+
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) || exit
+trap 'rm -rf $T' 0
+cd $T || exit 3
+unset x
+echo 'x=2' >testscript
+. ./testscript
+[ "$x" = 2 ] || failure $LINENO
+cd / || exit 3
+x=1
+PATH=$T:$PATH . testscript
+[ "$x" = 2 ] || failure $LINENO
+
+test -z "$failures"
diff --git a/sh/tests/builtins/dot2.0 b/sh/tests/builtins/dot2.0
new file mode 100644 (file)
index 0000000..ed6379b
--- /dev/null
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+failures=
+failure() {
+       echo "Error at line $1" >&2
+       failures=x$failures
+}
+
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) || exit
+trap 'rm -rf $T' 0
+cd $T || exit 3
+unset x
+echo 'x=2' >testscript
+. -- ./testscript
+[ "$x" = 2 ] || failure $LINENO
+cd / || exit 3
+x=1
+PATH=$T:$PATH . -- testscript
+[ "$x" = 2 ] || failure $LINENO
+
+test -z "$failures"
diff --git a/sh/tests/builtins/dot3.0 b/sh/tests/builtins/dot3.0
new file mode 100644 (file)
index 0000000..b337f0f
--- /dev/null
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+# . should return 0 if no command was executed.
+
+if false; then
+       exit 3
+else
+       . /dev/null
+       exit $?
+fi
diff --git a/sh/tests/builtins/dot4.0 b/sh/tests/builtins/dot4.0
new file mode 100644 (file)
index 0000000..b898131
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+v=abcd
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+r=$( (
+       trap 'exit 0' 0
+       . "$v"
+) 2>&1 >/dev/null) && [ -n "$r" ]
diff --git a/sh/tests/builtins/eval1.0 b/sh/tests/builtins/eval1.0
new file mode 100644 (file)
index 0000000..04606a4
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+set -e
+
+eval
+eval "" ""
+eval "true"
+! eval "false
+
+"
diff --git a/sh/tests/builtins/eval2.0 b/sh/tests/builtins/eval2.0
new file mode 100644 (file)
index 0000000..bf06b6e
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+eval '
+false
+
+' && exit 1
+exit 0
diff --git a/sh/tests/builtins/eval3.0 b/sh/tests/builtins/eval3.0
new file mode 100644 (file)
index 0000000..dfb8357
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+eval 'false;' && exit 1
+eval 'true;' || exit 1
+eval 'false;
+' && exit 1
+eval 'true;
+' || exit 1
+exit 0
diff --git a/sh/tests/builtins/eval4.0 b/sh/tests/builtins/eval4.0
new file mode 100644 (file)
index 0000000..67da2f5
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+# eval should preserve $? from command substitutions when starting
+# the parsed command.
+[ $(eval 'echo $?' $(false)) = 1 ]
diff --git a/sh/tests/builtins/eval5.0 b/sh/tests/builtins/eval5.0
new file mode 100644 (file)
index 0000000..3e86de9
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+# eval should return 0 if no command was executed.
+eval $(false)
diff --git a/sh/tests/builtins/eval6.0 b/sh/tests/builtins/eval6.0
new file mode 100644 (file)
index 0000000..6752bb6
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+# eval should preserve $? from command substitutions when starting
+# the parsed command.
+[ $(false; eval 'echo $?' $(:)) = 0 ]
diff --git a/sh/tests/builtins/eval7.0 b/sh/tests/builtins/eval7.0
new file mode 100644 (file)
index 0000000..a309c91
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+# Assumes that break can break out of a loop outside eval.
+
+while :; do
+       eval "break
+echo bad1"
+       echo bad2
+       exit 3
+done
diff --git a/sh/tests/builtins/eval8.7 b/sh/tests/builtins/eval8.7
new file mode 100644 (file)
index 0000000..af6064c
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+f() {
+       eval "return 7
+echo bad2"
+}
+f
diff --git a/sh/tests/builtins/exec1.0 b/sh/tests/builtins/exec1.0
new file mode 100644 (file)
index 0000000..dd30a4c
--- /dev/null
@@ -0,0 +1,25 @@
+# $FreeBSD$
+
+failures=
+failure() {
+       echo "Error at line $1" >&2
+       failures=x$failures
+}
+
+(
+       exec >/dev/null
+       echo bad
+)
+[ $? = 0 ] || failure $LINENO
+(
+       exec ${SH} -c 'exit 42'
+       echo bad
+)
+[ $? = 42 ] || failure $LINENO
+(
+       exec /var/empty/nosuch
+       echo bad
+) 2>/dev/null
+[ $? = 127 ] || failure $LINENO
+
+test -z "$failures"
diff --git a/sh/tests/builtins/exec2.0 b/sh/tests/builtins/exec2.0
new file mode 100644 (file)
index 0000000..3dcb6c4
--- /dev/null
@@ -0,0 +1,25 @@
+# $FreeBSD$
+
+failures=
+failure() {
+       echo "Error at line $1" >&2
+       failures=x$failures
+}
+
+(
+       exec -- >/dev/null
+       echo bad
+)
+[ $? = 0 ] || failure $LINENO
+(
+       exec -- ${SH} -c 'exit 42'
+       echo bad
+)
+[ $? = 42 ] || failure $LINENO
+(
+       exec -- /var/empty/nosuch
+       echo bad
+) 2>/dev/null
+[ $? = 127 ] || failure $LINENO
+
+test -z "$failures"
diff --git a/sh/tests/builtins/exit1.0 b/sh/tests/builtins/exit1.0
new file mode 100644 (file)
index 0000000..496d448
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+# exit with an argument should overwrite the exit status in an EXIT trap.
+
+trap 'true; exit $?' 0
+false
diff --git a/sh/tests/builtins/exit2.8 b/sh/tests/builtins/exit2.8
new file mode 100644 (file)
index 0000000..124c32e
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+# exit without arguments is the same as exit $? outside a trap.
+
+trap 'true; true' 0
+(exit 8)
+exit
diff --git a/sh/tests/builtins/exit3.0 b/sh/tests/builtins/exit3.0
new file mode 100644 (file)
index 0000000..80655ac
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+# exit without arguments differs from exit $? in an EXIT trap.
+
+trap 'false; exit' 0
diff --git a/sh/tests/builtins/export1.0 b/sh/tests/builtins/export1.0
new file mode 100644 (file)
index 0000000..7b08c9d
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+env @badness=1 ${SH} -c 'v=`export -p`; eval "$v"'
diff --git a/sh/tests/builtins/fc1.0 b/sh/tests/builtins/fc1.0
new file mode 100644 (file)
index 0000000..ab7a387
--- /dev/null
@@ -0,0 +1,27 @@
+# $FreeBSD$
+set -e
+trap 'echo Broken pipe -- test failed' PIPE
+
+P=${TMPDIR:-/tmp}
+cd $P
+T=$(mktemp -d sh-test.XXXXXX)
+cd $T
+
+mkfifo input output error
+HISTFILE=/dev/null ${SH} +m -i <input >output 2>error &
+{
+       # Syntax error
+       echo ')' >&3
+       # Read error message, shell will read new input now
+       read dummy <&5
+       # Execute bad command again
+       echo 'fc -e true' >&3
+       # Verify that the shell is still running
+       echo 'echo continued' >&3 || rc=3
+       echo 'exit' >&3 || rc=3
+       read line <&4 && [ "$line" = continued ] && : ${rc:=0}
+} 3>input 4<output 5<error
+
+rm input output error
+rmdir ${P}/${T}
+exit ${rc:-3}
diff --git a/sh/tests/builtins/fc2.0 b/sh/tests/builtins/fc2.0
new file mode 100644 (file)
index 0000000..7eb92ac
--- /dev/null
@@ -0,0 +1,34 @@
+# $FreeBSD$
+set -e
+trap 'echo Broken pipe -- test failed' PIPE
+
+P=${TMPDIR:-/tmp}
+cd $P
+T=$(mktemp -d sh-test.XXXXXX)
+cd $T
+
+mkfifo input output error
+HISTFILE=/dev/null ${SH} +m -i <input >output 2>error &
+exec 3>input
+{
+       # Command not found, containing slash
+       echo '/var/empty/nonexistent' >&3
+       # Read error message, shell will read new input now
+       read dummy <&5
+       # Execute bad command again
+       echo 'fc -e true; echo continued' >&3
+       read dummy <&5
+       read line <&4 && [ "$line" = continued ] && : ${rc:=0}
+       exec 3>&-
+       # Old sh duplicates itself after the fc, producing another line
+       # of output.
+       if read line <&4; then
+               echo "Extraneous output: $line"
+               rc=1
+       fi
+} 4<output 5<error
+exec 3>&-
+
+rm input output error
+rmdir ${P}/${T}
+exit ${rc:-3}
diff --git a/sh/tests/builtins/for1.0 b/sh/tests/builtins/for1.0
new file mode 100644 (file)
index 0000000..cd55e2c
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+false
+for i in `false`; do exit 3; done
diff --git a/sh/tests/builtins/for2.0 b/sh/tests/builtins/for2.0
new file mode 100644 (file)
index 0000000..48c22ce
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+r=x
+f() { return 42; }
+f
+for i in x; do
+       r=$?
+done
+[ "$r" = 42 ]
diff --git a/sh/tests/builtins/for3.0 b/sh/tests/builtins/for3.0
new file mode 100644 (file)
index 0000000..cc37238
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+r=x
+f() { return 42; }
+for i in x`f`; do
+       r=$?
+done
+[ "$r" = 42 ]
diff --git a/sh/tests/builtins/getopts1.0 b/sh/tests/builtins/getopts1.0
new file mode 100644 (file)
index 0000000..64763bc
--- /dev/null
@@ -0,0 +1,25 @@
+# $FreeBSD$
+
+printf -- '-1-\n'
+set -- -abc
+getopts "ab:" OPTION
+echo ${OPTION}
+
+# In this case 'getopts' should realize that we have not provided the
+# required argument for "-b".
+# Note that Solaris 10's (UNIX 03) /usr/xpg4/bin/sh, /bin/sh, and /bin/ksh;
+# ksh93 20090505; pdksh 5.2.14p2; mksh R39c; bash 4.1 PL7; and zsh 4.3.10.
+# all recognize that "b" is missing its argument on the *first* iteration
+# of 'getopts' and do not produce the "a" in $OPTION.
+printf -- '-2-\n'
+set -- -ab
+getopts "ab:" OPTION
+echo ${OPTION}
+getopts "ab:" OPTION 3>&2 2>&1 >&3 3>&-
+echo ${OPTION}
+
+# The 'shift' is aimed at causing an error.
+printf -- '-3-\n'
+shift 1
+getopts "ab:" OPTION
+echo ${OPTION}
diff --git a/sh/tests/builtins/getopts1.0.stdout b/sh/tests/builtins/getopts1.0.stdout
new file mode 100644 (file)
index 0000000..a0a347e
--- /dev/null
@@ -0,0 +1,8 @@
+-1-
+a
+-2-
+a
+No arg for -b option
+?
+-3-
+?
diff --git a/sh/tests/builtins/getopts2.0 b/sh/tests/builtins/getopts2.0
new file mode 100644 (file)
index 0000000..5a5dbe2
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+set - -ax
+getopts ax option
+set -C
+getopts ax option
+echo $option
diff --git a/sh/tests/builtins/getopts2.0.stdout b/sh/tests/builtins/getopts2.0.stdout
new file mode 100644 (file)
index 0000000..587be6b
--- /dev/null
@@ -0,0 +1 @@
+x
diff --git a/sh/tests/builtins/getopts3.0 b/sh/tests/builtins/getopts3.0
new file mode 100644 (file)
index 0000000..d02469b
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+shift $#
+getopts x opt
+r=$?
+[ "$r" != 0 ] && [ "$OPTIND" = 1 ]
diff --git a/sh/tests/builtins/getopts4.0 b/sh/tests/builtins/getopts4.0
new file mode 100644 (file)
index 0000000..61d5c2b
--- /dev/null
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+set -- -x
+opt=not
+getopts x opt
+r1=$? OPTIND1=$OPTIND opt1=$opt
+getopts x opt
+r2=$? OPTIND2=$OPTIND
+[ "$r1" = 0 ] && [ "$OPTIND1" = 2 ] && [ "$opt1" = x ] && [ "$r2" != 0 ] &&
+       [ "$OPTIND2" = 2 ]
diff --git a/sh/tests/builtins/getopts5.0 b/sh/tests/builtins/getopts5.0
new file mode 100644 (file)
index 0000000..666ee76
--- /dev/null
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+set -- -x arg
+opt=not
+getopts x opt
+r1=$? OPTIND1=$OPTIND opt1=$opt
+getopts x opt
+r2=$? OPTIND2=$OPTIND
+[ "$r1" = 0 ] && [ "$OPTIND1" = 2 ] && [ "$opt1" = x ] && [ "$r2" != 0 ] &&
+       [ "$OPTIND2" = 2 ]
diff --git a/sh/tests/builtins/getopts6.0 b/sh/tests/builtins/getopts6.0
new file mode 100644 (file)
index 0000000..1d3c39b
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+set -- -x -y
+getopts :x var || echo "First getopts bad: $?"
+getopts :x var
+r=$?
+[ r != 0 ] && [ "$OPTIND" = 3 ]
diff --git a/sh/tests/builtins/getopts7.0 b/sh/tests/builtins/getopts7.0
new file mode 100644 (file)
index 0000000..3745555
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+set -- -x
+getopts :x: var
+r=$?
+[ r != 0 ] && [ "$OPTIND" = 2 ]
diff --git a/sh/tests/builtins/getopts8.0 b/sh/tests/builtins/getopts8.0
new file mode 100644 (file)
index 0000000..da4af6b
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+set -- -yz -wx
+opt=wrong1 OPTARG=wrong2
+while getopts :x opt; do
+       echo "$opt:${OPTARG-unset}"
+done
+echo "OPTIND=$OPTIND"
diff --git a/sh/tests/builtins/getopts8.0.stdout b/sh/tests/builtins/getopts8.0.stdout
new file mode 100644 (file)
index 0000000..f10cefc
--- /dev/null
@@ -0,0 +1,5 @@
+?:y
+?:z
+?:w
+x:unset
+OPTIND=3
diff --git a/sh/tests/builtins/getopts9.0 b/sh/tests/builtins/getopts9.0
new file mode 100644 (file)
index 0000000..d23fc43
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+args='-ab'
+getopts ab opt $args
+echo $?:$opt:$OPTARG
+for dummy in dummy1 dummy2; do
+       getopts ab opt $args
+       echo $?:$opt:$OPTARG
+done
diff --git a/sh/tests/builtins/getopts9.0.stdout b/sh/tests/builtins/getopts9.0.stdout
new file mode 100644 (file)
index 0000000..4d32063
--- /dev/null
@@ -0,0 +1,3 @@
+0:a:
+0:b:
+1:?:
diff --git a/sh/tests/builtins/hash1.0 b/sh/tests/builtins/hash1.0
new file mode 100644 (file)
index 0000000..45cc300
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+cat /dev/null
+hash
+hash -r
+hash
diff --git a/sh/tests/builtins/hash1.0.stdout b/sh/tests/builtins/hash1.0.stdout
new file mode 100644 (file)
index 0000000..3afc3e7
--- /dev/null
@@ -0,0 +1 @@
+/bin/cat
diff --git a/sh/tests/builtins/hash2.0 b/sh/tests/builtins/hash2.0
new file mode 100644 (file)
index 0000000..e5cd21b
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+hash
+hash cat
+hash
diff --git a/sh/tests/builtins/hash2.0.stdout b/sh/tests/builtins/hash2.0.stdout
new file mode 100644 (file)
index 0000000..3afc3e7
--- /dev/null
@@ -0,0 +1 @@
+/bin/cat
diff --git a/sh/tests/builtins/hash3.0 b/sh/tests/builtins/hash3.0
new file mode 100644 (file)
index 0000000..eade0b3
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+hash -v cat
+hash
diff --git a/sh/tests/builtins/hash3.0.stdout b/sh/tests/builtins/hash3.0.stdout
new file mode 100644 (file)
index 0000000..a34864c
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/cat
+/bin/cat
diff --git a/sh/tests/builtins/hash4.0 b/sh/tests/builtins/hash4.0
new file mode 100644 (file)
index 0000000..dec584c
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+exec 3>&1
+m=`hash nosuchtool 2>&1 >&3`
+r=$?
+[ "$r" != 0 ] && [ -n "$m" ]
diff --git a/sh/tests/builtins/jobid1.0 b/sh/tests/builtins/jobid1.0
new file mode 100644 (file)
index 0000000..483fda2
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+# Non-standard builtin.
+
+: &
+p1=$!
+p2=$(jobid)
+[ "${p1:?}" = "${p2:?}" ]
diff --git a/sh/tests/builtins/jobid2.0 b/sh/tests/builtins/jobid2.0
new file mode 100644 (file)
index 0000000..101831a
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+: &
+p1=$(jobid)
+p2=$(jobid --)
+p3=$(jobid %+)
+p4=$(jobid -- %+)
+[ "${p1:?}" = "${p2:?}" ] && [ "${p2:?}" = "${p3:?}" ] &&
+[ "${p3:?}" = "${p4:?}" ] && [ "${p4:?}" = "${p1:?}" ]
diff --git a/sh/tests/builtins/kill1.0 b/sh/tests/builtins/kill1.0
new file mode 100644 (file)
index 0000000..c1b8550
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+: &
+p1=$!
+: &
+p2=$!
+wait $p2
+kill %1
diff --git a/sh/tests/builtins/kill2.0 b/sh/tests/builtins/kill2.0
new file mode 100644 (file)
index 0000000..31e0ba3
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+sleep 1 | sleep 1 &
+kill %+
+wait "$!"
+r=$?
+[ "$r" -gt 128 ] && [ "$(kill -l "$r")" = TERM ]
diff --git a/sh/tests/builtins/lineno.0 b/sh/tests/builtins/lineno.0
new file mode 100644 (file)
index 0000000..c9311f8
--- /dev/null
@@ -0,0 +1,16 @@
+# $FreeBSD$
+echo $LINENO
+echo $LINENO
+
+f() {  
+       echo $LINENO
+       echo $LINENO
+}
+
+f
+
+echo ${LINENO:-foo}
+echo ${LINENO=foo}
+echo ${LINENO:+foo}
+echo ${LINENO+foo}
+echo ${#LINENO}
diff --git a/sh/tests/builtins/lineno.0.stdout b/sh/tests/builtins/lineno.0.stdout
new file mode 100644 (file)
index 0000000..82583a9
--- /dev/null
@@ -0,0 +1,9 @@
+2
+3
+2
+3
+12
+13
+foo
+foo
+2
diff --git a/sh/tests/builtins/lineno2.0 b/sh/tests/builtins/lineno2.0
new file mode 100644 (file)
index 0000000..ddbd104
--- /dev/null
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+f() {
+       : ${LINENO+${x?}}
+}
+
+unset -v x
+command eval f 2>/dev/null && exit 3
+x=1
+f
diff --git a/sh/tests/builtins/lineno3.0 b/sh/tests/builtins/lineno3.0
new file mode 100644 (file)
index 0000000..eb8f9ab
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+echo before: $LINENO
+dummy=$'a\0
+'
+echo after: $LINENO
diff --git a/sh/tests/builtins/lineno3.0.stdout b/sh/tests/builtins/lineno3.0.stdout
new file mode 100644 (file)
index 0000000..6e0e4ac
--- /dev/null
@@ -0,0 +1,2 @@
+before: 3
+after: 6
diff --git a/sh/tests/builtins/local1.0 b/sh/tests/builtins/local1.0
new file mode 100644 (file)
index 0000000..b28837e
--- /dev/null
@@ -0,0 +1,13 @@
+# $FreeBSD$
+# A commonly used but non-POSIX builtin.
+
+f() {
+       local x
+       x=2
+       [ "$x" = 2 ]
+}
+x=1
+f || exit 3
+[ "$x" = 1 ] || exit 3
+f || exit 3
+[ "$x" = 1 ] || exit 3
diff --git a/sh/tests/builtins/local2.0 b/sh/tests/builtins/local2.0
new file mode 100644 (file)
index 0000000..cc8c10f
--- /dev/null
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+f() {
+       local -
+       set -a
+       case $- in
+       *a*) : ;;
+       *) echo In-function \$- bad
+       esac
+}
+case $- in
+*a*) echo Initial \$- bad
+esac
+f
+case $- in
+*a*) echo Final \$- bad
+esac
diff --git a/sh/tests/builtins/local3.0 b/sh/tests/builtins/local3.0
new file mode 100644 (file)
index 0000000..39ee370
--- /dev/null
@@ -0,0 +1,26 @@
+# $FreeBSD$
+
+f() {
+       local "$@"
+       set -a
+       x=7
+       case $- in
+       *a*) : ;;
+       *) echo In-function \$- bad
+       esac
+       [ "$x" = 7 ] || echo In-function \$x bad
+}
+x=1
+case $- in
+*a*) echo Initial \$- bad
+esac
+f x -
+case $- in
+*a*) echo Intermediate \$- bad
+esac
+[ "$x" = 1 ] || echo Intermediate \$x bad
+f - x
+case $- in
+*a*) echo Final \$- bad
+esac
+[ "$x" = 1 ] || echo Final \$x bad
diff --git a/sh/tests/builtins/local4.0 b/sh/tests/builtins/local4.0
new file mode 100644 (file)
index 0000000..3955aaa
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+f() {
+       local -- x
+       x=2
+       [ "$x" = 2 ]
+}
+x=1
+f || exit 3
+[ "$x" = 1 ] || exit 3
+f || exit 3
+[ "$x" = 1 ] || exit 3
diff --git a/sh/tests/builtins/locale1.0 b/sh/tests/builtins/locale1.0
new file mode 100644 (file)
index 0000000..90b1094
--- /dev/null
@@ -0,0 +1,134 @@
+# $FreeBSD$
+# Note: this test depends on strerror() using locale.
+
+failures=0
+
+check() {
+       if ! eval "[ $1 ]"; then
+               echo "Failed: $1 at $2"
+               : $((failures += 1))
+       fi
+}
+
+unset LANG LC_ALL LC_COLLATE LC_CTYPE LC_MONETARY LC_NUMERIC LC_TIME LC_MESSAGES
+unset LANGUAGE
+
+msgeng="No such file or directory"
+msgdut="Bestand of map niet gevonden"
+
+# Verify C locale error message.
+case $(command . /var/empty/foo 2>&1) in
+       *"$msgeng"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+# Various locale variables that should not affect the message.
+case $(LC_ALL=C command . /var/empty/foo 2>&1) in
+       *"$msgeng"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+case $(LC_ALL=C LANG=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in
+       *"$msgeng"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+case $(LC_ALL=C LC_MESSAGES=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in
+       *"$msgeng"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+case $(LC_CTYPE=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in
+       *"$msgeng"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+# Verify Dutch message.
+case $(export LANG=nl_NL.ISO8859-1; command . /var/empty/foo 2>&1) in
+       *"$msgdut"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+case $(export LC_MESSAGES=nl_NL.ISO8859-1; command . /var/empty/foo 2>&1) in
+       *"$msgdut"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+case $(export LC_ALL=nl_NL.ISO8859-1; command . /var/empty/foo 2>&1) in
+       *"$msgdut"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+case $(LANG=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in
+       *"$msgdut"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+case $(LC_MESSAGES=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in
+       *"$msgdut"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+case $(LC_ALL=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1) in
+       *"$msgdut"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+# Verify that command assignments do not set the locale persistently.
+case $(command . /var/empty/foo 2>&1) in
+       *"$msgeng"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+case $(LANG=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1; command . /var/empty/foo 2>&1) in
+       *"$msgdut"*"$msgeng"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+case $(LC_MESSAGES=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1; command . /var/empty/foo 2>&1) in
+       *"$msgdut"*"$msgeng"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+case $(LC_ALL=nl_NL.ISO8859-1 command . /var/empty/foo 2>&1; command . /var/empty/foo 2>&1) in
+       *"$msgdut"*"$msgeng"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+# Check special builtin; add colon invocation to avoid depending on certain fix.
+case $(LC_ALL=nl_NL.ISO8859-1 . /var/empty/foo 2>&1; :) in
+       *"$msgdut"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+# Assignments on special builtins are exported to that builtin; the export
+# is not persistent.
+case $(LC_ALL=nl_NL.ISO8859-1 . /dev/null; . /var/empty/foo 2>&1) in
+       *"$msgeng"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+case $(export LC_ALL; LC_ALL=nl_NL.ISO8859-1 . /dev/null; . /var/empty/foo 2>&1) in
+       *"$msgdut"*) ok=1 ;;
+       *) ok=0 ;;
+esac
+check '$ok -eq 1' $LINENO
+
+exit $((failures > 0))
diff --git a/sh/tests/builtins/printf1.0 b/sh/tests/builtins/printf1.0
new file mode 100644 (file)
index 0000000..99a82d0
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+[ "$(printf '%c\0%s%d' x '\' 010 | tr '\0' Z)" = 'xZ\8' ]
diff --git a/sh/tests/builtins/printf2.0 b/sh/tests/builtins/printf2.0
new file mode 100644 (file)
index 0000000..7763d6f
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+[ "$(printf '%cZ%s%d' x '\' 010)" = 'xZ\8' ]
diff --git a/sh/tests/builtins/printf3.0 b/sh/tests/builtins/printf3.0
new file mode 100644 (file)
index 0000000..0e7ea85
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+set -e
+v=$(! printf "%d" @wrong 2>/dev/null)
+[ "$v" = "0" ]
diff --git a/sh/tests/builtins/printf4.0 b/sh/tests/builtins/printf4.0
new file mode 100644 (file)
index 0000000..2dd3e72
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+set -e
+v=$(! printf "%d" 4wrong 2>/dev/null)
+[ "$v" = "4" ]
diff --git a/sh/tests/builtins/read1.0 b/sh/tests/builtins/read1.0
new file mode 100644 (file)
index 0000000..06a68fa
--- /dev/null
@@ -0,0 +1,26 @@
+# $FreeBSD$
+set -e
+
+echo "1 2 3"           | { read a; echo "x${a}x"; }
+echo "1 2 3"           | { read a b; echo "x${a}x${b}x"; }
+echo "1 2 3"           | { read a b c; echo "x${a}x${b}x${c}x"; }
+echo "1 2 3"           | { read a b c d; echo "x${a}x${b}x${c}x${d}x"; }
+
+echo " 1  2 3 "        | { read a b c; echo "x${a}x${b}x${c}x"; }
+echo " 1  2 3 "        | { unset IFS; read a b c; echo "x${a}x${b}x${c}x"; }
+echo " 1  2 3 "        | { IFS=$(printf ' \t\n') read a b c; echo "x${a}x${b}x${c}x"; }
+echo " 1  2 3 "        | { IFS= read a b; echo "x${a}x${b}x"; }
+
+echo " 1,2 3 "         | { IFS=' ,' read a b c; echo "x${a}x${b}x${c}x"; }
+echo ", 2 ,3"          | { IFS=' ,' read a b c; echo "x${a}x${b}x${c}x"; }
+echo " 1 ,,3"          | { IFS=' ,' read a b c; echo "x${a}x${b}x${c}x"; }
+echo " 1 , , 3"                | { IFS=' ,' read a b c; echo "x${a}x${b}x${c}x"; }
+echo " 1 ,2 3,"                | { IFS=' ,' read a b c; echo "x${a}x${b}x${c}x"; }
+echo " 1 ,2 3,,"       | { IFS=' ,' read a b c; echo "x${a}x${b}x${c}x"; }
+
+echo " 1,2 3 "         | { IFS=', ' read a b c; echo "x${a}x${b}x${c}x"; }
+echo ", 2 ,3"          | { IFS=', ' read a b c; echo "x${a}x${b}x${c}x"; }
+echo " 1 ,,3"          | { IFS=', ' read a b c; echo "x${a}x${b}x${c}x"; }
+echo " 1 , , 3"                | { IFS=', ' read a b c; echo "x${a}x${b}x${c}x"; }
+echo " 1 ,2 3,"                | { IFS=', ' read a b c; echo "x${a}x${b}x${c}x"; }
+echo " 1 ,2 3,,"       | { IFS=', ' read a b c; echo "x${a}x${b}x${c}x"; }
diff --git a/sh/tests/builtins/read1.0.stdout b/sh/tests/builtins/read1.0.stdout
new file mode 100644 (file)
index 0000000..dbcb1af
--- /dev/null
@@ -0,0 +1,20 @@
+x1 2 3x
+x1x2 3x
+x1x2x3x
+x1x2x3xx
+x1x2x3x
+x1x2x3x
+x1x2x3x
+x      1  2 3 xx
+x1x2x3x
+xx2x3x
+x1xx3x
+x1xx3x
+x1x2x3x
+x1x2x3,,x
+x1x2x3x
+xx2x3x
+x1xx3x
+x1xx3x
+x1x2x3x
+x1x2x3,,x
diff --git a/sh/tests/builtins/read2.0 b/sh/tests/builtins/read2.0
new file mode 100644 (file)
index 0000000..fc74511
--- /dev/null
@@ -0,0 +1,31 @@
+# $FreeBSD$
+
+set -e
+{
+       echo 1
+       echo two
+       echo three
+} | {
+       read x
+       [ "$x" = 1 ]
+       (read x
+       [ "$x" = two ])
+       read x
+       [ "$x" = three ]
+}
+
+T=`mktemp sh-test.XXXXXX`
+trap 'rm -f "$T"' 0
+{
+       echo 1
+       echo two
+       echo three
+} >$T
+{
+       read x
+       [ "$x" = 1 ]
+       (read x
+       [ "$x" = two ])
+       read x
+       [ "$x" = three ]
+} <$T
diff --git a/sh/tests/builtins/read3.0 b/sh/tests/builtins/read3.0
new file mode 100644 (file)
index 0000000..c6ae9c1
--- /dev/null
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+printf '%s\n' 'a\ b c' | { read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' 'a b\ c' | { read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' 'a\:b:c' | { IFS=: read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' 'a:b\:c' | { IFS=: read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' '\ a'    | { read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' '\:a'    | { IFS=: read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' '\\'     | { read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' '\\\ a'  | { read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' '\\\ a'  | { read -r a b; printf '%s\n' "x${a}x${b}x"; }
diff --git a/sh/tests/builtins/read3.0.stdout b/sh/tests/builtins/read3.0.stdout
new file mode 100644 (file)
index 0000000..8ed98ca
--- /dev/null
@@ -0,0 +1,9 @@
+xa bxcx
+xaxb cx
+xa:bxcx
+xaxb:cx
+x axx
+x:axx
+x\xx
+x\ axx
+x\\\xax
diff --git a/sh/tests/builtins/read4.0 b/sh/tests/builtins/read4.0
new file mode 100644 (file)
index 0000000..7204a35
--- /dev/null
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+printf '%s\n' '\a\ b c'        | { read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' '\a b\ c'        | { read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' '\a\:b:c'        | { IFS=: read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' '\a:b\:c'        | { IFS=: read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' '\\ a'   | { read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' '\\:a'   | { IFS=: read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' '\\\ a'  | { read a b; printf '%s\n' "x${a}x${b}x"; }
+printf '%s\n' '\\\:a'  | { IFS=: read a b; printf '%s\n' "x${a}x${b}x"; }
diff --git a/sh/tests/builtins/read4.0.stdout b/sh/tests/builtins/read4.0.stdout
new file mode 100644 (file)
index 0000000..a8747a4
--- /dev/null
@@ -0,0 +1,8 @@
+xa bxcx
+xaxb cx
+xa:bxcx
+xaxb:cx
+x\xax
+x\xax
+x\ axx
+x\:axx
diff --git a/sh/tests/builtins/read5.0 b/sh/tests/builtins/read5.0
new file mode 100644 (file)
index 0000000..7d83391
--- /dev/null
@@ -0,0 +1,32 @@
+# $FreeBSD$
+
+unset LC_ALL
+LC_CTYPE=en_US.ISO8859-1
+export LC_CTYPE
+
+# Note: the first and last characters are not whitespace.
+# Exclude backslash and newline.
+bad1=`printf %03o \'\\\\`
+bad2=`printf %03o \''
+'`
+e=
+for i in 0 1 2 3; do
+       for j in 0 1 2 3 4 5 6 7; do
+               for k in 0 1 2 3 4 5 6 7; do
+                       case $i$j$k in
+                       000|$bad1|$bad2) continue ;;
+                       esac
+                       e="$e\\$i$j$k"
+               done
+       done
+done
+e=`printf "$e"`
+[ "${#e}" = 253 ] || echo length bad
+
+r1=`printf '%s\n' "$e" | { read -r x; printf '%s' "$x"; }`
+[ "$r1" = "$e" ] || echo "read with -r bad"
+r2=`printf '%s\n' "$e" | { read x; printf '%s' "$x"; }`
+[ "$r2" = "$e" ] || echo "read without -r bad 1"
+IFS=
+r3=`printf '%s\n' "$e" | { read x; printf '%s' "$x"; }`
+[ "$r3" = "$e" ] || echo "read without -r bad 2"
diff --git a/sh/tests/builtins/read6.0 b/sh/tests/builtins/read6.0
new file mode 100644 (file)
index 0000000..2168e10
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+: | read x
+r=$?
+[ "$r" = 1 ]
diff --git a/sh/tests/builtins/read7.0 b/sh/tests/builtins/read7.0
new file mode 100644 (file)
index 0000000..e78f887
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+{ errmsg=`read x <&- 2>&1 >&3`; } 3>&1
+r=$?
+[ "$r" -ge 2 ] && [ "$r" -le 128 ] && [ -n "$errmsg" ]
diff --git a/sh/tests/builtins/return1.0 b/sh/tests/builtins/return1.0
new file mode 100644 (file)
index 0000000..787e892
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+f() {
+       return 0
+       exit 1
+}
+
+f
diff --git a/sh/tests/builtins/return2.1 b/sh/tests/builtins/return2.1
new file mode 100644 (file)
index 0000000..0ef8171
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+f() {
+       true && return 1
+       return 0
+}
+
+f
diff --git a/sh/tests/builtins/return3.1 b/sh/tests/builtins/return3.1
new file mode 100644 (file)
index 0000000..605ec68
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+return 1
+exit 0
diff --git a/sh/tests/builtins/return4.0 b/sh/tests/builtins/return4.0
new file mode 100644 (file)
index 0000000..be5582b
--- /dev/null
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+failures=
+failure() {
+       echo "Error at line $1" >&2
+       failures=x$failures
+}
+
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX) || exit
+trap 'rm -rf $T' 0
+cd $T || exit 3
+echo 'return 42; exit 4' >testscript
+. ./testscript
+[ "$?" = 42 ] || failure $LINENO
+
+test -z "$failures"
diff --git a/sh/tests/builtins/return5.0 b/sh/tests/builtins/return5.0
new file mode 100644 (file)
index 0000000..6e4b7bd
--- /dev/null
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+if [ "$1" != nested ]; then
+       f() {
+               set -- nested
+               . "$0"
+               # Allow return to return from the function or the dot script.
+               return 4
+       }
+       f
+       exit $(($? ^ 4))
+fi
+# To trigger the bug, the following commands must be at the top level,
+# with newlines in between.
+return 4
+echo bad
+exit 1
diff --git a/sh/tests/builtins/return6.4 b/sh/tests/builtins/return6.4
new file mode 100644 (file)
index 0000000..e4d8e0d
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+while return 4; do exit 3; done
diff --git a/sh/tests/builtins/return7.4 b/sh/tests/builtins/return7.4
new file mode 100644 (file)
index 0000000..2047373
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+f() {
+       while return 4; do exit 3; done
+}
+f
diff --git a/sh/tests/builtins/return8.0 b/sh/tests/builtins/return8.0
new file mode 100644 (file)
index 0000000..f00e859
--- /dev/null
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+if [ "$1" = nested ]; then
+       return 17
+fi
+
+f() {
+       set -- nested
+       . "$0"
+       return $(($? ^ 1))
+}
+f
+exit $(($? ^ 16))
diff --git a/sh/tests/builtins/set1.0 b/sh/tests/builtins/set1.0
new file mode 100644 (file)
index 0000000..fc39fad
--- /dev/null
@@ -0,0 +1,32 @@
+# $FreeBSD$
+
+set +C
+set +f
+set -e
+
+settings=$(set +o)
+set -C
+set -f
+set +e
+case $- in
+*C*) ;;
+*) echo missing C ;;
+esac
+case $- in
+*f*) ;;
+*) echo missing C ;;
+esac
+case $- in
+*e*) echo bad e ;;
+esac
+eval "$settings"
+case $- in
+*C*) echo bad C ;;
+esac
+case $- in
+*f*) echo bad f ;;
+esac
+case $- in
+*e*) ;;
+*) echo missing e ;;
+esac
diff --git a/sh/tests/builtins/set2.0 b/sh/tests/builtins/set2.0
new file mode 100644 (file)
index 0000000..ad13eab
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+! env @badness=1 ${SH} -c 'v=`set`; eval "$v"' 2>&1 | grep @badness
diff --git a/sh/tests/builtins/trap1.0 b/sh/tests/builtins/trap1.0
new file mode 100644 (file)
index 0000000..313f6a3
--- /dev/null
@@ -0,0 +1,22 @@
+# $FreeBSD$
+
+test "$(trap 'echo trapped' EXIT; :)" = trapped || exit 1
+
+test "$(trap 'echo trapped' EXIT; /usr/bin/true)" = trapped || exit 1
+
+result=$(${SH} -c 'trap "echo trapped" EXIT; /usr/bin/false')
+test $? -eq 1 || exit 1
+test "$result" = trapped || exit 1
+
+result=$(${SH} -c 'trap "echo trapped" EXIT; exec /usr/bin/false')
+test $? -eq 1 || exit 1
+test -z "$result" || exit 1
+
+result=0
+trap 'result=$((result+1))' INT
+kill -INT $$
+test "$result" -eq 1 || exit 1
+(kill -INT $$)
+test "$result" -eq 2 || exit 1
+
+exit 0
diff --git a/sh/tests/builtins/trap10.0 b/sh/tests/builtins/trap10.0
new file mode 100644 (file)
index 0000000..fa0e35d
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+# Check that the return statement will not break the EXIT trap, ie. all
+# trap commands are executed before the script exits.
+
+test "$(trap 'printf trap; echo ped' EXIT; f() { return; }; f)" = trapped || exit 1
diff --git a/sh/tests/builtins/trap11.0 b/sh/tests/builtins/trap11.0
new file mode 100644 (file)
index 0000000..cfeea9e
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+# Check that the return statement will not break the USR1 trap, ie. all
+# trap commands are executed before the script resumes.
+
+result=$(${SH} -c 'trap "printf trap; echo ped" USR1; f() { return $(kill -USR1 $$); }; f')
+test $? -eq 0 || exit 1
+test "$result" = trapped || exit 1
diff --git a/sh/tests/builtins/trap12.0 b/sh/tests/builtins/trap12.0
new file mode 100644 (file)
index 0000000..8c62ffd
--- /dev/null
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+f() {
+       trap 'return 42' USR1
+       kill -USR1 $$
+       return 3
+}
+f
+r=$?
+[ "$r" = 42 ]
diff --git a/sh/tests/builtins/trap13.0 b/sh/tests/builtins/trap13.0
new file mode 100644 (file)
index 0000000..d90eb08
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+{
+       trap 'exit 0' INT
+       ${SH} -c 'kill -INT $PPID'
+       exit 3
+} &
+wait $!
diff --git a/sh/tests/builtins/trap14.0 b/sh/tests/builtins/trap14.0
new file mode 100644 (file)
index 0000000..97cce8d
--- /dev/null
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+{
+       trap - INT
+       ${SH} -c 'kill -INT $PPID' &
+       wait
+} &
+wait $!
+r=$?
+[ "$r" -gt 128 ] && [ "$(kill -l "$r")" = INT ]
diff --git a/sh/tests/builtins/trap15.0 b/sh/tests/builtins/trap15.0
new file mode 100644 (file)
index 0000000..6b9857d
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+(${SH} -c 'term(){ exit 5;}; trap term TERM; kill -TERM $$') &
+wait >/dev/null 2>&1 $!
+[ $? -eq 5 ]
diff --git a/sh/tests/builtins/trap16.0 b/sh/tests/builtins/trap16.0
new file mode 100644 (file)
index 0000000..3d70cce
--- /dev/null
@@ -0,0 +1,20 @@
+# $FreeBSD$
+
+traps=$(${SH} -c 'trap "echo bad" 0; trap - 0; trap')
+[ -z "$traps" ] || exit 1
+traps=$(${SH} -c 'trap "echo bad" 0; trap "" 0; trap')
+expected_traps=$(${SH} -c 'trap "" EXIT; trap')
+[ "$traps" = "$expected_traps" ] || exit 2
+traps=$(${SH} -c 'trap "echo bad" 0; trap 0; trap')
+[ -z "$traps" ] || exit 3
+traps=$(${SH} -c 'trap "echo bad" 0; trap -- 0; trap')
+[ -z "$traps" ] || exit 4
+traps=$(${SH} -c 'trap "echo bad" 0 1 2; trap - 0 1 2; trap')
+[ -z "$traps" ] || exit 5
+traps=$(${SH} -c 'trap "echo bad" 0 1 2; trap "" 0 1 2; trap')
+expected_traps=$(${SH} -c 'trap "" EXIT HUP INT; trap')
+[ "$traps" = "$expected_traps" ] || exit 6
+traps=$(${SH} -c 'trap "echo bad" 0 1 2; trap 0 1 2; trap')
+[ -z "$traps" ] || exit 7
+traps=$(${SH} -c 'trap "echo bad" 0 1 2; trap -- 0 1 2; trap')
+[ -z "$traps" ] || exit 8
diff --git a/sh/tests/builtins/trap2.0 b/sh/tests/builtins/trap2.0
new file mode 100644 (file)
index 0000000..a05287a
--- /dev/null
@@ -0,0 +1,52 @@
+# $FreeBSD$
+# This is really a test for outqstr(), which is readily accessible via trap.
+
+runtest()
+{
+       teststring=$1
+       trap -- "$teststring" USR1
+       traps=$(trap)
+       if [ "$teststring" != "-" ] && [ -z "$traps" ]; then
+               # One possible reading of POSIX requires the above to return an
+               # empty string because backquote commands are executed in a
+               # subshell and subshells shall reset traps. However, an example
+               # in the normative description of the trap builtin shows the
+               # same usage as here, it is useful and our /bin/sh allows it.
+               echo '$(trap) is broken'
+               exit 1
+       fi
+       trap - USR1
+       eval "$traps"
+       traps2=$(trap)
+       if [ "$traps" != "$traps2" ]; then
+               echo "Mismatch for '$teststring'"
+               exit 1
+       fi
+}
+
+runtest 'echo'
+runtest 'echo hi'
+runtest "'echo' 'hi'"
+runtest '"echo" $PATH'
+runtest '\echo "$PATH"'
+runtest ' 0'
+runtest '0 '
+runtest ' 1'
+runtest '1 '
+i=1
+while [ $i -le 127 ]; do
+       c=$(printf \\"$(printf %o $i)")
+       if [ $i -lt 48 ] || [ $i -gt 57 ]; then
+               runtest "$c"
+       fi
+       runtest " $c$c"
+       runtest "a$c"
+       i=$((i+1))
+done
+IFS=,
+runtest ' '
+runtest ','
+unset IFS
+runtest ' '
+
+exit 0
diff --git a/sh/tests/builtins/trap3.0 b/sh/tests/builtins/trap3.0
new file mode 100644 (file)
index 0000000..8160729
--- /dev/null
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+{
+       trap '' garbage && exit 3
+       trap - garbage && exit 3
+       trap true garbage && exit 3
+       trap '' 99999 && exit 3
+       trap - 99999 && exit 3
+       trap true 99999 && exit 3
+} 2>/dev/null
+exit 0
diff --git a/sh/tests/builtins/trap4.0 b/sh/tests/builtins/trap4.0
new file mode 100644 (file)
index 0000000..7f2080e
--- /dev/null
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX)
+trap 'rm -rf $T' 0
+cd $T || exit 3
+mkfifo fifo1
+
+v=$(
+       exec 3>&1
+       : <fifo1 &
+       {
+               wait $!
+               trap 'trap "" PIPE; echo trapped >&3 2>/dev/null' PIPE
+               echo x 2>/dev/null
+       } >fifo1
+)
+test "$v" = trapped
diff --git a/sh/tests/builtins/trap5.0 b/sh/tests/builtins/trap5.0
new file mode 100644 (file)
index 0000000..56e0fb1
--- /dev/null
@@ -0,0 +1,19 @@
+# $FreeBSD$
+
+set -e
+trap - USR1
+initial=$(trap)
+trap -- -l USR1
+added=$(trap)
+[ -n "$added" ]
+trap - USR1
+second=$(trap)
+[ "$initial" = "$second" ]
+eval "$added"
+added2=$(trap)
+added3=$(trap --)
+[ "$added" = "$added2" ]
+[ "$added2" = "$added3" ]
+trap -- - USR1
+third=$(trap)
+[ "$initial" = "$third" ]
diff --git a/sh/tests/builtins/trap6.0 b/sh/tests/builtins/trap6.0
new file mode 100644 (file)
index 0000000..bd2bf7e
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+v=$(
+       ${SH} -c 'trap "echo ok; exit" USR1; kill -USR1 $$' &
+       # Suppress possible message about exit on signal
+       wait $! >/dev/null 2>&1
+)
+r=$(kill -l $?)
+[ "$v" = "ok" ] && { [ "$r" = "USR1" ] || [ "$r" = "usr1" ]; }
diff --git a/sh/tests/builtins/trap7.0 b/sh/tests/builtins/trap7.0
new file mode 100644 (file)
index 0000000..35529b8
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+[ "$(trap 'echo trapped' EXIT)" = trapped ]
diff --git a/sh/tests/builtins/trap8.0 b/sh/tests/builtins/trap8.0
new file mode 100644 (file)
index 0000000..cdce976
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+# I am not sure if POSIX requires the shell to continue processing
+# further trap names in the same trap command after an invalid one.
+
+test -n "$(trap true garbage TERM 2>/dev/null || trap)" || exit 3
+exit 0
diff --git a/sh/tests/builtins/trap9.0 b/sh/tests/builtins/trap9.0
new file mode 100644 (file)
index 0000000..0f584ec
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+test "$(trap 'printf trap; echo ped' EXIT; f() { :; }; f)" = trapped || exit 1
diff --git a/sh/tests/builtins/type1.0 b/sh/tests/builtins/type1.0
new file mode 100644 (file)
index 0000000..c5e4564
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+command -v not-here && exit 1
+command -v /not-here && exit 1
+command -V not-here && exit 1
+command -V /not-here && exit 1
+type not-here && exit 1
+type /not-here && exit 1
+exit 0
diff --git a/sh/tests/builtins/type1.0.stderr b/sh/tests/builtins/type1.0.stderr
new file mode 100644 (file)
index 0000000..7853418
--- /dev/null
@@ -0,0 +1,4 @@
+not-here: not found
+/not-here: No such file or directory
+not-here: not found
+/not-here: No such file or directory
diff --git a/sh/tests/builtins/type2.0 b/sh/tests/builtins/type2.0
new file mode 100644 (file)
index 0000000..fe44d95
--- /dev/null
@@ -0,0 +1,26 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if ! eval "$*"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+check 'PATH=/libexec type ld-elf.so.1 >/dev/null'
+check '! PATH=/libexec type ls 2>/dev/null'
+
+PATH=/libexec:$PATH
+
+check 'type ld-elf.so.1 >/dev/null'
+
+PATH=/libexec
+
+check 'type ld-elf.so.1 >/dev/null'
+check '! type ls 2>/dev/null'
+check 'PATH=/bin type ls >/dev/null'
+check '! PATH=/bin type ld-elf.so.1 2>/dev/null'
+
+exit $((failures > 0))
diff --git a/sh/tests/builtins/type3.0 b/sh/tests/builtins/type3.0
new file mode 100644 (file)
index 0000000..87cccdd
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+[ "$(type type)" = "$(type -- type)" ]
diff --git a/sh/tests/builtins/unalias.0 b/sh/tests/builtins/unalias.0
new file mode 100644 (file)
index 0000000..34d8d6e
--- /dev/null
@@ -0,0 +1,21 @@
+# $FreeBSD$
+set -e
+
+alias false=true
+false
+unalias false
+false && exit 1
+unalias false && exit 1
+
+alias a1=foo a2=bar
+unalias a1 a2
+unalias a1 && exit 1
+unalias a2 && exit 1
+alias a2=bar
+unalias a1 a2 && exit 1
+
+alias a1=foo a2=bar
+unalias -a
+unalias a1 && exit 1
+unalias a2 && exit 1
+exit 0
diff --git a/sh/tests/builtins/var-assign.0 b/sh/tests/builtins/var-assign.0
new file mode 100644 (file)
index 0000000..ace39c0
--- /dev/null
@@ -0,0 +1,55 @@
+# $FreeBSD$
+IFS=,
+
+SPECIAL="break,\
+       :,\
+       continue,\
+       . /dev/null,
+       eval,
+       exec,
+       export -p,
+       readonly -p,
+       set,
+       shift 0,
+       times,
+       trap,
+       unset foo"
+
+UTILS="alias,\
+       bg,\
+       bind,\
+       cd,\
+       command echo,\
+       echo,\
+       false,\
+       fc -l,\
+       fg,\
+       getopts a var,\
+       hash,\
+       jobs,\
+       printf a,\
+       pwd,\
+       read var < /dev/null,\
+       test,\
+       true,\
+       type ls,\
+       ulimit,\
+       umask,\
+       unalias -a,\
+       wait"
+
+set -e
+
+# For special built-ins variable assignments affect the shell environment.
+set -- ${SPECIAL}
+for cmd in "$@"
+do
+       ${SH} -c "VAR=1; VAR=0 ${cmd}; exit \${VAR}" >/dev/null 2>&1
+done
+
+# For other built-ins and utilites they do not.
+set -- ${UTILS}
+for cmd in "$@"
+do
+       ${SH} -c "VAR=0; VAR=1 ${cmd}; exit \${VAR}" >/dev/null 2>&1
+done
diff --git a/sh/tests/builtins/var-assign2.0 b/sh/tests/builtins/var-assign2.0
new file mode 100644 (file)
index 0000000..eafec89
--- /dev/null
@@ -0,0 +1,55 @@
+# $FreeBSD$
+IFS=,
+
+SPECIAL="break,\
+       :,\
+       continue,\
+       . /dev/null,\
+       eval,\
+       exec,\
+       export -p,\
+       readonly -p,\
+       set,\
+       shift 0,\
+       times,\
+       trap,\
+       unset foo"
+
+UTILS="alias,\
+       bg,\
+       bind,\
+       cd,\
+       command echo,\
+       echo,\
+       false,\
+       fc -l,\
+       fg,\
+       getopts a var,\
+       hash,\
+       jobs,\
+       printf a,\
+       pwd,\
+       read var < /dev/null,\
+       test,\
+       true,\
+       type ls,\
+       ulimit,\
+       umask,\
+       unalias -a,\
+       wait"
+
+set -e
+
+# With 'command', variable assignments do not affect the shell environment.
+
+set -- ${SPECIAL}
+for cmd in "$@"
+do
+       ${SH} -c "VAR=0; VAR=1 command ${cmd}; exit \${VAR}" >/dev/null 2>&1
+done
+
+set -- ${UTILS}
+for cmd in "$@"
+do
+       ${SH} -c "VAR=0; VAR=1 command ${cmd}; exit \${VAR}" >/dev/null 2>&1
+done
diff --git a/sh/tests/builtins/wait1.0 b/sh/tests/builtins/wait1.0
new file mode 100644 (file)
index 0000000..1ca8530
--- /dev/null
@@ -0,0 +1,23 @@
+# $FreeBSD$
+
+failures=
+failure() {
+       echo "Error at line $1" >&2
+       failures=x$failures
+}
+
+exit 4 & p4=$!
+exit 8 & p8=$!
+wait $p4
+[ $? = 4 ] || failure $LINENO
+wait $p8
+[ $? = 8 ] || failure $LINENO
+
+exit 3 & p3=$!
+exit 7 & p7=$!
+wait $p7
+[ $? = 7 ] || failure $LINENO
+wait $p3
+[ $? = 3 ] || failure $LINENO
+
+test -z "$failures"
diff --git a/sh/tests/builtins/wait10.0 b/sh/tests/builtins/wait10.0
new file mode 100644 (file)
index 0000000..864fc78
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+# Init cannot be a child of the shell.
+exit 49 & p49=$!
+wait 1 "$p49"
+[ "$?" = 49 ]
diff --git a/sh/tests/builtins/wait2.0 b/sh/tests/builtins/wait2.0
new file mode 100644 (file)
index 0000000..e61455c
--- /dev/null
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+failures=
+failure() {
+       echo "Error at line $1" >&2
+       failures=x$failures
+}
+
+for i in 1 2 3 4 5 6 7 8 9 10; do
+       exit $i &
+done
+wait || failure $LINENO
+wait || failure $LINENO
+
+test -z "$failures"
diff --git a/sh/tests/builtins/wait3.0 b/sh/tests/builtins/wait3.0
new file mode 100644 (file)
index 0000000..1ed5299
--- /dev/null
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+failures=
+failure() {
+       echo "Error at line $1" >&2
+       failures=x$failures
+}
+
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX)
+trap 'rm -rf $T' 0
+cd $T || exit 3
+mkfifo fifo1
+for i in 1 2 3 4 5 6 7 8 9 10; do
+       exit $i 4<fifo1 &
+done
+exec 3>fifo1
+wait || failure $LINENO
+(${SH} -c echo >&3) 2>/dev/null && failure $LINENO
+wait || failure $LINENO
+
+test -z "$failures"
diff --git a/sh/tests/builtins/wait4.0 b/sh/tests/builtins/wait4.0
new file mode 100644 (file)
index 0000000..7935131
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+T=`mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX`
+trap 'rm -rf $T' 0
+cd $T || exit 3
+mkfifo fifo1
+trapped=
+trap trapped=1 QUIT
+{ kill -QUIT $$; sleep 1; exit 4; } >fifo1 &
+wait $! <fifo1
+r=$?
+[ "$r" -gt 128 ] && [ -n "$trapped" ]
diff --git a/sh/tests/builtins/wait5.0 b/sh/tests/builtins/wait5.0
new file mode 100644 (file)
index 0000000..6874bf6
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+T=`mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX`
+trap 'rm -rf $T' 0
+cd $T || exit 3
+mkfifo fifo1
+trapped=
+trap trapped=1 QUIT
+{ kill -QUIT $$; sleep 1; exit 4; } >fifo1 &
+wait <fifo1
+r=$?
+[ "$r" -gt 128 ] && [ -n "$trapped" ]
diff --git a/sh/tests/builtins/wait6.0 b/sh/tests/builtins/wait6.0
new file mode 100644 (file)
index 0000000..20e3c68
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+wait --
diff --git a/sh/tests/builtins/wait7.0 b/sh/tests/builtins/wait7.0
new file mode 100644 (file)
index 0000000..0fb092f
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+: &
+wait -- $!
diff --git a/sh/tests/builtins/wait8.0 b/sh/tests/builtins/wait8.0
new file mode 100644 (file)
index 0000000..b59ff59
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+exit 44 & p44=$!
+exit 45 & p45=$!
+exit 7 & p7=$!
+wait "$p44" "$p7" "$p45"
+[ "$?" = 45 ]
diff --git a/sh/tests/builtins/wait9.127 b/sh/tests/builtins/wait9.127
new file mode 100644 (file)
index 0000000..661f275
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+# Init cannot be a child of the shell.
+wait 1
diff --git a/sh/tests/errors/Makefile b/sh/tests/errors/Makefile
new file mode 100644 (file)
index 0000000..ace9a01
--- /dev/null
@@ -0,0 +1,32 @@
+# $FreeBSD$
+
+TESTSDIR=      ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH=  functional_test
+
+FILESDIR=      ${TESTSDIR}
+
+FILES=         assignment-error1.0
+FILES+=                assignment-error2.0
+FILES+=                backquote-error1.0
+FILES+=                backquote-error2.0
+FILES+=                bad-binary1.126
+FILES+=                bad-keyword1.0
+FILES+=                bad-parm-exp1.0
+FILES+=                bad-parm-exp2.2 bad-parm-exp2.2.stderr
+FILES+=                bad-parm-exp3.2 bad-parm-exp3.2.stderr
+FILES+=                bad-parm-exp4.2 bad-parm-exp4.2.stderr
+FILES+=                bad-parm-exp5.2 bad-parm-exp5.2.stderr
+FILES+=                bad-parm-exp6.2 bad-parm-exp6.2.stderr
+FILES+=                option-error.0
+FILES+=                redirection-error.0
+FILES+=                redirection-error2.2
+FILES+=                redirection-error3.0
+FILES+=                redirection-error4.0
+FILES+=                redirection-error5.0
+FILES+=                redirection-error6.0
+FILES+=                redirection-error7.0
+FILES+=                write-error1.0
+
+.include <bsd.test.mk>
diff --git a/sh/tests/errors/assignment-error1.0 b/sh/tests/errors/assignment-error1.0
new file mode 100644 (file)
index 0000000..00eaed9
--- /dev/null
@@ -0,0 +1,30 @@
+# $FreeBSD$
+IFS=,
+
+SPECIAL="break,\
+       :,\
+       continue,\
+       . /dev/null,\
+       eval,\
+       exec,\
+       export -p,\
+       readonly -p,\
+       set,\
+       shift,\
+       times,\
+       trap,\
+       unset foo"
+
+# If there is no command word, the shell must abort on an assignment error.
+${SH} -c "readonly a=0; a=2; exit 0" 2>/dev/null && exit 1
+
+# Special built-in utilities must abort on an assignment error.
+set -- ${SPECIAL}
+for cmd in "$@"
+do
+       ${SH} -c "readonly a=0; a=2 ${cmd}; exit 0" 2>/dev/null && exit 1
+done
+
+# Other utilities must not abort; we currently still execute them.
+${SH} -c 'readonly a=0; a=1 true; exit $a' 2>/dev/null || exit 1
+${SH} -c 'readonly a=0; a=1 command :; exit $a' 2>/dev/null || exit 1
diff --git a/sh/tests/errors/assignment-error2.0 b/sh/tests/errors/assignment-error2.0
new file mode 100644 (file)
index 0000000..ff4e629
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+set -e
+HOME=/
+readonly HOME
+cd /sbin
+{ HOME=/bin cd; } 2>/dev/null || :
+[ "$(pwd)" != /bin ]
diff --git a/sh/tests/errors/backquote-error1.0 b/sh/tests/errors/backquote-error1.0
new file mode 100644 (file)
index 0000000..43e3303
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+echo 'echo `for` echo ".BAD"CODE.' | ${SH} +m -i 2>&1 | grep -q BADCODE && exit 1
+exit 0
diff --git a/sh/tests/errors/backquote-error2.0 b/sh/tests/errors/backquote-error2.0
new file mode 100644 (file)
index 0000000..5b49e2b
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+${SH} -c 'echo `echo .BA"DCODE.`
+echo ".BAD"CODE.' 2>&1 | grep -q BADCODE && exit 1
+echo '`"`' | ${SH} -n 2>/dev/null && exit 1
+echo '`'"'"'`' | ${SH} -n 2>/dev/null && exit 1
+exit 0
diff --git a/sh/tests/errors/bad-binary1.126 b/sh/tests/errors/bad-binary1.126
new file mode 100644 (file)
index 0000000..d92e9de
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+# Checking for binary "scripts" without magic number is permitted but not
+# required by POSIX. However, it is preferable to getting errors like
+# Syntax error: word unexpected (expecting ")")
+# from trying to execute ELF binaries for the wrong architecture.
+
+T=`mktemp -d "${TMPDIR:-/tmp}/sh-test.XXXXXXXX"` || exit
+trap 'rm -rf "${T}"' 0
+printf '\0echo bad\n' >"$T/testshellproc"
+chmod 755 "$T/testshellproc"
+PATH=$T:$PATH
+testshellproc 2>/dev/null
diff --git a/sh/tests/errors/bad-keyword1.0 b/sh/tests/errors/bad-keyword1.0
new file mode 100644 (file)
index 0000000..ac01536
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+echo ':; fi' | ${SH} -n 2>/dev/null && exit 1
+exit 0
diff --git a/sh/tests/errors/bad-parm-exp1.0 b/sh/tests/errors/bad-parm-exp1.0
new file mode 100644 (file)
index 0000000..6e94994
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+false && {
+       ${}
+       ${foo/}
+       ${foo@bar}
+}
+:
diff --git a/sh/tests/errors/bad-parm-exp2.2 b/sh/tests/errors/bad-parm-exp2.2
new file mode 100644 (file)
index 0000000..a0826ec
--- /dev/null
@@ -0,0 +1,2 @@
+# $FreeBSD$
+eval '${}'
diff --git a/sh/tests/errors/bad-parm-exp2.2.stderr b/sh/tests/errors/bad-parm-exp2.2.stderr
new file mode 100644 (file)
index 0000000..51ea69c
--- /dev/null
@@ -0,0 +1 @@
+eval: ${}: Bad substitution
diff --git a/sh/tests/errors/bad-parm-exp3.2 b/sh/tests/errors/bad-parm-exp3.2
new file mode 100644 (file)
index 0000000..bb41208
--- /dev/null
@@ -0,0 +1,2 @@
+# $FreeBSD$
+eval '${foo/}'
diff --git a/sh/tests/errors/bad-parm-exp3.2.stderr b/sh/tests/errors/bad-parm-exp3.2.stderr
new file mode 100644 (file)
index 0000000..70473f9
--- /dev/null
@@ -0,0 +1 @@
+eval: ${foo/}: Bad substitution
diff --git a/sh/tests/errors/bad-parm-exp4.2 b/sh/tests/errors/bad-parm-exp4.2
new file mode 100644 (file)
index 0000000..2837f9b
--- /dev/null
@@ -0,0 +1,2 @@
+# $FreeBSD$
+eval '${foo:@abc}'
diff --git a/sh/tests/errors/bad-parm-exp4.2.stderr b/sh/tests/errors/bad-parm-exp4.2.stderr
new file mode 100644 (file)
index 0000000..3363f51
--- /dev/null
@@ -0,0 +1 @@
+eval: ${foo:@...}: Bad substitution
diff --git a/sh/tests/errors/bad-parm-exp5.2 b/sh/tests/errors/bad-parm-exp5.2
new file mode 100644 (file)
index 0000000..1ba343b
--- /dev/null
@@ -0,0 +1,2 @@
+# $FreeBSD$
+eval '${/}'
diff --git a/sh/tests/errors/bad-parm-exp5.2.stderr b/sh/tests/errors/bad-parm-exp5.2.stderr
new file mode 100644 (file)
index 0000000..13763f8
--- /dev/null
@@ -0,0 +1 @@
+eval: ${/}: Bad substitution
diff --git a/sh/tests/errors/bad-parm-exp6.2 b/sh/tests/errors/bad-parm-exp6.2
new file mode 100644 (file)
index 0000000..b53a91b
--- /dev/null
@@ -0,0 +1,2 @@
+# $FreeBSD$
+eval '${#foo^}'
diff --git a/sh/tests/errors/bad-parm-exp6.2.stderr b/sh/tests/errors/bad-parm-exp6.2.stderr
new file mode 100644 (file)
index 0000000..cc56f65
--- /dev/null
@@ -0,0 +1 @@
+eval: ${foo...}: Bad substitution
diff --git a/sh/tests/errors/option-error.0 b/sh/tests/errors/option-error.0
new file mode 100644 (file)
index 0000000..b4b44c4
--- /dev/null
@@ -0,0 +1,46 @@
+# $FreeBSD$
+IFS=,
+
+SPECIAL="break abc,\
+       continue abc,\
+       .,
+       exit abc,
+       export -x,
+       readonly -x,
+       return abc,
+       set -z,
+       shift abc,
+       trap -y,
+       unset -y"
+
+UTILS="alias -y,\
+       cat -z,\
+       cd abc def,\
+       command break abc,\
+       expr 1 +,\
+       fc -z,\
+       getopts,\
+       hash -z,\
+       jobs -z,\
+       printf,\
+       pwd abc,\
+       read,\
+       test abc =,\
+       ulimit -z,\
+       umask -z,\
+       unalias -z,\
+       wait abc"
+
+# Special built-in utilities must abort on an option or operand error.
+set -- ${SPECIAL}
+for cmd in "$@"
+do
+       ${SH} -c "${cmd}; exit 0" 2>/dev/null && exit 1
+done
+
+# Other utilities must not abort.
+set -- ${UTILS}
+for cmd in "$@"
+do
+       ${SH} -c "${cmd}; exit 0" 2>/dev/null || exit 1
+done
diff --git a/sh/tests/errors/redirection-error.0 b/sh/tests/errors/redirection-error.0
new file mode 100644 (file)
index 0000000..cb8c0b1
--- /dev/null
@@ -0,0 +1,53 @@
+# $FreeBSD$
+IFS=,
+
+SPECIAL="break,\
+       :,\
+       continue,\
+       . /dev/null,
+       eval,
+       exec,
+       export -p,
+       readonly -p,
+       set,
+       shift,
+       times,
+       trap,
+       unset foo"
+
+UTILS="alias,\
+       bg,\
+       bind,\
+       cd,\
+       command echo,\
+       echo,\
+       false,\
+       fc -l,\
+       fg,\
+       getopts a -a,\
+       hash,\
+       jobs,\
+       printf a,\
+       pwd,\
+       read var < /dev/null,\
+       test,\
+       true,\
+       type ls,\
+       ulimit,\
+       umask,\
+       unalias -a,\
+       wait"
+
+# Special built-in utilities must abort on a redirection error.
+set -- ${SPECIAL}
+for cmd in "$@"
+do
+       ${SH} -c "${cmd} > /; exit 0" 2>/dev/null && exit 1
+done
+
+# Other utilities must not abort.
+set -- ${UTILS}
+for cmd in "$@"
+do
+       ${SH} -c "${cmd} > /; exit 0" 2>/dev/null || exit 1
+done
diff --git a/sh/tests/errors/redirection-error2.2 b/sh/tests/errors/redirection-error2.2
new file mode 100644 (file)
index 0000000..32bccd8
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+# sh should fail gracefully on this bad redirect
+${SH} -c 'echo 1 >&$a' 2>/dev/null
diff --git a/sh/tests/errors/redirection-error3.0 b/sh/tests/errors/redirection-error3.0
new file mode 100644 (file)
index 0000000..8a07d03
--- /dev/null
@@ -0,0 +1,54 @@
+# $FreeBSD$
+IFS=,
+
+SPECIAL="break,\
+       :,\
+       continue,\
+       . /dev/null,\
+       eval,\
+       exec,\
+       export -p,\
+       readonly -p,\
+       set,\
+       shift,\
+       times,\
+       trap,\
+       unset foo"
+
+UTILS="alias,\
+       bg,\
+       bind,\
+       cd,\
+       command echo,\
+       echo,\
+       false,\
+       fc -l,\
+       fg,\
+       getopts a -a,\
+       hash,\
+       jobs,\
+       printf a,\
+       pwd,\
+       read var < /dev/null,\
+       test,\
+       true,\
+       type ls,\
+       ulimit,\
+       umask,\
+       unalias -a,\
+       wait"
+
+# When used with 'command', neither special built-in utilities nor other
+# utilities must abort on a redirection error.
+
+set -- ${SPECIAL}
+for cmd in "$@"
+do
+       ${SH} -c "command ${cmd} > /; exit 0" 2>/dev/null || exit 1
+done
+
+set -- ${UTILS}
+for cmd in "$@"
+do
+       ${SH} -c "command ${cmd} > /; exit 0" 2>/dev/null || exit 1
+done
diff --git a/sh/tests/errors/redirection-error4.0 b/sh/tests/errors/redirection-error4.0
new file mode 100644 (file)
index 0000000..2060974
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+# A redirection error should not abort the shell if there is no command word.
+exec 2>/dev/null
+</var/empty/x
+</var/empty/x y=2
+y=2 </var/empty/x
+exit 0
diff --git a/sh/tests/errors/redirection-error5.0 b/sh/tests/errors/redirection-error5.0
new file mode 100644 (file)
index 0000000..1fcd47e
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+# A redirection error on a subshell should not abort the shell.
+exec 2>/dev/null
+( echo bad ) </var/empty/x
+exit 0
diff --git a/sh/tests/errors/redirection-error6.0 b/sh/tests/errors/redirection-error6.0
new file mode 100644 (file)
index 0000000..17d1109
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+# A redirection error on a compound command should not abort the shell.
+exec 2>/dev/null
+{ echo bad; } </var/empty/x
+if :; then echo bad; fi </var/empty/x
+for i in 1; do echo bad; done </var/empty/x
+i=0
+while [ $i = 0 ]; do echo bad; i=1; done </var/empty/x
+i=0
+until [ $i != 0 ]; do echo bad; i=1; done </var/empty/x
+case i in *) echo bad ;; esac </var/empty/x
+exit 0
diff --git a/sh/tests/errors/redirection-error7.0 b/sh/tests/errors/redirection-error7.0
new file mode 100644 (file)
index 0000000..5b20f04
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+! dummy=$(
+       exec 3>&1 >&2 2>&3
+       ulimit -n 9
+       exec 9<.
+) && [ -n "$dummy" ]
diff --git a/sh/tests/errors/write-error1.0 b/sh/tests/errors/write-error1.0
new file mode 100644 (file)
index 0000000..fcb52e7
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+! echo >&- 2>/dev/null
diff --git a/sh/tests/execution/Makefile b/sh/tests/execution/Makefile
new file mode 100644 (file)
index 0000000..638492b
--- /dev/null
@@ -0,0 +1,56 @@
+# $FreeBSD$
+
+TESTSDIR=      ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH=  functional_test
+
+FILESDIR=      ${TESTSDIR}
+
+FILES=         bg1.0
+FILES+=                bg2.0
+FILES+=                bg3.0
+FILES+=                bg4.0
+FILES+=                bg5.0
+FILES+=                bg6.0 bg6.0.stdout
+FILES+=                bg7.0
+FILES+=                bg8.0
+FILES+=                bg9.0
+FILES+=                bg10.0 bg10.0.stdout
+FILES+=                fork1.0
+FILES+=                fork2.0
+FILES+=                fork3.0
+FILES+=                func1.0
+FILES+=                func2.0
+FILES+=                func3.0
+FILES+=                hash1.0
+FILES+=                int-cmd1.0
+FILES+=                killed1.0
+FILES+=                killed2.0
+FILES+=                not1.0
+FILES+=                not2.0
+FILES+=                path1.0
+FILES+=                redir1.0
+FILES+=                redir2.0
+FILES+=                redir3.0
+FILES+=                redir4.0
+FILES+=                redir5.0
+FILES+=                redir6.0
+FILES+=                redir7.0
+FILES+=                set-n1.0
+FILES+=                set-n2.0
+FILES+=                set-n3.0
+FILES+=                set-n4.0
+FILES+=                set-x1.0
+FILES+=                set-x2.0
+FILES+=                set-x3.0
+FILES+=                set-x4.0
+FILES+=                shellproc1.0
+FILES+=                subshell1.0 subshell1.0.stdout
+FILES+=                subshell2.0
+FILES+=                subshell3.0
+FILES+=                subshell4.0
+FILES+=                unknown1.0
+FILES+=                var-assign1.0
+
+.include <bsd.test.mk>
diff --git a/sh/tests/execution/bg1.0 b/sh/tests/execution/bg1.0
new file mode 100644 (file)
index 0000000..edb92ae
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+: `false` &
diff --git a/sh/tests/execution/bg10.0 b/sh/tests/execution/bg10.0
new file mode 100644 (file)
index 0000000..44a25dc
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+# The redirection overrides the </dev/null implicit in a background command.
+
+echo yes | ${SH} -c '{ cat & wait; } <&0'
diff --git a/sh/tests/execution/bg10.0.stdout b/sh/tests/execution/bg10.0.stdout
new file mode 100644 (file)
index 0000000..7cfab5b
--- /dev/null
@@ -0,0 +1 @@
+yes
diff --git a/sh/tests/execution/bg2.0 b/sh/tests/execution/bg2.0
new file mode 100644 (file)
index 0000000..2e2fbc5
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+f() { return 42; }
+f
+: | : &
diff --git a/sh/tests/execution/bg3.0 b/sh/tests/execution/bg3.0
new file mode 100644 (file)
index 0000000..359fc6f
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+f() { return 42; }
+f
+(:) &
diff --git a/sh/tests/execution/bg4.0 b/sh/tests/execution/bg4.0
new file mode 100644 (file)
index 0000000..25e4f4e
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+x=''
+: ${x:=1} &
+wait
+exit ${x:-0}
diff --git a/sh/tests/execution/bg5.0 b/sh/tests/execution/bg5.0
new file mode 100644 (file)
index 0000000..cc9ceaa
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+# A background command has an implicit </dev/null redirection.
+
+echo bad | ${SH} -c '{ cat & wait; }'
diff --git a/sh/tests/execution/bg6.0 b/sh/tests/execution/bg6.0
new file mode 100644 (file)
index 0000000..b0faf9e
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+# The redirection overrides the </dev/null implicit in a background command.
+
+echo yes | ${SH} -c '{ cat & wait; } </dev/stdin'
diff --git a/sh/tests/execution/bg6.0.stdout b/sh/tests/execution/bg6.0.stdout
new file mode 100644 (file)
index 0000000..7cfab5b
--- /dev/null
@@ -0,0 +1 @@
+yes
diff --git a/sh/tests/execution/bg7.0 b/sh/tests/execution/bg7.0
new file mode 100644 (file)
index 0000000..f771edc
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+# The redirection does not apply to the background command, and therefore
+# does not override the implicit </dev/null.
+
+echo bad | ${SH} -c '</dev/null; { cat & wait; }'
diff --git a/sh/tests/execution/bg8.0 b/sh/tests/execution/bg8.0
new file mode 100644 (file)
index 0000000..33667cb
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+# The redirection does not apply to the background command, and therefore
+# does not override the implicit </dev/null.
+
+echo bad | ${SH} -c 'command eval \) </dev/null 2>/dev/null; { cat & wait; }'
diff --git a/sh/tests/execution/bg9.0 b/sh/tests/execution/bg9.0
new file mode 100644 (file)
index 0000000..64fde3e
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+# The redirection does not apply to the background command, and therefore
+# does not override the implicit </dev/null.
+
+echo bad | ${SH} -c 'command eval eval \\\) \</dev/null 2>/dev/null; { cat & wait; }'
diff --git a/sh/tests/execution/fork1.0 b/sh/tests/execution/fork1.0
new file mode 100644 (file)
index 0000000..2eeac79
--- /dev/null
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+shname=${SH%% *}
+shname=${shname##*/}
+
+result=$(${SH} -c 'ps -p $$ -o comm=')
+test "$result" = "ps" || exit 1
+
+result=$(${SH} -c 'ps -p $$ -o comm=; :')
+test "$result" = "$shname" || exit 1
diff --git a/sh/tests/execution/fork2.0 b/sh/tests/execution/fork2.0
new file mode 100644 (file)
index 0000000..62a2537
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+result=$(${SH} -c '(/bin/sleep 1)& sleep 0.1; ps -p $! -o comm=; kill $!')
+test "$result" = sleep || exit 1
+
+result=$(${SH} -c '{ trap "echo trapped" EXIT; (/usr/bin/true); } & wait')
+test "$result" = trapped || exit 1
+
+exit 0
diff --git a/sh/tests/execution/fork3.0 b/sh/tests/execution/fork3.0
new file mode 100644 (file)
index 0000000..3cb678c
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+result=$(${SH} -c 'f() { ps -p $$ -o comm=; }; f')
+test "$result" = "ps"
diff --git a/sh/tests/execution/func1.0 b/sh/tests/execution/func1.0
new file mode 100644 (file)
index 0000000..29fcc07
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+MALLOC_OPTIONS=J ${SH} -c 'g() { g() { :; }; :; }; g' &&
+MALLOC_OPTIONS=J ${SH} -c 'g() { unset -f g; :; }; g'
diff --git a/sh/tests/execution/func2.0 b/sh/tests/execution/func2.0
new file mode 100644 (file)
index 0000000..9830b5e
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+# The empty pairs of braces here are to test that this does not cause a crash.
+
+f() { }
+f
+hash -v f >/dev/null
+f() { { }; }
+f
+hash -v f >/dev/null
+f() { { } }
+f
+hash -v f >/dev/null
diff --git a/sh/tests/execution/func3.0 b/sh/tests/execution/func3.0
new file mode 100644 (file)
index 0000000..e0ed581
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+# This may fail when parsing or when defining the function, or the definition
+# may silently do nothing. In no event may the function be executed.
+
+${SH} -c 'unset() { echo overriding function executed, bad; }; v=1; unset v; exit "${v-0}"' 2>/dev/null
+:
diff --git a/sh/tests/execution/hash1.0 b/sh/tests/execution/hash1.0
new file mode 100644 (file)
index 0000000..a645c2a
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+T=`mktemp -d "${TMPDIR:-/tmp}/sh-test.XXXXXXXX"` || exit
+trap 'rm -rf "${T}"' 0
+PATH=$T:$PATH
+ls -ld . >/dev/null
+cat <<EOF >"$T/ls"
+:
+EOF
+chmod 755 "$T/ls"
+PATH=$PATH
+ls -ld .
diff --git a/sh/tests/execution/int-cmd1.0 b/sh/tests/execution/int-cmd1.0
new file mode 100644 (file)
index 0000000..a1f097b
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+! echo echo bad | $SH -ic 'fi' 2>/dev/null
diff --git a/sh/tests/execution/killed1.0 b/sh/tests/execution/killed1.0
new file mode 100644 (file)
index 0000000..41d3e05
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+# Sometimes the "Killed" message is not flushed soon enough and it
+# is redirected along with the output of a builtin.
+# Do not change the semicolon to a newline as it would hide the bug.
+
+exec 3>&1
+exec >/dev/null 2>&1
+${SH} -c 'kill -9 $$'; : >&3 2>&3
diff --git a/sh/tests/execution/killed2.0 b/sh/tests/execution/killed2.0
new file mode 100644 (file)
index 0000000..7ff3fe2
--- /dev/null
@@ -0,0 +1,10 @@
+# $FreeBSD$
+# Most shells print a message when a foreground job is killed by a signal.
+# POSIX allows this, provided the message is sent to stderr, not stdout.
+# Some trickery is needed to capture the message as redirecting stderr of
+# the command itself does not affect it. The colon command ensures that
+# the subshell forks for ${SH}.
+
+exec 3>&1
+r=`(${SH} -c 'kill $$'; :) 2>&1 >&3`
+[ -n "$r" ]
diff --git a/sh/tests/execution/not1.0 b/sh/tests/execution/not1.0
new file mode 100644 (file)
index 0000000..12c6265
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+f() { ! return $1; }
+f 0 && ! f 1
diff --git a/sh/tests/execution/not2.0 b/sh/tests/execution/not2.0
new file mode 100644 (file)
index 0000000..1b128d0
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+while :; do
+       ! break
+       exit 3
+done
diff --git a/sh/tests/execution/path1.0 b/sh/tests/execution/path1.0
new file mode 100644 (file)
index 0000000..50829d6
--- /dev/null
@@ -0,0 +1,15 @@
+# $FreeBSD$
+# Some builtins should not be overridable via PATH.
+
+set -e
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX)
+trap 'rm -rf ${T}' 0
+echo '#!/bin/sh
+echo bad' >"$T/cd"
+chmod 755 "$T/cd"
+cd /bin
+oPATH=$PATH
+PATH=$T:$PATH:%builtin
+cd /
+PATH=$oPATH
+[ "$(pwd)" = / ]
diff --git a/sh/tests/execution/redir1.0 b/sh/tests/execution/redir1.0
new file mode 100644 (file)
index 0000000..dd0011f
--- /dev/null
@@ -0,0 +1,27 @@
+# $FreeBSD$
+trap ': $((brokenpipe+=1))' PIPE
+
+P=${TMPDIR:-/tmp}
+cd $P
+T=$(mktemp -d sh-test.XXXXXX)
+cd $T
+
+brokenpipe=0
+mkfifo fifo1 fifo2
+read dummy >fifo2 <fifo1 &
+{
+       exec 4>fifo2
+} 3<fifo2 # Formerly, sh would keep fd 3 and a duplicate of it open.
+echo dummy >fifo1
+if [ $brokenpipe -ne 0 ]; then
+       rc=3
+fi
+wait
+echo dummy >&4 2>/dev/null
+if [ $brokenpipe -eq 1 ]; then
+       : ${rc:=0}
+fi
+
+rm fifo1 fifo2
+rmdir ${P}/${T}
+exit ${rc:-3}
diff --git a/sh/tests/execution/redir2.0 b/sh/tests/execution/redir2.0
new file mode 100644 (file)
index 0000000..1588105
--- /dev/null
@@ -0,0 +1,29 @@
+# $FreeBSD$
+trap ': $((brokenpipe+=1))' PIPE
+
+P=${TMPDIR:-/tmp}
+cd $P
+T=$(mktemp -d sh-test.XXXXXX)
+cd $T
+
+brokenpipe=0
+mkfifo fifo1 fifo2
+{
+       {
+               exec ${SH} -c 'exec <fifo1; read dummy'
+       } 7<&- # fifo2 should be kept open, but not passed to programs
+       true
+} 7<fifo2 &
+
+exec 4>fifo2
+exec 3>fifo1
+echo dummy >&4 2>/dev/null
+if [ $brokenpipe -eq 1 ]; then
+       : ${rc:=0}
+fi
+echo dummy >&3
+wait
+
+rm fifo1 fifo2
+rmdir ${P}/${T}
+exit ${rc:-3}
diff --git a/sh/tests/execution/redir3.0 b/sh/tests/execution/redir3.0
new file mode 100644 (file)
index 0000000..d68e450
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+3>&- 3>&-
diff --git a/sh/tests/execution/redir4.0 b/sh/tests/execution/redir4.0
new file mode 100644 (file)
index 0000000..57054c1
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+{ echo bad 0>&3; } 2>/dev/null 3>/dev/null 3>&-
+exit 0
diff --git a/sh/tests/execution/redir5.0 b/sh/tests/execution/redir5.0
new file mode 100644 (file)
index 0000000..707ca68
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+{ (echo bad) >/dev/null; } </dev/null
diff --git a/sh/tests/execution/redir6.0 b/sh/tests/execution/redir6.0
new file mode 100644 (file)
index 0000000..4e3ac0c
--- /dev/null
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if [ "$2" != "$3" ]; then
+               echo "Failure at $1" >&2
+               failures=$((failures + 1))
+       fi
+}
+
+check $LINENO "$(trap "echo bye" EXIT; : >/dev/null)" bye
+check $LINENO "$(trap "echo bye" EXIT; { :; } >/dev/null)" bye
+check $LINENO "$(trap "echo bye" EXIT; (:) >/dev/null)" bye
+check $LINENO "$(trap "echo bye" EXIT; (: >/dev/null))" bye
+check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; : >/dev/null')" bye
+check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; { :; } >/dev/null')" bye
+check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; (:) >/dev/null')" bye
+check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; (: >/dev/null)')" bye
+
+exit $((failures > 0))
diff --git a/sh/tests/execution/redir7.0 b/sh/tests/execution/redir7.0
new file mode 100644 (file)
index 0000000..2487bcf
--- /dev/null
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if [ "$2" != "$3" ]; then
+               echo "Failure at $1" >&2
+               failures=$((failures + 1))
+       fi
+}
+
+check $LINENO "$(trap "echo bye" EXIT; f() { :; }; f >/dev/null)" bye
+check $LINENO "$(trap "echo bye" EXIT; f() { :; }; { f; } >/dev/null)" bye
+check $LINENO "$(trap "echo bye" EXIT; f() { :; }; (f) >/dev/null)" bye
+check $LINENO "$(trap "echo bye" EXIT; f() { :; }; (f >/dev/null))" bye
+check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; f() { :; }; f >/dev/null')" bye
+check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; f() { :; }; { f; } >/dev/null')" bye
+check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; f() { :; }; (f) >/dev/null')" bye
+check $LINENO "$(${SH} -c 'trap "echo bye" EXIT; f() { :; }; (f >/dev/null)')" bye
+
+exit $((failures > 0))
diff --git a/sh/tests/execution/set-n1.0 b/sh/tests/execution/set-n1.0
new file mode 100644 (file)
index 0000000..14c9b93
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+v=$( ($SH -n <<'EOF'
+for
+EOF
+) 2>&1 >/dev/null)
+[ $? -ne 0 ] && [ -n "$v" ]
diff --git a/sh/tests/execution/set-n2.0 b/sh/tests/execution/set-n2.0
new file mode 100644 (file)
index 0000000..c7f3162
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+$SH -n <<'EOF'
+echo bad
+EOF
diff --git a/sh/tests/execution/set-n3.0 b/sh/tests/execution/set-n3.0
new file mode 100644 (file)
index 0000000..24a9159
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+v=$( ($SH -nc 'for') 2>&1 >/dev/null)
+[ $? -ne 0 ] && [ -n "$v" ]
diff --git a/sh/tests/execution/set-n4.0 b/sh/tests/execution/set-n4.0
new file mode 100644 (file)
index 0000000..3698508
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+$SH -nc 'echo bad'
diff --git a/sh/tests/execution/set-x1.0 b/sh/tests/execution/set-x1.0
new file mode 100644 (file)
index 0000000..7fe1dbf
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+key='must_contain_this'
+{ r=`set -x; { : "$key"; } 2>&1 >/dev/null`; } 2>/dev/null
+case $r in
+*"$key"*) true ;;
+*) false ;;
+esac
diff --git a/sh/tests/execution/set-x2.0 b/sh/tests/execution/set-x2.0
new file mode 100644 (file)
index 0000000..56d54e3
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+key='must contain this'
+PS4="$key+ "
+{ r=`set -x; { :; } 2>&1 >/dev/null`; } 2>/dev/null
+case $r in
+*"$key"*) true ;;
+*) false ;;
+esac
diff --git a/sh/tests/execution/set-x3.0 b/sh/tests/execution/set-x3.0
new file mode 100644 (file)
index 0000000..1ca57ac
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+key='must contain this'
+PS4='$key+ '
+{ r=`set -x; { :; } 2>&1 >/dev/null`; } 2>/dev/null
+case $r in
+*"$key"*) true ;;
+*) false ;;
+esac
diff --git a/sh/tests/execution/set-x4.0 b/sh/tests/execution/set-x4.0
new file mode 100644 (file)
index 0000000..0904766
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+key=`printf '\r\t\001\200\300'`
+r=`{ set -x; : "$key"; } 2>&1 >/dev/null`
+case $r in
+*[![:print:]]*) echo fail; exit 3
+esac
diff --git a/sh/tests/execution/shellproc1.0 b/sh/tests/execution/shellproc1.0
new file mode 100644 (file)
index 0000000..1326bc2
--- /dev/null
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+T=`mktemp -d "${TMPDIR:-/tmp}/sh-test.XXXXXXXX"` || exit
+trap 'rm -rf "${T}"' 0
+cat <<EOF >"$T/testshellproc"
+printf 'this '
+echo is a test
+EOF
+chmod 755 "$T/testshellproc"
+PATH=$T:$PATH
+[ "`testshellproc`" = "this is a test" ]
diff --git a/sh/tests/execution/subshell1.0 b/sh/tests/execution/subshell1.0
new file mode 100644 (file)
index 0000000..347806e
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+(eval "cd /
+v=$(printf %0100000d 1)
+echo \${#v}")
+echo end
diff --git a/sh/tests/execution/subshell1.0.stdout b/sh/tests/execution/subshell1.0.stdout
new file mode 100644 (file)
index 0000000..8c71af3
--- /dev/null
@@ -0,0 +1,2 @@
+100000
+end
diff --git a/sh/tests/execution/subshell2.0 b/sh/tests/execution/subshell2.0
new file mode 100644 (file)
index 0000000..3216449
--- /dev/null
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+f() {
+       x=2
+}
+(
+       x=1
+       f
+       [ "$x" = 2 ]
+)
diff --git a/sh/tests/execution/subshell3.0 b/sh/tests/execution/subshell3.0
new file mode 100644 (file)
index 0000000..9a87acb
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+(false; exit) && exit 3
+exit 0
diff --git a/sh/tests/execution/subshell4.0 b/sh/tests/execution/subshell4.0
new file mode 100644 (file)
index 0000000..b39edb1
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+(eval "set v=1"; false) && echo bad; :
diff --git a/sh/tests/execution/unknown1.0 b/sh/tests/execution/unknown1.0
new file mode 100644 (file)
index 0000000..45f541e
--- /dev/null
@@ -0,0 +1,29 @@
+# $FreeBSD$
+
+nosuchtool 2>/dev/null
+[ $? -ne 127 ] && exit 1
+/var/empty/nosuchtool 2>/dev/null
+[ $? -ne 127 ] && exit 1
+(nosuchtool) 2>/dev/null
+[ $? -ne 127 ] && exit 1
+(/var/empty/nosuchtool) 2>/dev/null
+[ $? -ne 127 ] && exit 1
+/ 2>/dev/null
+[ $? -ne 126 ] && exit 1
+PATH=/usr bin 2>/dev/null
+[ $? -ne 126 ] && exit 1
+
+dummy=$(nosuchtool 2>/dev/null)
+[ $? -ne 127 ] && exit 1
+dummy=$(/var/empty/nosuchtool 2>/dev/null)
+[ $? -ne 127 ] && exit 1
+dummy=$( (nosuchtool) 2>/dev/null)
+[ $? -ne 127 ] && exit 1
+dummy=$( (/var/empty/nosuchtool) 2>/dev/null)
+[ $? -ne 127 ] && exit 1
+dummy=$(/ 2>/dev/null)
+[ $? -ne 126 ] && exit 1
+dummy=$(PATH=/usr bin 2>/dev/null)
+[ $? -ne 126 ] && exit 1
+
+exit 0
diff --git a/sh/tests/execution/var-assign1.0 b/sh/tests/execution/var-assign1.0
new file mode 100644 (file)
index 0000000..26e5424
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+[ "$(HOME=/etc HOME=/ cd && pwd)" = / ]
diff --git a/sh/tests/expansion/Makefile b/sh/tests/expansion/Makefile
new file mode 100644 (file)
index 0000000..e359ae7
--- /dev/null
@@ -0,0 +1,94 @@
+# $FreeBSD$
+
+TESTSDIR=      ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH=  functional_test
+
+FILESDIR=      ${TESTSDIR}
+
+FILES=         arith1.0
+FILES+=                arith2.0
+FILES+=                arith3.0
+FILES+=                arith4.0
+FILES+=                arith5.0
+FILES+=                arith6.0
+FILES+=                arith7.0
+FILES+=                arith8.0
+FILES+=                arith9.0
+FILES+=                arith10.0
+FILES+=                arith11.0
+FILES+=                arith12.0
+FILES+=                arith13.0
+FILES+=                arith14.0
+FILES+=                assign1.0
+FILES+=                cmdsubst1.0
+FILES+=                cmdsubst2.0
+FILES+=                cmdsubst3.0
+FILES+=                cmdsubst4.0
+FILES+=                cmdsubst5.0
+FILES+=                cmdsubst6.0
+FILES+=                cmdsubst7.0
+FILES+=                cmdsubst8.0
+FILES+=                cmdsubst9.0
+FILES+=                cmdsubst10.0
+FILES+=                cmdsubst11.0
+FILES+=                cmdsubst12.0
+FILES+=                cmdsubst13.0
+FILES+=                cmdsubst14.0
+FILES+=                cmdsubst15.0
+FILES+=                cmdsubst16.0
+FILES+=                cmdsubst17.0
+FILES+=                export1.0
+FILES+=                export2.0
+FILES+=                export3.0
+FILES+=                heredoc1.0
+FILES+=                heredoc2.0
+FILES+=                ifs1.0
+FILES+=                ifs2.0
+FILES+=                ifs3.0
+FILES+=                ifs4.0
+FILES+=                ifs5.0
+FILES+=                ifs6.0
+FILES+=                ifs7.0
+FILES+=                length1.0
+FILES+=                length2.0
+FILES+=                length3.0
+FILES+=                length4.0
+FILES+=                length5.0
+FILES+=                length6.0
+FILES+=                length7.0
+FILES+=                length8.0
+FILES+=                local1.0
+FILES+=                local2.0
+FILES+=                pathname1.0
+FILES+=                pathname2.0
+FILES+=                pathname3.0
+FILES+=                pathname4.0
+FILES+=                pathname5.0
+FILES+=                plus-minus1.0
+FILES+=                plus-minus2.0
+FILES+=                plus-minus3.0
+FILES+=                plus-minus4.0
+FILES+=                plus-minus5.0
+FILES+=                plus-minus6.0
+FILES+=                plus-minus7.0
+FILES+=                plus-minus8.0
+FILES+=                question1.0
+FILES+=                readonly1.0
+FILES+=                redir1.0
+FILES+=                set-u1.0
+FILES+=                set-u2.0
+FILES+=                set-u3.0
+FILES+=                tilde1.0
+FILES+=                tilde2.0
+FILES+=                trim1.0
+FILES+=                trim2.0
+FILES+=                trim3.0
+FILES+=                trim4.0
+FILES+=                trim5.0
+FILES+=                trim6.0
+FILES+=                trim7.0
+FILES+=                trim8.0
+
+.include <bsd.test.mk>
diff --git a/sh/tests/expansion/arith1.0 b/sh/tests/expansion/arith1.0
new file mode 100644 (file)
index 0000000..118ba22
--- /dev/null
@@ -0,0 +1,30 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if [ $(($1)) != $2 ]; then
+               failures=$((failures+1))
+               echo "For $1, expected $2 actual $(($1))"
+       fi
+}
+
+check "0&&0" 0
+check "1&&0" 0
+check "0&&1" 0
+check "1&&1" 1
+check "2&&2" 1
+check "1&&2" 1
+check "1<<40&&1<<40" 1
+check "1<<40&&4" 1
+
+check "0||0" 0
+check "1||0" 1
+check "0||1" 1
+check "1||1" 1
+check "2||2" 1
+check "1||2" 1
+check "1<<40||1<<40" 1
+check "1<<40||4" 1
+
+exit $((failures != 0))
diff --git a/sh/tests/expansion/arith10.0 b/sh/tests/expansion/arith10.0
new file mode 100644 (file)
index 0000000..1aaf619
--- /dev/null
@@ -0,0 +1,35 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if [ $(($1)) != $2 ]; then
+               failures=$((failures+1))
+               echo "For $1, expected $2 actual $(($1))"
+       fi
+}
+
+readonly ro=4
+rw=1
+check "0 && 0 / 0" 0
+check "1 || 0 / 0" 1
+check "0 && (ro = 2)" 0
+check "ro" 4
+check "1 || (ro = -1)" 1
+check "ro" 4
+check "0 && (rw += 1)" 0
+check "rw" 1
+check "1 || (rw += 1)" 1
+check "rw" 1
+check "0 ? 44 / 0 : 51" 51
+check "0 ? ro = 3 : 52" 52
+check "ro" 4
+check "0 ? rw += 1 : 52" 52
+check "rw" 1
+check "1 ? 68 : 30 / 0" 68
+check "2 ? 1 : (ro += 2)" 1
+check "ro" 4
+check "4 ? 1 : (rw += 1)" 1
+check "rw" 1
+
+exit $((failures != 0))
diff --git a/sh/tests/expansion/arith11.0 b/sh/tests/expansion/arith11.0
new file mode 100644 (file)
index 0000000..6bc7369
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+# Try to divide the smallest integer by -1.
+# On amd64 this causes SIGFPE, so make sure the shell checks.
+
+# Calculate the minimum possible value, assuming two's complement and
+# a certain interpretation of overflow when shifting left.
+minint=1
+while [ $((minint <<= 1)) -gt 0 ]; do
+       :
+done
+v=$( eval ': $((minint / -1))' 2>&1 >/dev/null)
+[ $? -ne 0 ] && [ -n "$v" ]
diff --git a/sh/tests/expansion/arith12.0 b/sh/tests/expansion/arith12.0
new file mode 100644 (file)
index 0000000..cb7da3b
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+_x=4 y_=5 z_z=6
+[ "$((_x*100+y_*10+z_z))" = 456 ]
diff --git a/sh/tests/expansion/arith13.0 b/sh/tests/expansion/arith13.0
new file mode 100644 (file)
index 0000000..207e488
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+# Pre-increment and pre-decrement in arithmetic expansion are not in POSIX.
+# Require either an error or a correct implementation.
+
+! (eval 'x=4; [ $((++x)) != 5 ] || [ $x != 5 ]') 2>/dev/null &&
+! (eval 'x=2; [ $((--x)) != 1 ] || [ $x != 1 ]') 2>/dev/null
diff --git a/sh/tests/expansion/arith14.0 b/sh/tests/expansion/arith14.0
new file mode 100644 (file)
index 0000000..8369043
--- /dev/null
@@ -0,0 +1,40 @@
+# $FreeBSD$
+# Check that <</>> use the low bits of the shift count.
+
+if [ $((1<<16<<16)) = 0 ]; then
+       width=32
+elif [ $((1<<32<<32)) = 0 ]; then
+       width=64
+elif [ $((1<<64<<64)) = 0 ]; then
+       width=128
+elif [ $((1<<64>>64)) = 1 ]; then
+       # Integers are wider than 128 bits; assume arbitrary precision.
+       # Nothing to test here.
+       exit 0
+else
+       echo "Cannot determine integer width"
+       exit 2
+fi
+
+twowidth=$((width * 2))
+j=43 k=$((1 << (width - 2))) r=0
+
+i=0
+while [ $i -lt $twowidth ]; do
+       if [ "$((j << i))" != "$((j << (i + width)))" ]; then
+               echo "Problem with $j << $i"
+               r=2
+       fi
+       i=$((i + 1))
+done
+
+i=0
+while [ $i -lt $twowidth ]; do
+       if [ "$((k >> i))" != "$((k >> (i + width)))" ]; then
+               echo "Problem with $k >> $i"
+               r=2
+       fi
+       i=$((i + 1))
+done
+
+exit $r
diff --git a/sh/tests/expansion/arith2.0 b/sh/tests/expansion/arith2.0
new file mode 100644 (file)
index 0000000..95b48a0
--- /dev/null
@@ -0,0 +1,77 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if [ $(($1)) != $2 ]; then
+               failures=$((failures+1))
+               echo "For $1, expected $2 actual $(($1))"
+       fi
+}
+
+# variables
+unset v
+check "v=2" 2
+check "v" 2
+check "$(($v))" 2
+check "v+=1" 3
+check "v" 3
+
+# constants
+check "4611686018427387904" 4611686018427387904
+check "0x4000000000000000" 4611686018427387904
+check "0400000000000000000000" 4611686018427387904
+check "0x4Ab0000000000000" 5381801554707742720
+check "010" 8
+
+# try out all operators
+v=42
+check "!v" 0
+check "!!v" 1
+check "!0" 1
+check "~0" -1
+check "~(-1)" 0
+check "-0" 0
+check "-v" -42
+check "v*v" 1764
+check "v/2" 21
+check "v%10" 2
+check "v+v" 84
+check "v-4" 38
+check "v<<1" 84
+check "v>>1" 21
+check "v<43" 1
+check "v>42" 0
+check "v<=43" 1
+check "v>=43" 0
+check "v==41" 0
+check "v!=42" 0
+check "v&3" 2
+check "v^3" 41
+check "v|3" 43
+check "v>=40&&v<=44" 1
+check "v<40||v>44" 0
+check "(v=42)&&(v+=1)==43" 1
+check "v" 43
+check "(v=42)&&(v-=1)==41" 1
+check "v" 41
+check "(v=42)&&(v*=2)==84" 1
+check "v" 84
+check "(v=42)&&(v/=10)==4" 1
+check "v" 4
+check "(v=42)&&(v%=10)==2" 1
+check "v" 2
+check "(v=42)&&(v<<=1)==84" 1
+check "v" 84
+check "(v=42)&&(v>>=2)==10" 1
+check "v" 10
+check "(v=42)&&(v&=32)==32" 1
+check "v" 32
+check "(v=42)&&(v^=32)==10" 1
+check "v" 10
+check "(v=42)&&(v|=32)==42" 1
+check "v" 42
+
+# missing: ternary
+
+exit $((failures != 0))
diff --git a/sh/tests/expansion/arith3.0 b/sh/tests/expansion/arith3.0
new file mode 100644 (file)
index 0000000..b69159d
--- /dev/null
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if [ $(($1)) != $2 ]; then
+               failures=$((failures+1))
+               echo "For $1, expected $2 actual $(($1))"
+       fi
+}
+
+check "1 << 1 + 1 | 1" 5
+
+exit $((failures != 0))
diff --git a/sh/tests/expansion/arith4.0 b/sh/tests/expansion/arith4.0
new file mode 100644 (file)
index 0000000..610dad8
--- /dev/null
@@ -0,0 +1,20 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if [ $(($1)) != $2 ]; then
+               failures=$((failures+1))
+               echo "For $1, expected $2 actual $(($1))"
+       fi
+}
+
+check '20 / 2 / 2' 5
+check '20 - 2 - 2' 16
+unset a b c d
+check "a = b = c = d = 1" 1
+check "a == 1 && b == 1 && c == 1 && d == 1" 1
+check "a += b += c += d" 4
+check "a == 4 && b == 3 && c == 2 && d == 1" 1
+
+exit $((failures != 0))
diff --git a/sh/tests/expansion/arith5.0 b/sh/tests/expansion/arith5.0
new file mode 100644 (file)
index 0000000..d0f2331
--- /dev/null
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if [ "$2" != "$3" ]; then
+               failures=$((failures+1))
+               echo "For $1, expected $3 actual $2"
+       fi
+}
+
+unset a
+check '$((1+${a:-$((7+2))}))' "$((1+${a:-$((7+2))}))" 10
+check '$((1+${a:=$((2+2))}))' "$((1+${a:=$((2+2))}))" 5
+check '$a' "$a" 4
+
+exit $((failures != 0))
diff --git a/sh/tests/expansion/arith6.0 b/sh/tests/expansion/arith6.0
new file mode 100644 (file)
index 0000000..fc4589c
--- /dev/null
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+v1=1\ +\ 1
+v2=D
+v3=C123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+f() { v4="$*"; }
+
+while [ ${#v2} -lt 1250 ]; do
+       eval $v2=$((3+${#v2})) $v3=$((4-${#v2}))
+       eval f $(($v2+ $v1 +$v3))
+       if [ $v4 -ne 9 ]; then
+               echo bad: $v4 -ne 9 at ${#v2}
+       fi
+       v2=x$v2
+       v3=y$v3
+done
diff --git a/sh/tests/expansion/arith7.0 b/sh/tests/expansion/arith7.0
new file mode 100644 (file)
index 0000000..5aada2b
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+v=1+
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+[ "$(cat <<EOF
+$(($v 1))
+EOF
+)" = 1025 ]
diff --git a/sh/tests/expansion/arith8.0 b/sh/tests/expansion/arith8.0
new file mode 100644 (file)
index 0000000..2d03e50
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+v=$( (eval ': $((08))') 2>&1 >/dev/null)
+[ $? -ne 0 ] && [ -n "$v" ]
diff --git a/sh/tests/expansion/arith9.0 b/sh/tests/expansion/arith9.0
new file mode 100644 (file)
index 0000000..cc8b597
--- /dev/null
@@ -0,0 +1,20 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if [ $(($1)) != $2 ]; then
+               failures=$((failures+1))
+               echo "For $1, expected $2 actual $(($1))"
+       fi
+}
+
+check "0 ? 44 : 51" 51
+check "1 ? 68 : 30" 68
+check "2 ? 1 : -5" 1
+check "0 ? 4 : 0 ? 5 : 6" 6
+check "0 ? 4 : 1 ? 5 : 6" 5
+check "1 ? 4 : 0 ? 5 : 6" 4
+check "1 ? 4 : 1 ? 5 : 6" 4
+
+exit $((failures != 0))
diff --git a/sh/tests/expansion/assign1.0 b/sh/tests/expansion/assign1.0
new file mode 100644 (file)
index 0000000..d4fa772
--- /dev/null
@@ -0,0 +1,37 @@
+# $FreeBSD$
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+       code="$1"
+       expected="$2"
+       oIFS="$IFS"
+       eval "$code"
+       IFS='|'
+       result="$#|$*"
+       IFS="$oIFS"
+       if [ "x$result" = "x$expected" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $code, expected $expected actual $result"
+       fi
+}
+
+testcase 'v=; set -- ${v=a b} $v'              '0|'
+testcase 'unset v; set -- ${v=a b} $v'         '4|a|b|a|b'
+testcase 'v=; set -- ${v:=a b} $v'             '4|a|b|a|b'
+testcase 'v=; set -- "${v:=a b}" "$v"'         '2|a b|a b'
+# expect sensible behaviour, although it disagrees with POSIX
+testcase 'v=; set -- ${v:=a\ b} $v'            '4|a|b|a|b'
+testcase 'v=; set -- ${v:=$p} $v'              '2|/etc/|/etc/'
+testcase 'v=; set -- "${v:=$p}" "$v"'          '2|/et[c]/|/et[c]/'
+testcase 'v=; set -- "${v:=a\ b}" "$v"'                '2|a\ b|a\ b'
+testcase 'v=; set -- ${v:="$p"} $v'            '2|/etc/|/etc/'
+# whether $p is quoted or not shouldn't really matter
+testcase 'v=; set -- "${v:="$p"}" "$v"'                '2|/et[c]/|/et[c]/'
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/cmdsubst1.0 b/sh/tests/expansion/cmdsubst1.0
new file mode 100644 (file)
index 0000000..515c7da
--- /dev/null
@@ -0,0 +1,48 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+check '"$(echo abcde)" = "abcde"'
+check '"$(echo abcde; :)" = "abcde"'
+
+check '"$(printf abcde)" = "abcde"'
+check '"$(printf abcde; :)" = "abcde"'
+
+# regular
+check '-n "$(umask)"'
+check '-n "$(umask; :)"'
+check '-n "$(umask 2>&1)"'
+check '-n "$(umask 2>&1; :)"'
+
+# special
+check '-n "$(times)"'
+check '-n "$(times; :)"'
+check '-n "$(times 2>&1)"'
+check '-n "$(times 2>&1; :)"'
+
+# regular
+check '".$(umask -@ 2>&1)." = ".umask: Illegal option -@."'
+check '".$(umask -@ 2>&1; :)." = ".umask: Illegal option -@."'
+check '".$({ umask -@; } 2>&1)." = ".umask: Illegal option -@."'
+
+# special
+check '".$(shift xyz 2>&1)." = ".shift: Illegal number: xyz."'
+check '".$(shift xyz 2>&1; :)." = ".shift: Illegal number: xyz."'
+check '".$({ shift xyz; } 2>&1)." = ".shift: Illegal number: xyz."'
+
+v=1
+check '-z "$(v=2 :)"'
+check '"$v" = 1'
+check '-z "$(v=3)"'
+check '"$v" = 1'
+check '"$(v=4 eval echo \$v)" = 4'
+check '"$v" = 1'
+
+exit $((failures > 0))
diff --git a/sh/tests/expansion/cmdsubst10.0 b/sh/tests/expansion/cmdsubst10.0
new file mode 100644 (file)
index 0000000..7cf17a3
--- /dev/null
@@ -0,0 +1,51 @@
+# $FreeBSD$
+
+a1=$(alias)
+: $(alias testalias=abcd)
+a2=$(alias)
+[ "$a1" = "$a2" ] || echo Error at line $LINENO
+
+alias testalias2=abcd
+a1=$(alias)
+: $(unalias testalias2)
+a2=$(alias)
+[ "$a1" = "$a2" ] || echo Error at line $LINENO
+
+[ "$(command -V pwd)" = "$(command -V pwd; exit $?)" ] || echo Error at line $LINENO
+
+v=1
+: $(export v=2)
+[ "$v" = 1 ] || echo Error at line $LINENO
+
+rotest=1
+: $(readonly rotest=2)
+[ "$rotest" = 1 ] || echo Error at line $LINENO
+
+set +u
+: $(set -u)
+case $- in
+*u*) echo Error at line $LINENO ;;
+esac
+set +u
+
+set +u
+: $(set -o nounset)
+case $- in
+*u*) echo Error at line $LINENO ;;
+esac
+set +u
+
+set +u
+: $(command set -u)
+case $- in
+*u*) echo Error at line $LINENO ;;
+esac
+set +u
+
+umask 77
+u1=$(umask)
+: $(umask 022)
+u2=$(umask)
+[ "$u1" = "$u2" ] || echo Error at line $LINENO
+
+dummy=$(exit 3); [ $? -eq 3 ] || echo Error at line $LINENO
diff --git a/sh/tests/expansion/cmdsubst11.0 b/sh/tests/expansion/cmdsubst11.0
new file mode 100644 (file)
index 0000000..f1af547
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+# Not required by POSIX but useful for efficiency.
+
+[ $$ = $(eval '${SH} -c echo\ \$PPID') ]
diff --git a/sh/tests/expansion/cmdsubst12.0 b/sh/tests/expansion/cmdsubst12.0
new file mode 100644 (file)
index 0000000..50394db
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+f() {
+       echo x$(printf foo >&2)y
+}
+[ "$(f 2>&1)" = "fooxy" ]
diff --git a/sh/tests/expansion/cmdsubst13.0 b/sh/tests/expansion/cmdsubst13.0
new file mode 100644 (file)
index 0000000..7fdc5b2
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+x=1 y=2
+[ "$(
+       case $((x+=1)) in
+       ($((y+=1)))     echo bad1 ;;
+       ($((y-1)))      echo $x.$y ;;
+       ($((y=2)))      echo bad2 ;;
+       (*)             echo bad3 ;;
+       esac
+)" = "2.3" ] || echo "Error at $LINENO"
+[ "$x.$y" = "1.2" ] || echo "Error at $LINENO"
diff --git a/sh/tests/expansion/cmdsubst14.0 b/sh/tests/expansion/cmdsubst14.0
new file mode 100644 (file)
index 0000000..bdbbb82
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+! v=`false
+
+`
diff --git a/sh/tests/expansion/cmdsubst15.0 b/sh/tests/expansion/cmdsubst15.0
new file mode 100644 (file)
index 0000000..31d85d4
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+! v=`false;
+
+`
diff --git a/sh/tests/expansion/cmdsubst16.0 b/sh/tests/expansion/cmdsubst16.0
new file mode 100644 (file)
index 0000000..71df562
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+f() { return 3; }
+f
+[ `echo $?` = 3 ]
diff --git a/sh/tests/expansion/cmdsubst17.0 b/sh/tests/expansion/cmdsubst17.0
new file mode 100644 (file)
index 0000000..8c29e83
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+f() { return 3; }
+f
+[ `echo $?; :` = 3 ]
diff --git a/sh/tests/expansion/cmdsubst2.0 b/sh/tests/expansion/cmdsubst2.0
new file mode 100644 (file)
index 0000000..b86776e
--- /dev/null
@@ -0,0 +1,43 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+check '`echo /et[c]/` = "/etc/"'
+check '`printf /var/empty%s /et[c]/` = "/var/empty/etc/"'
+check '"`echo /et[c]/`" = "/etc/"'
+check '`echo "/et[c]/"` = "/etc/"'
+check '`printf /var/empty%s "/et[c]/"` = "/var/empty/et[c]/"'
+check '`printf /var/empty/%s \"/et[c]/\"` = "/var/empty/\"/et[c]/\""'
+check '"`echo \"/et[c]/\"`" = "/et[c]/"'
+check '"`echo "/et[c]/"`" = "/et[c]/"'
+check '`echo $$` = $$'
+check '"`echo $$`" = $$'
+check '`echo \$\$` = $$'
+check '"`echo \$\$`" = $$'
+
+# Command substitutions consisting of a single builtin may be treated
+# differently.
+check '`:; echo /et[c]/` = "/etc/"'
+check '`:; printf /var/empty%s /et[c]/` = "/var/empty/etc/"'
+check '"`:; echo /et[c]/`" = "/etc/"'
+check '`:; echo "/et[c]/"` = "/etc/"'
+check '`:; printf /var/empty%s "/et[c]/"` = "/var/empty/et[c]/"'
+check '`:; printf /var/empty/%s \"/et[c]/\"` = "/var/empty/\"/et[c]/\""'
+check '"`:; echo \"/et[c]/\"`" = "/et[c]/"'
+check '"`:; echo "/et[c]/"`" = "/et[c]/"'
+check '`:; echo $$` = $$'
+check '"`:; echo $$`" = $$'
+check '`:; echo \$\$` = $$'
+check '"`:; echo \$\$`" = $$'
+
+check '`set -f; echo /et[c]/` = "/etc/"'
+check '"`set -f; echo /et[c]/`" = "/et[c]/"'
+
+exit $((failures > 0))
diff --git a/sh/tests/expansion/cmdsubst3.0 b/sh/tests/expansion/cmdsubst3.0
new file mode 100644 (file)
index 0000000..abb6b22
--- /dev/null
@@ -0,0 +1,23 @@
+# $FreeBSD$
+
+unset LC_ALL
+export LC_CTYPE=en_US.ISO8859-1
+
+e=
+for i in 0 1 2 3; do
+       for j in 0 1 2 3 4 5 6 7; do
+               for k in 0 1 2 3 4 5 6 7; do
+                       case $i$j$k in
+                       000) continue ;;
+                       esac
+                       e="$e\n\\$i$j$k"
+               done
+       done
+done
+e1=$(printf "$e")
+e2="$(printf "$e")"
+[ "${#e1}" = 510 ] || echo length bad
+[ "$e1" = "$e2" ] || echo e1 != e2
+[ "$e1" = "$(printf "$e")" ] || echo quoted bad
+IFS=
+[ "$e1" = $(printf "$e") ] || echo unquoted bad
diff --git a/sh/tests/expansion/cmdsubst4.0 b/sh/tests/expansion/cmdsubst4.0
new file mode 100644 (file)
index 0000000..ee1ce73
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+exec 2>/dev/null
+! y=$(: </var/empty/nonexistent)
diff --git a/sh/tests/expansion/cmdsubst5.0 b/sh/tests/expansion/cmdsubst5.0
new file mode 100644 (file)
index 0000000..afca371
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+unset v
+exec 2>/dev/null
+! y=$(: ${v?})
diff --git a/sh/tests/expansion/cmdsubst6.0 b/sh/tests/expansion/cmdsubst6.0
new file mode 100644 (file)
index 0000000..6586f33
--- /dev/null
@@ -0,0 +1,53 @@
+# $FreeBSD$
+# This tests if the cmdsubst optimization is still used if possible.
+
+failures=''
+ok=''
+
+testcase() {
+       code="$1"
+
+       unset v
+       eval "pid=\$(dummy=$code echo \$(\$SH -c echo\ \\\$PPID))"
+
+       if [ "$pid" = "$$" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "Failure for $code"
+       fi
+}
+
+unset v
+w=1
+testcase '$w'
+testcase '1${w+1}'
+testcase '1${w-1}'
+testcase '1${v+1}'
+testcase '1${v-1}'
+testcase '1${w:+1}'
+testcase '1${w:-1}'
+testcase '1${v:+1}'
+testcase '1${v:-1}'
+testcase '${w?}'
+testcase '${w:?}'
+testcase '${w#x}'
+testcase '${w##x}'
+testcase '${w%x}'
+testcase '${w%%x}'
+
+testcase '$((w))'
+testcase '$(((w+4)*2/3))'
+testcase '$((w==1))'
+testcase '$((w>=0 && w<=5 && w!=2))'
+testcase '$((${#w}))'
+testcase '$((${#IFS}))'
+testcase '$((${#w}>=1))'
+testcase '$(($$))'
+testcase '$(($#))'
+testcase '$(($?))'
+
+testcase '$(: $((w=4)))'
+testcase '$(: ${v=2})'
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/cmdsubst7.0 b/sh/tests/expansion/cmdsubst7.0
new file mode 100644 (file)
index 0000000..dbd1aec
--- /dev/null
@@ -0,0 +1,31 @@
+# $FreeBSD$
+
+failures=''
+ok=''
+
+testcase() {
+       code="$1"
+
+       unset v
+       eval ": \$($code)"
+
+       if [ "${v:+bad}" = "" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "Failure for $code"
+       fi
+}
+
+testcase ': ${v=0}'
+testcase ': ${v:=0}'
+testcase ': $((v=1))'
+testcase ': $((v+=1))'
+w='v=1'
+testcase ': $(($w))'
+testcase ': $((${$+v=1}))'
+testcase ': $((v${$+=1}))'
+testcase ': $((v $(echo =) 1))'
+testcase ': $(($(echo $w)))'
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/cmdsubst8.0 b/sh/tests/expansion/cmdsubst8.0
new file mode 100644 (file)
index 0000000..52adaea
--- /dev/null
@@ -0,0 +1,17 @@
+# $FreeBSD$
+# Not required by POSIX (although referenced in a non-normative section),
+# but possibly useful.
+
+: hi there &
+p=$!
+q=$(jobs -l $p)
+
+# Change tabs to spaces.
+set -f
+set -- $q
+r="$*"
+
+case $r in
+*" $p "*) ;;
+*) echo Pid missing; exit 3 ;;
+esac
diff --git a/sh/tests/expansion/cmdsubst9.0 b/sh/tests/expansion/cmdsubst9.0
new file mode 100644 (file)
index 0000000..0b1f81f
--- /dev/null
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+set -e
+
+cd /
+dummy=$(cd /bin)
+[ "$(pwd)" = / ]
+
+v=1
+dummy=$(eval v=2)
+[ "$v" = 1 ]
diff --git a/sh/tests/expansion/export1.0 b/sh/tests/expansion/export1.0
new file mode 100644 (file)
index 0000000..4ee3ef4
--- /dev/null
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+w='@ vv=6'
+
+v=0 vv=0
+export \v=$w
+[ "$v" = "@" ] || echo "Expected @ got $v"
+[ "$vv" = "6" ] || echo "Expected 6 got $vv"
+
+HOME=/known/value
+
+export \v=~
+[ "$v" = \~ ] || echo "Expected ~ got $v"
diff --git a/sh/tests/expansion/export2.0 b/sh/tests/expansion/export2.0
new file mode 100644 (file)
index 0000000..57f64e7
--- /dev/null
@@ -0,0 +1,24 @@
+# $FreeBSD$
+
+w='@ @'
+check() {
+       [ "$v" = "$w" ] || echo "Expected $w got $v"
+}
+
+export v=$w
+check
+
+HOME=/known/value
+check() {
+       [ "$v" = ~ ] || echo "Expected $HOME got $v"
+}
+
+export v=~
+check
+
+check() {
+       [ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v"
+}
+
+export v=x:~
+check
diff --git a/sh/tests/expansion/export3.0 b/sh/tests/expansion/export3.0
new file mode 100644 (file)
index 0000000..a1a0e66
--- /dev/null
@@ -0,0 +1,30 @@
+# $FreeBSD$
+
+w='@ @'
+check() {
+       [ "$v" = "$w" ] || echo "Expected $w got $v"
+}
+
+command export v=$w
+check
+command command export v=$w
+check
+
+HOME=/known/value
+check() {
+       [ "$v" = ~ ] || echo "Expected $HOME got $v"
+}
+
+command export v=~
+check
+command command export v=~
+check
+
+check() {
+       [ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v"
+}
+
+command export v=x:~
+check
+command command export v=x:~
+check
diff --git a/sh/tests/expansion/heredoc1.0 b/sh/tests/expansion/heredoc1.0
new file mode 100644 (file)
index 0000000..a67b2da
--- /dev/null
@@ -0,0 +1,25 @@
+# $FreeBSD$
+
+f() { return $1; }
+
+[ `f 42; { cat; } <<EOF
+$?
+EOF
+` = 42 ] || echo compound command bad
+
+[ `f 42; (cat) <<EOF
+$?
+EOF
+` = 42 ] || echo subshell bad
+
+long=`printf %08192d 0`
+
+[ `f 42; { cat; } <<EOF
+$long.$?
+EOF
+` = $long.42 ] || echo long compound command bad
+
+[ `f 42; (cat) <<EOF
+$long.$?
+EOF
+` = $long.42 ] || echo long subshell bad
diff --git a/sh/tests/expansion/heredoc2.0 b/sh/tests/expansion/heredoc2.0
new file mode 100644 (file)
index 0000000..2551432
--- /dev/null
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+f() { return $1; }
+
+[ `f 42; cat <<EOF
+$?
+EOF
+` = 42 ] || echo simple command bad
+
+long=`printf %08192d 0`
+
+[ `f 42; cat <<EOF
+$long.$?
+EOF
+` = $long.42 ] || echo long simple command bad
diff --git a/sh/tests/expansion/ifs1.0 b/sh/tests/expansion/ifs1.0
new file mode 100644 (file)
index 0000000..e7f53c7
--- /dev/null
@@ -0,0 +1,35 @@
+# $FreeBSD$
+
+c=: e= s=' '
+failures=''
+ok=''
+
+check_result() {
+       if [ "x$2" = "x$3" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $1, expected $3 actual $2"
+       fi
+}
+
+IFS='  
+'
+set -- a ''
+set -- "$@"
+check_result 'set -- "$@"' "($#)($1)($2)" "(2)(a)()"
+
+set -- a ''
+set -- "$@"$e
+check_result 'set -- "$@"$e' "($#)($1)($2)" "(2)(a)()"
+
+set -- a ''
+set -- "$@"$s
+check_result 'set -- "$@"$s' "($#)($1)($2)" "(2)(a)()"
+
+IFS="$c"
+set -- a ''
+set -- "$@"$c
+check_result 'set -- "$@"$c' "($#)($1)($2)" "(2)(a)()"
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/ifs2.0 b/sh/tests/expansion/ifs2.0
new file mode 100644 (file)
index 0000000..e91b867
--- /dev/null
@@ -0,0 +1,24 @@
+# $FreeBSD$
+
+failures=0
+i=1
+set -f
+while [ "$i" -le 127 ]; do
+       # A different byte still in the range 1..127.
+       i2=$((i^2+(i==2)))
+       # Add a character to work around command substitution's removal of
+       # final newlines, then remove it again.
+       c=$(printf \\"$(printf %o@ "$i")")
+       c=${c%@}
+       c2=$(printf \\"$(printf %o@ "$i2")")
+       c2=${c2%@}
+       IFS=$c
+       set -- $c2$c$c2$c$c2
+       if [ "$#" -ne 3 ] || [ "$1" != "$c2" ] || [ "$2" != "$c2" ] ||
+           [ "$3" != "$c2" ]; then
+               echo "Bad results for separator $i (word $i2)" >&2
+               : $((failures += 1))
+       fi
+       i=$((i+1))
+done
+exit $((failures > 0))
diff --git a/sh/tests/expansion/ifs3.0 b/sh/tests/expansion/ifs3.0
new file mode 100644 (file)
index 0000000..0569b57
--- /dev/null
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+failures=0
+unset LC_ALL
+export LC_CTYPE=en_US.ISO8859-1
+i=128
+set -f
+while [ "$i" -le 255 ]; do
+       i2=$((i^2))
+       c=$(printf \\"$(printf %o "$i")")
+       c2=$(printf \\"$(printf %o "$i2")")
+       IFS=$c
+       set -- $c2$c$c2$c$c2
+       if [ "$#" -ne 3 ] || [ "$1" != "$c2" ] || [ "$2" != "$c2" ] ||
+           [ "$3" != "$c2" ]; then
+               echo "Bad results for separator $i (word $i2)" >&2
+               : $((failures += 1))
+       fi
+       i=$((i+1))
+done
+exit $((failures > 0))
diff --git a/sh/tests/expansion/ifs4.0 b/sh/tests/expansion/ifs4.0
new file mode 100644 (file)
index 0000000..5b896a2
--- /dev/null
@@ -0,0 +1,39 @@
+# $FreeBSD$
+
+c=: e= s=' '
+failures=''
+ok=''
+
+check_result() {
+       if [ "x$2" = "x$3" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $1, expected $3 actual $2"
+       fi
+}
+
+IFS='  
+'
+set -- a b '' c
+set -- $@
+check_result 'set -- $@' "($#)($1)($2)($3)($4)" "(3)(a)(b)(c)()"
+
+IFS=''
+set -- a b '' c
+set -- $@
+check_result 'set -- $@' "($#)($1)($2)($3)($4)" "(3)(a)(b)(c)()"
+
+set -- a b '' c
+set -- $*
+check_result 'set -- $*' "($#)($1)($2)($3)($4)" "(3)(a)(b)(c)()"
+
+set -- a b '' c
+set -- "$@"
+check_result 'set -- "$@"' "($#)($1)($2)($3)($4)" "(4)(a)(b)()(c)"
+
+set -- a b '' c
+set -- "$*"
+check_result 'set -- "$*"' "($#)($1)($2)($3)($4)" "(1)(abc)()()()"
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/ifs5.0 b/sh/tests/expansion/ifs5.0
new file mode 100644 (file)
index 0000000..ab0e646
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+set -- $(echo a b c d)
+[ "$#" = 4 ]
diff --git a/sh/tests/expansion/ifs6.0 b/sh/tests/expansion/ifs6.0
new file mode 100644 (file)
index 0000000..be77945
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+IFS=': '
+x=': :'
+set -- $x
+[ "$#|$1|$2|$3" = "2|||" ]
diff --git a/sh/tests/expansion/ifs7.0 b/sh/tests/expansion/ifs7.0
new file mode 100644 (file)
index 0000000..0cc0834
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+IFS=2
+set -- $((123))
+[ "$#|$1|$2|$3" = "2|1|3|" ]
diff --git a/sh/tests/expansion/length1.0 b/sh/tests/expansion/length1.0
new file mode 100644 (file)
index 0000000..2aaebf9
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+v=abcd
+[ "${#v}" = 4 ] || echo '${#v} wrong'
+v=$$
+[ "${#$}" = "${#v}" ] || echo '${#$} wrong'
+[ "${#!}" = 0 ] || echo '${#!} wrong'
+set -- 01 2 3 4 5 6 7 8 9 10 11 12 0013
+[ "${#1}" = 2 ] || echo '${#1} wrong'
+[ "${#13}" = 4 ] || echo '${#13} wrong'
+v=$0
+[ "${#0}" = "${#v}" ] || echo '${#0} wrong'
diff --git a/sh/tests/expansion/length2.0 b/sh/tests/expansion/length2.0
new file mode 100644 (file)
index 0000000..d749b51
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+v=$-
+[ "${#-}" = "${#v}" ] || echo '${#-} wrong'
diff --git a/sh/tests/expansion/length3.0 b/sh/tests/expansion/length3.0
new file mode 100644 (file)
index 0000000..2093eed
--- /dev/null
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+set -- 1 2 3 4 5 6 7 8 9 10 11 12 13
+[ "$#" = 13 ] || echo '$# wrong'
+[ "${#}" = 13 ] || echo '${#} wrong'
+[ "${##}" = 2 ] || echo '${##} wrong'
+set --
+[ "$#" = 0 ] || echo '$# wrong'
+[ "${#}" = 0 ] || echo '${#} wrong'
+[ "${##}" = 1 ] || echo '${##} wrong'
diff --git a/sh/tests/expansion/length4.0 b/sh/tests/expansion/length4.0
new file mode 100644 (file)
index 0000000..5348be5
--- /dev/null
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+# The construct ${#?} is ambiguous in POSIX.1-2008: it could be the length
+# of $? or it could be $# giving an error in the (impossible) case that it
+# is not set.
+# We use the former interpretation; it seems more useful.
+
+:
+[ "${#?}" = 1 ] || echo '${#?} wrong'
+(exit 42)
+[ "${#?}" = 2 ] || echo '${#?} wrong'
diff --git a/sh/tests/expansion/length5.0 b/sh/tests/expansion/length5.0
new file mode 100644 (file)
index 0000000..322ca16
--- /dev/null
@@ -0,0 +1,27 @@
+# $FreeBSD$
+
+unset LC_ALL
+LC_CTYPE=en_US.ISO8859-1
+export LC_CTYPE
+
+e=
+for i in 0 1 2 3; do
+       for j in 0 1 2 3 4 5 6 7; do
+               for k in 0 1 2 3 4 5 6 7; do
+                       case $i$j$k in
+                       000) continue ;;
+                       esac
+                       e="$e\\$i$j$k"
+               done
+       done
+done
+ee=`printf "$e"`
+[ ${#ee} = 255 ] || echo bad 1
+[ "${#ee}" = 255 ] || echo bad 2
+[ $((${#ee})) = 255 ] || echo bad 3
+[ "$((${#ee}))" = 255 ] || echo bad 4
+set -- "$ee"
+[ ${#1} = 255 ] || echo bad 5
+[ "${#1}" = 255 ] || echo bad 6
+[ $((${#1})) = 255 ] || echo bad 7
+[ "$((${#1}))" = 255 ] || echo bad 8
diff --git a/sh/tests/expansion/length6.0 b/sh/tests/expansion/length6.0
new file mode 100644 (file)
index 0000000..6b78309
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+x='!@#$%^&*()[]'
+[ ${#x} = 12 ] || echo bad 1
+[ "${#x}" = 12 ] || echo bad 2
+IFS=2
+[ ${#x} = 1 ] || echo bad 3
+[ "${#x}" = 12 ] || echo bad 4
diff --git a/sh/tests/expansion/length7.0 b/sh/tests/expansion/length7.0
new file mode 100644 (file)
index 0000000..b79b116
--- /dev/null
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+unset LC_ALL
+LC_CTYPE=en_US.UTF-8
+export LC_CTYPE
+
+# a umlaut
+s=$(printf '\303\244')
+# euro sign
+s=$s$(printf '\342\202\254')
+# some sort of 't' outside BMP
+s=$s$(printf '\360\235\225\245')
+set -- "$s"
+[ ${#s} = 3 ] && [ ${#1} = 3 ]
diff --git a/sh/tests/expansion/length8.0 b/sh/tests/expansion/length8.0
new file mode 100644 (file)
index 0000000..3cc6c15
--- /dev/null
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+unset LC_ALL
+LC_CTYPE=en_US.ISO8859-1
+export LC_CTYPE
+
+# a umlaut
+s=$(printf '\303\244')
+# euro sign
+s=$s$(printf '\342\202\254')
+# some sort of 't' outside BMP
+s=$s$(printf '\360\235\225\245')
+set -- "$s"
+[ ${#s} = 9 ] && [ ${#1} = 9 ]
diff --git a/sh/tests/expansion/local1.0 b/sh/tests/expansion/local1.0
new file mode 100644 (file)
index 0000000..3477835
--- /dev/null
@@ -0,0 +1,28 @@
+# $FreeBSD$
+
+run_test() {
+       w='@ @'
+       check() {
+               [ "$v" = "$w" ] || echo "Expected $w got $v"
+       }
+
+       local v=$w
+       check
+
+       HOME=/known/value
+       check() {
+               [ "$v" = ~ ] || echo "Expected $HOME got $v"
+       }
+
+       local v=~
+       check
+
+       check() {
+               [ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v"
+       }
+
+       local v=x:~
+       check
+}
+
+run_test
diff --git a/sh/tests/expansion/local2.0 b/sh/tests/expansion/local2.0
new file mode 100644 (file)
index 0000000..1984290
--- /dev/null
@@ -0,0 +1,34 @@
+# $FreeBSD$
+
+run_test() {
+       w='@ @'
+       check() {
+               [ "$v" = "$w" ] || echo "Expected $w got $v"
+       }
+
+       command local v=$w
+       check
+       command command local v=$w
+       check
+
+       HOME=/known/value
+       check() {
+               [ "$v" = ~ ] || echo "Expected $HOME got $v"
+       }
+
+       command local v=~
+       check
+       command command local v=~
+       check
+
+       check() {
+               [ "$v" = "x:$HOME" ] || echo "Expected x:$HOME got $v"
+       }
+
+       command local v=x:~
+       check
+       command command local v=x:~
+       check
+}
+
+run_test
diff --git a/sh/tests/expansion/pathname1.0 b/sh/tests/expansion/pathname1.0
new file mode 100644 (file)
index 0000000..8c6b01c
--- /dev/null
@@ -0,0 +1,61 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       testcase=$1
+       expect=$2
+       eval "set -- $testcase"
+       actual="$*"
+       if [ "$actual" != "$expect" ]; then
+               failures=$((failures+1))
+               printf '%s\n' "For $testcase, expected $expect actual $actual"
+       fi
+}
+
+set -e
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX)
+trap 'rm -rf $T' 0
+cd -P $T
+
+mkdir testdir testdir2 'testdir/*' 'testdir/?' testdir/a testdir/b testdir2/b
+mkdir testdir2/.c
+touch testf 'testdir/*/1' 'testdir/?/1' testdir/a/1 testdir/b/1 testdir2/b/.a
+
+check '' ''
+check 'testdir/b' 'testdir/b'
+check 'testdir/c' 'testdir/c'
+check '\*' '*'
+check '\?' '?'
+check '*' 'testdir testdir2 testf'
+check '*""' 'testdir testdir2 testf'
+check '""*' 'testdir testdir2 testf'
+check '*/' 'testdir/ testdir2/'
+check 'testdir*/a' 'testdir/a'
+check 'testdir*/b' 'testdir/b testdir2/b'
+check '*/.c' 'testdir2/.c'
+check 'testdir2/*' 'testdir2/b'
+check 'testdir2/b/*' 'testdir2/b/*'
+check 'testdir/*' 'testdir/* testdir/? testdir/a testdir/b'
+check 'testdir/*/1' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1'
+check '"testdir/"*/1' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1'
+check 'testdir/\*/*' 'testdir/*/1'
+check 'testdir/\?/*' 'testdir/?/1'
+check 'testdir/"?"/*' 'testdir/?/1'
+check '"testdir"/"?"/*' 'testdir/?/1'
+check '"testdir"/"?"*/*' 'testdir/?/1'
+check '"testdir"/*"?"/*' 'testdir/?/1'
+check '"testdir/?"*/*' 'testdir/?/1'
+check 'testdir/\*/' 'testdir/*/'
+check 'testdir/\?/' 'testdir/?/'
+check 'testdir/"?"/' 'testdir/?/'
+check '"testdir"/"?"/' 'testdir/?/'
+check '"testdir"/"?"*/' 'testdir/?/'
+check '"testdir"/*"?"/' 'testdir/?/'
+check '"testdir/?"*/' 'testdir/?/'
+check 'testdir/[*]/' 'testdir/*/'
+check 'testdir/[?]/' 'testdir/?/'
+check 'testdir/[*?]/' 'testdir/*/ testdir/?/'
+check '[tz]estdir/[*]/' 'testdir/*/'
+
+exit $((failures != 0))
diff --git a/sh/tests/expansion/pathname2.0 b/sh/tests/expansion/pathname2.0
new file mode 100644 (file)
index 0000000..8a884ff
--- /dev/null
@@ -0,0 +1,31 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       testcase=$1
+       expect=$2
+       eval "set -- $testcase"
+       actual="$*"
+       if [ "$actual" != "$expect" ]; then
+               failures=$((failures+1))
+               printf '%s\n' "For $testcase, expected $expect actual $actual"
+       fi
+}
+
+set -e
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX)
+trap 'rm -rf $T' 0
+cd -P $T
+
+mkdir testdir testdir2 'testdir/*' 'testdir/?' testdir/a testdir/b testdir2/b
+mkdir testdir2/.c
+touch testf 'testdir/*/1' 'testdir/?/1' testdir/a/1 testdir/b/1 testdir2/b/.a
+
+check '*\/' 'testdir/ testdir2/'
+check '"testdir/"*"/1"' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1'
+check '"testdir/"*"/"*' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1'
+check '"testdir/"*\/*' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1'
+check '"testdir"*"/"*"/"*' 'testdir/*/1 testdir/?/1 testdir/a/1 testdir/b/1'
+
+exit $((failures != 0))
diff --git a/sh/tests/expansion/pathname3.0 b/sh/tests/expansion/pathname3.0
new file mode 100644 (file)
index 0000000..d1672e0
--- /dev/null
@@ -0,0 +1,29 @@
+# $FreeBSD$
+
+v=12345678
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+v=$v$v$v$v
+# 8192 bytes
+v=${v##???}
+[ /*/$v = "/*/$v" ] || exit 1
+
+s=////
+s=$s$s$s$s
+s=$s$s$s$s
+s=$s$s$s$s
+s=$s$s$s$s
+# 1024 bytes
+s=${s##??????????}
+[ /var/empt[y]/$s/$v = "/var/empt[y]/$s/$v" ] || exit 2
+while [ ${#s} -lt 1034 ]; do
+       set -- /.${s}et[c]
+       [ ${#s} -gt 1018 ] || [ "$1" = /.${s}etc ] || exit 3
+       set -- /.${s}et[c]/
+       [ ${#s} -gt 1017 ] || [ "$1" = /.${s}etc/ ] || exit 4
+       set -- /.${s}et[c]/.
+       [ ${#s} -gt 1016 ] || [ "$1" = /.${s}etc/. ] || exit 5
+       s=$s/
+done
diff --git a/sh/tests/expansion/pathname4.0 b/sh/tests/expansion/pathname4.0
new file mode 100644 (file)
index 0000000..18269c4
--- /dev/null
@@ -0,0 +1,28 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       testcase=$1
+       expect=$2
+       eval "set -- $testcase"
+       actual="$*"
+       if [ "$actual" != "$expect" ]; then
+               failures=$((failures+1))
+               printf '%s\n' "For $testcase, expected $expect actual $actual"
+       fi
+}
+
+set -e
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX)
+trap 'rm -rf $T' 0
+cd -P $T
+
+mkdir !!a
+touch !!a/fff
+
+chmod u-r .
+check '!!a/ff*' '!!a/fff'
+chmod u+r .
+
+exit $((failures != 0))
diff --git a/sh/tests/expansion/pathname5.0 b/sh/tests/expansion/pathname5.0
new file mode 100644 (file)
index 0000000..bc27812
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+[ `echo '/[e]tc'` = /etc ]
diff --git a/sh/tests/expansion/plus-minus1.0 b/sh/tests/expansion/plus-minus1.0
new file mode 100644 (file)
index 0000000..9a6a53a
--- /dev/null
@@ -0,0 +1,76 @@
+# $FreeBSD$
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+       code="$1"
+       expected="$2"
+       oIFS="$IFS"
+       eval "$code"
+       IFS='|'
+       result="$#|$*"
+       IFS="$oIFS"
+       if [ "x$result" = "x$expected" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $code, expected $expected actual $result"
+       fi
+}
+
+testcase 'set -- a b'                          '2|a|b'
+testcase 'set --'                              '0|'
+testcase 'set -- ${e}'                         '0|'
+testcase 'set -- "${e}"'                       '1|'
+
+testcase 'set -- $p'                           '1|/etc/'
+testcase 'set -- "$p"'                         '1|/et[c]/'
+testcase 'set -- ${s+$p}'                      '1|/etc/'
+testcase 'set -- "${s+$p}"'                    '1|/et[c]/'
+testcase 'set -- ${s+"$p"}'                    '1|/et[c]/'
+# Dquotes in dquotes is undefined for Bourne shell operators
+#testcase 'set -- "${s+"$p"}"'                 '1|/et[c]/'
+testcase 'set -- ${e:-$p}'                     '1|/etc/'
+testcase 'set -- "${e:-$p}"'                   '1|/et[c]/'
+testcase 'set -- ${e:-"$p"}'                   '1|/et[c]/'
+# Dquotes in dquotes is undefined for Bourne shell operators
+#testcase 'set -- "${e:-"$p"}"'                        '1|/et[c]/'
+testcase 'set -- ${e:+"$e"}'                   '0|'
+testcase 'set -- ${e:+$w"$e"}'                 '0|'
+testcase 'set -- ${w:+"$w"}'                   '1|a b c'
+testcase 'set -- ${w:+$w"$w"}'                 '3|a|b|ca b c'
+
+testcase 'set -- "${s+a b}"'                   '1|a b'
+testcase 'set -- "${e:-a b}"'                  '1|a b'
+testcase 'set -- ${e:-\}}'                     '1|}'
+testcase 'set -- ${e:+{}}'                     '1|}'
+testcase 'set -- "${e:+{}}"'                   '1|}'
+
+testcase 'set -- ${e+x}${e+x}'                 '1|xx'
+testcase 'set -- "${e+x}"${e+x}'               '1|xx'
+testcase 'set -- ${e+x}"${e+x}"'               '1|xx'
+testcase 'set -- "${e+x}${e+x}"'               '1|xx'
+testcase 'set -- "${e+x}""${e+x}"'             '1|xx'
+
+testcase 'set -- ${e:-${e:-$p}}'               '1|/etc/'
+testcase 'set -- "${e:-${e:-$p}}"'             '1|/et[c]/'
+testcase 'set -- ${e:-"${e:-$p}"}'             '1|/et[c]/'
+testcase 'set -- ${e:-${e:-"$p"}}'             '1|/et[c]/'
+testcase 'set -- ${e:-${e:-${e:-$w}}}'         '3|a|b|c'
+testcase 'set -- ${e:-${e:-${e:-"$w"}}}'       '1|a b c'
+testcase 'set -- ${e:-${e:-"${e:-$w}"}}'       '1|a b c'
+testcase 'set -- ${e:-"${e:-${e:-$w}}"}'       '1|a b c'
+testcase 'set -- "${e:-${e:-${e:-$w}}}"'       '1|a b c'
+
+testcase 'shift $#; set -- ${1+"$@"}'          '0|'
+testcase 'set -- ""; set -- ${1+"$@"}'         '1|'
+testcase 'set -- "" a; set -- ${1+"$@"}'       '2||a'
+testcase 'set -- a ""; set -- ${1+"$@"}'       '2|a|'
+testcase 'set -- a b; set -- ${1+"$@"}'                '2|a|b'
+testcase 'set -- a\ b; set -- ${1+"$@"}'       '1|a b'
+testcase 'set -- " " ""; set -- ${1+"$@"}'     '2| |'
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/plus-minus2.0 b/sh/tests/expansion/plus-minus2.0
new file mode 100644 (file)
index 0000000..f5a8752
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+e=
+test "${e:-\}}" = '}'
diff --git a/sh/tests/expansion/plus-minus3.0 b/sh/tests/expansion/plus-minus3.0
new file mode 100644 (file)
index 0000000..49fcdc2
--- /dev/null
@@ -0,0 +1,44 @@
+# $FreeBSD$
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+       code="$1"
+       expected="$2"
+       oIFS="$IFS"
+       eval "$code"
+       IFS='|'
+       result="$#|$*"
+       IFS="$oIFS"
+       if [ "x$result" = "x$expected" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $code, expected $expected actual $result"
+       fi
+}
+
+# We follow original ash behaviour for quoted ${var+-=?} expansions:
+# a double-quote in one switches back to unquoted state.
+# This allows expanding a variable as a single word if it is set
+# and substituting multiple words otherwise.
+# It is also close to the Bourne and Korn shells.
+# POSIX leaves this undefined, and various other shells treat
+# such double-quotes as introducing a second level of quoting
+# which does not do much except quoting close braces.
+
+testcase 'set -- "${p+"/et[c]/"}"'             '1|/etc/'
+testcase 'set -- "${p-"/et[c]/"}"'             '1|/et[c]/'
+testcase 'set -- "${p+"$p"}"'                  '1|/etc/'
+testcase 'set -- "${p-"$p"}"'                  '1|/et[c]/'
+testcase 'set -- "${p+"""/et[c]/"}"'           '1|/etc/'
+testcase 'set -- "${p-"""/et[c]/"}"'           '1|/et[c]/'
+testcase 'set -- "${p+"""$p"}"'                        '1|/etc/'
+testcase 'set -- "${p-"""$p"}"'                        '1|/et[c]/'
+testcase 'set -- "${p+"\@"}"'                  '1|@'
+testcase 'set -- "${p+"'\''/et[c]/'\''"}"'     '1|/et[c]/'
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/plus-minus4.0 b/sh/tests/expansion/plus-minus4.0
new file mode 100644 (file)
index 0000000..66dea38
--- /dev/null
@@ -0,0 +1,38 @@
+# $FreeBSD$
+
+# These may be a bit unclear in the POSIX spec or the proposed revisions,
+# and conflict with bash's interpretation, but I think ksh93's interpretation
+# makes most sense. In particular, it makes no sense to me that single-quotes
+# must match but are not removed.
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+       code="$1"
+       expected="$2"
+       oIFS="$IFS"
+       eval "$code"
+       IFS='|'
+       result="$#|$*"
+       IFS="$oIFS"
+       if [ "x$result" = "x$expected" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $code, expected $expected actual $result"
+       fi
+}
+
+testcase 'set -- ${e:-'"'"'}'"'"'}'            '1|}'
+testcase "set -- \${e:-\\'}"                   "1|'"
+testcase "set -- \${e:-\\'\\'}"                        "1|''"
+testcase "set -- \"\${e:-'}\""                 "1|'"
+testcase "set -- \"\${e:-'}'}\""               "1|''}"
+testcase "set -- \"\${e:-''}\""                        "1|''"
+testcase 'set -- ${e:-\a}'                     '1|a'
+testcase 'set -- "${e:-\a}"'                   '1|\a'
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/plus-minus5.0 b/sh/tests/expansion/plus-minus5.0
new file mode 100644 (file)
index 0000000..0b25e53
--- /dev/null
@@ -0,0 +1,31 @@
+# $FreeBSD$
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+       code="$1"
+       expected="$2"
+       oIFS="$IFS"
+       eval "$code"
+       IFS='|'
+       result="$#|$*"
+       IFS="$oIFS"
+       if [ "x$result" = "x$expected" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $code, expected $expected actual $result"
+       fi
+}
+
+testcase 'set -- ${e:-"{x}"}'                  '1|{x}'
+testcase 'set -- "${e:-"{x}"}"'                        '1|{x}'
+testcase 'set -- ${h+"{x}"}'                   '1|{x}'
+testcase 'set -- "${h+"{x}"}"'                 '1|{x}'
+testcase 'set -- ${h:-"{x}"}'                  '1|##'
+testcase 'set -- "${h:-"{x}"}"'                        '1|##'
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/plus-minus6.0 b/sh/tests/expansion/plus-minus6.0
new file mode 100644 (file)
index 0000000..bc66805
--- /dev/null
@@ -0,0 +1,34 @@
+# $FreeBSD$
+
+failures=0
+unset LC_ALL
+export LC_CTYPE=en_US.ISO8859-1
+nl='
+'
+i=1
+set -f
+while [ "$i" -le 255 ]; do
+       # A different byte still in the range 1..255.
+       i2=$((i^2+(i==2)))
+       # Add a character to work around command substitution's removal of
+       # final newlines, then remove it again.
+       c=$(printf \\"$(printf %o@ "$i")")
+       c=${c%@}
+       c2=$(printf \\"$(printf %o@ "$i2")")
+       c2=${c2%@}
+       case $c in
+               [\'$nl'$}();&|\"`']) c=M
+       esac
+       case $c2 in
+               [\'$nl'$}();&|\"`']) c2=N
+       esac
+       IFS=$c
+       command eval "set -- \${\$+$c2$c$c2$c$c2}"
+       if [ "$#" -ne 3 ] || [ "$1" != "$c2" ] || [ "$2" != "$c2" ] ||
+           [ "$3" != "$c2" ]; then
+               echo "Bad results for separator $i (word $i2)" >&2
+               : $((failures += 1))
+       fi
+       i=$((i+1))
+done
+exit $((failures > 0))
diff --git a/sh/tests/expansion/plus-minus7.0 b/sh/tests/expansion/plus-minus7.0
new file mode 100644 (file)
index 0000000..9e81f58
--- /dev/null
@@ -0,0 +1,26 @@
+# $FreeBSD$
+
+e= s='foo'
+failures=''
+ok=''
+
+testcase() {
+       code="$1"
+       expected="$2"
+       oIFS="$IFS"
+       eval "$code"
+       IFS='|'
+       result="$#|$*"
+       IFS="$oIFS"
+       if [ "x$result" = "x$expected" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $code, expected $expected actual $result"
+       fi
+}
+
+testcase 'set -- ${s+a b}'                     '2|a|b'
+testcase 'set -- ${e:-a b}'                    '2|a|b'
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/plus-minus8.0 b/sh/tests/expansion/plus-minus8.0
new file mode 100644 (file)
index 0000000..beba009
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+set -- 1 2 3 4 5 6 7 8 9 10 11 12 13
+[ "${#+hi}" = hi ] || echo '${#+hi} wrong'
+[ "${#-hi}" = 13 ] || echo '${#-hi} wrong'
diff --git a/sh/tests/expansion/question1.0 b/sh/tests/expansion/question1.0
new file mode 100644 (file)
index 0000000..663c68d
--- /dev/null
@@ -0,0 +1,22 @@
+# $FreeBSD$
+
+x=a\ b
+[ "$x" = "${x?}" ] || exit 1
+set -- ${x?}
+{ [ "$#" = 2 ] && [ "$1" = a ] && [ "$2" = b ]; } || exit 1
+unset x
+(echo ${x?abcdefg}) 2>&1 | grep -q abcdefg || exit 1
+${SH} -c 'unset foo; echo ${foo?}' 2>/dev/null && exit 1
+${SH} -c 'foo=; echo ${foo:?}' 2>/dev/null && exit 1
+${SH} -c 'foo=; echo ${foo?}' >/dev/null || exit 1
+${SH} -c 'foo=1; echo ${foo:?}' >/dev/null || exit 1
+${SH} -c 'echo ${!?}' 2>/dev/null && exit 1
+${SH} -c ':& echo ${!?}' >/dev/null || exit 1
+${SH} -c 'echo ${#?}' >/dev/null || exit 1
+${SH} -c 'echo ${*?}' 2>/dev/null && exit 1
+${SH} -c 'echo ${*?}' ${SH} x >/dev/null || exit 1
+${SH} -c 'echo ${1?}' 2>/dev/null && exit 1
+${SH} -c 'echo ${1?}' ${SH} x >/dev/null || exit 1
+${SH} -c 'echo ${2?}' ${SH} x 2>/dev/null && exit 1
+${SH} -c 'echo ${2?}' ${SH} x y >/dev/null || exit 1
+exit 0
diff --git a/sh/tests/expansion/readonly1.0 b/sh/tests/expansion/readonly1.0
new file mode 100644 (file)
index 0000000..5ad0e14
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+w='@ @'
+
+v=0 HOME=/known/value
+readonly v=~:~/:$w
+[ "$v" = "$HOME:$HOME/:$w" ] || echo "Expected $HOME/:$w got $v"
diff --git a/sh/tests/expansion/redir1.0 b/sh/tests/expansion/redir1.0
new file mode 100644 (file)
index 0000000..aa13e15
--- /dev/null
@@ -0,0 +1,26 @@
+# $FreeBSD$
+
+bad=0
+for i in 0 1 2 3; do
+       for j in 0 1 2 3 4 5 6 7; do
+               for k in 0 1 2 3 4 5 6 7; do
+                       case $i$j$k in
+                       000) continue ;;
+                       esac
+                       set -- "$(printf \\$i$j$k@)"
+                       set -- "${1%@}"
+                       ff=
+                       for f in /dev/null /dev/zero /; do
+                               if [ -e "$f" ] && [ ! -e "$f$1" ]; then
+                                       ff=$f
+                               fi
+                       done
+                       [ -n "$ff" ] || continue
+                       if { true <$ff$1; } 2>/dev/null; then
+                               echo "Bad: $i$j$k ($ff)" >&2
+                               : $((bad += 1))
+                       fi
+               done
+       done
+done
+exit $((bad ? 2 : 0))
diff --git a/sh/tests/expansion/set-u1.0 b/sh/tests/expansion/set-u1.0
new file mode 100644 (file)
index 0000000..763eb7d
--- /dev/null
@@ -0,0 +1,29 @@
+# $FreeBSD$
+
+${SH} -uc 'unset foo; echo $foo' 2>/dev/null && exit 1
+${SH} -uc 'foo=; echo $foo' >/dev/null || exit 1
+${SH} -uc 'foo=1; echo $foo' >/dev/null || exit 1
+# -/+/= are unaffected by set -u
+${SH} -uc 'unset foo; echo ${foo-}' >/dev/null || exit 1
+${SH} -uc 'unset foo; echo ${foo+}' >/dev/null || exit 1
+${SH} -uc 'unset foo; echo ${foo=}' >/dev/null || exit 1
+# length/trimming are affected
+${SH} -uc 'unset foo; echo ${#foo}' 2>/dev/null && exit 1
+${SH} -uc 'foo=; echo ${#foo}' >/dev/null || exit 1
+${SH} -uc 'unset foo; echo ${foo#?}' 2>/dev/null && exit 1
+${SH} -uc 'foo=1; echo ${foo#?}' >/dev/null || exit 1
+${SH} -uc 'unset foo; echo ${foo##?}' 2>/dev/null && exit 1
+${SH} -uc 'foo=1; echo ${foo##?}' >/dev/null || exit 1
+${SH} -uc 'unset foo; echo ${foo%?}' 2>/dev/null && exit 1
+${SH} -uc 'foo=1; echo ${foo%?}' >/dev/null || exit 1
+${SH} -uc 'unset foo; echo ${foo%%?}' 2>/dev/null && exit 1
+${SH} -uc 'foo=1; echo ${foo%%?}' >/dev/null || exit 1
+
+${SH} -uc 'echo $!' 2>/dev/null && exit 1
+${SH} -uc ':& echo $!' >/dev/null || exit 1
+${SH} -uc 'echo $#' >/dev/null || exit 1
+${SH} -uc 'echo $1' 2>/dev/null && exit 1
+${SH} -uc 'echo $1' ${SH} x >/dev/null || exit 1
+${SH} -uc 'echo $2' ${SH} x 2>/dev/null && exit 1
+${SH} -uc 'echo $2' ${SH} x y >/dev/null || exit 1
+exit 0
diff --git a/sh/tests/expansion/set-u2.0 b/sh/tests/expansion/set-u2.0
new file mode 100644 (file)
index 0000000..f81aa62
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+set -u
+: $* $@ "$@" "$*"
+set -- x
+: $* $@ "$@" "$*"
+shift $#
+: $* $@ "$@" "$*"
+set -- y
+set --
+: $* $@ "$@" "$*"
+exit 0
diff --git a/sh/tests/expansion/set-u3.0 b/sh/tests/expansion/set-u3.0
new file mode 100644 (file)
index 0000000..7f199b4
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+set -u
+unset x
+v=$( (eval ': $((x))') 2>&1 >/dev/null)
+[ $? -ne 0 ] && [ -n "$v" ]
diff --git a/sh/tests/expansion/tilde1.0 b/sh/tests/expansion/tilde1.0
new file mode 100644 (file)
index 0000000..7d8581b
--- /dev/null
@@ -0,0 +1,56 @@
+# $FreeBSD$
+
+HOME=/tmp
+roothome=~root
+if [ "$roothome" = "~root" ]; then
+       echo "~root is not expanded!"
+       exit 2
+fi
+
+testcase() {
+       code="$1"
+       expected="$2"
+       oIFS="$IFS"
+       eval "$code"
+       IFS='|'
+       result="$#|$*"
+       IFS="$oIFS"
+       if [ "x$result" = "x$expected" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $code, expected $expected actual $result"
+       fi
+}
+
+testcase 'set -- ~'                            '1|/tmp'
+testcase 'set -- ~/foo'                                '1|/tmp/foo'
+testcase 'set -- x~'                           '1|x~'
+testcase 'set -- ~root'                                "1|$roothome"
+h=~
+testcase 'set -- "$h"'                         '1|/tmp'
+ooIFS=$IFS
+IFS=m
+testcase 'set -- ~'                            '1|/tmp'
+testcase 'set -- ~/foo'                                '1|/tmp/foo'
+testcase 'set -- $h'                           '2|/t|p'
+IFS=$ooIFS
+t=\~
+testcase 'set -- $t'                           '1|~'
+r=$(cat <<EOF
+~
+EOF
+)
+testcase 'set -- $r'                           '1|~'
+r=$(cat <<EOF
+${t+~}
+EOF
+)
+testcase 'set -- $r'                           '1|~'
+r=$(cat <<EOF
+${t+~/.}
+EOF
+)
+testcase 'set -- $r'                           '1|~/.'
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/tilde2.0 b/sh/tests/expansion/tilde2.0
new file mode 100644 (file)
index 0000000..4f8ed9b
--- /dev/null
@@ -0,0 +1,90 @@
+# $FreeBSD$
+
+HOME=/tmp
+roothome=~root
+if [ "$roothome" = "~root" ]; then
+       echo "~root is not expanded!"
+       exit 2
+fi
+
+testcase() {
+       code="$1"
+       expected="$2"
+       oIFS="$IFS"
+       eval "$code"
+       IFS='|'
+       result="$#|$*"
+       IFS="$oIFS"
+       if [ "x$result" = "x$expected" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $code, expected $expected actual $result"
+       fi
+}
+
+testcase 'set -- ${$+~}'                       '1|/tmp'
+testcase 'set -- ${$+~/}'                      '1|/tmp/'
+testcase 'set -- ${$+~/foo}'                   '1|/tmp/foo'
+testcase 'set -- ${$+x~}'                      '1|x~'
+testcase 'set -- ${$+~root}'                   "1|$roothome"
+testcase 'set -- ${$+"~"}'                     '1|~'
+testcase 'set -- ${$+"~/"}'                    '1|~/'
+testcase 'set -- ${$+"~/foo"}'                 '1|~/foo'
+testcase 'set -- ${$+"x~"}'                    '1|x~'
+testcase 'set -- ${$+"~root"}'                 "1|~root"
+testcase 'set -- "${$+~}"'                     '1|~'
+testcase 'set -- "${$+~/}"'                    '1|~/'
+testcase 'set -- "${$+~/foo}"'                 '1|~/foo'
+testcase 'set -- "${$+x~}"'                    '1|x~'
+testcase 'set -- "${$+~root}"'                 "1|~root"
+testcase 'set -- ${HOME#~}'                    '0|'
+h=~
+testcase 'set -- "$h"'                         '1|/tmp'
+f=~/foo
+testcase 'set -- "$f"'                         '1|/tmp/foo'
+testcase 'set -- ${f#~}'                       '1|/foo'
+testcase 'set -- ${f#~/}'                      '1|foo'
+
+ooIFS=$IFS
+IFS=m
+testcase 'set -- ${$+~}'                       '1|/tmp'
+testcase 'set -- ${$+~/foo}'                   '1|/tmp/foo'
+testcase 'set -- ${$+$h}'                      '2|/t|p'
+testcase 'set -- ${HOME#~}'                    '0|'
+IFS=$ooIFS
+
+t=\~
+testcase 'set -- ${$+$t}'                      '1|~'
+r=$(cat <<EOF
+${HOME#~}
+EOF
+)
+testcase 'set -- $r'                           '0|'
+r=$(cat <<EOF
+${HOME#'~'}
+EOF
+)
+testcase 'set -- $r'                           '1|/tmp'
+r=$(cat <<EOF
+${t#'~'}
+EOF
+)
+testcase 'set -- $r'                           '0|'
+r=$(cat <<EOF
+${roothome#~root}
+EOF
+)
+testcase 'set -- $r'                           '0|'
+r=$(cat <<EOF
+${f#~}
+EOF
+)
+testcase 'set -- $r'                           '1|/foo'
+r=$(cat <<EOF
+${f#~/}
+EOF
+)
+testcase 'set -- $r'                           '1|foo'
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/trim1.0 b/sh/tests/expansion/trim1.0
new file mode 100644 (file)
index 0000000..b548e52
--- /dev/null
@@ -0,0 +1,85 @@
+# $FreeBSD$
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+       code="$1"
+       expected="$2"
+       oIFS="$IFS"
+       eval "$code"
+       IFS='|'
+       result="$#|$*"
+       IFS="$oIFS"
+       if [ "x$result" = "x$expected" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $code, expected $expected actual $result"
+       fi
+}
+
+testcase 'set -- ${t%t}'                       '1|texttex'
+testcase 'set -- "${t%t}"'                     '1|texttex'
+testcase 'set -- ${t%e*}'                      '1|textt'
+testcase 'set -- "${t%e*}"'                    '1|textt'
+testcase 'set -- ${t%%e*}'                     '1|t'
+testcase 'set -- "${t%%e*}"'                   '1|t'
+testcase 'set -- ${t%%*}'                      '0|'
+testcase 'set -- "${t%%*}"'                    '1|'
+testcase 'set -- ${t#t}'                       '1|exttext'
+testcase 'set -- "${t#t}"'                     '1|exttext'
+testcase 'set -- ${t#*x}'                      '1|ttext'
+testcase 'set -- "${t#*x}"'                    '1|ttext'
+testcase 'set -- ${t##*x}'                     '1|t'
+testcase 'set -- "${t##*x}"'                   '1|t'
+testcase 'set -- ${t##*}'                      '0|'
+testcase 'set -- "${t##*}"'                    '1|'
+testcase 'set -- ${t%e$a}'                     '1|textt'
+
+set -f
+testcase 'set -- ${s%[?]*}'                    '1|ast*que'
+testcase 'set -- "${s%[?]*}"'                  '1|ast*que'
+testcase 'set -- ${s%[*]*}'                    '1|ast'
+testcase 'set -- "${s%[*]*}"'                  '1|ast'
+set +f
+
+testcase 'set -- $b'                           '1|{{(#)}}'
+testcase 'set -- ${b%\}}'                      '1|{{(#)}'
+testcase 'set -- ${b#{}'                       '1|{(#)}}'
+testcase 'set -- "${b#{}"'                     '1|{(#)}}'
+# Parentheses are special in ksh, check that they can be escaped
+testcase 'set -- ${b%\)*}'                     '1|{{(#'
+testcase 'set -- ${b#{}'                       '1|{(#)}}'
+testcase 'set -- $h'                           '1|##'
+testcase 'set -- ${h#\#}'                      '1|#'
+testcase 'set -- ${h###}'                      '1|#'
+testcase 'set -- "${h###}"'                    '1|#'
+testcase 'set -- ${h%#}'                       '1|#'
+testcase 'set -- "${h%#}"'                     '1|#'
+
+set -f
+testcase 'set -- ${s%"${s#?}"}'                        '1|a'
+testcase 'set -- ${s%"${s#????}"}'             '1|ast*'
+testcase 'set -- ${s%"${s#????????}"}'         '1|ast*que?'
+testcase 'set -- ${s#"${s%?}"}'                        '1|n'
+testcase 'set -- ${s#"${s%????}"}'             '1|?non'
+testcase 'set -- ${s#"${s%????????}"}'         '1|*que?non'
+set +f
+testcase 'set -- "${s%"${s#?}"}"'              '1|a'
+testcase 'set -- "${s%"${s#????}"}"'           '1|ast*'
+testcase 'set -- "${s%"${s#????????}"}"'       '1|ast*que?'
+testcase 'set -- "${s#"${s%?}"}"'              '1|n'
+testcase 'set -- "${s#"${s%????}"}"'           '1|?non'
+testcase 'set -- "${s#"${s%????????}"}"'       '1|*que?non'
+testcase 'set -- ${p#${p}}'                    '1|/etc/'
+testcase 'set -- "${p#${p}}"'                  '1|/et[c]/'
+testcase 'set -- ${p#*[[]}'                    '1|c]/'
+testcase 'set -- "${p#*[[]}"'                  '1|c]/'
+testcase 'set -- ${p#*\[}'                     '1|c]/'
+testcase 'set -- ${p#*"["}'                    '1|c]/'
+testcase 'set -- "${p#*"["}"'                  '1|c]/'
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/trim2.0 b/sh/tests/expansion/trim2.0
new file mode 100644 (file)
index 0000000..619ef65
--- /dev/null
@@ -0,0 +1,55 @@
+# $FreeBSD$
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+       code="$1"
+       expected="$2"
+       oIFS="$IFS"
+       eval "$code"
+       IFS='|'
+       result="$#|$*"
+       IFS="$oIFS"
+       if [ "x$result" = "x$expected" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $code, expected $expected actual $result"
+       fi
+}
+
+set -f
+testcase 'set -- $s'                           '1|ast*que?non'
+testcase 'set -- ${s%\?*}'                     '1|ast*que'
+testcase 'set -- "${s%\?*}"'                   '1|ast*que'
+testcase 'set -- ${s%\**}'                     '1|ast'
+testcase 'set -- "${s%\**}"'                   '1|ast'
+testcase 'set -- ${s%"$q"*}'                   '1|ast*que'
+testcase 'set -- "${s%"$q"*}"'                 '1|ast*que'
+testcase 'set -- ${s%"$a"*}'                   '1|ast'
+testcase 'set -- "${s%"$a"*}"'                 '1|ast'
+testcase 'set -- ${s%"$q"$a}'                  '1|ast*que'
+testcase 'set -- "${s%"$q"$a}"'                        '1|ast*que'
+testcase 'set -- ${s%"$a"$a}'                  '1|ast'
+testcase 'set -- "${s%"$a"$a}"'                        '1|ast'
+set +f
+
+testcase 'set -- "${b%\}}"'                    '1|{{(#)}'
+# Parentheses are special in ksh, check that they can be escaped
+testcase 'set -- "${b%\)*}"'                   '1|{{(#'
+testcase 'set -- "${h#\#}"'                    '1|#'
+
+testcase 'set -- ${p%"${p#?}"}'                        '1|/'
+testcase 'set -- ${p%"${p#??????}"}'           '1|/etc'
+testcase 'set -- ${p%"${p#???????}"}'          '1|/etc/'
+testcase 'set -- "${p%"${p#?}"}"'              '1|/'
+testcase 'set -- "${p%"${p#??????}"}"'         '1|/et[c]'
+testcase 'set -- "${p%"${p#???????}"}"'                '1|/et[c]/'
+testcase 'set -- ${p#"${p}"}'                  '0|'
+testcase 'set -- "${p#"${p}"}"'                        '1|'
+testcase 'set -- "${p#*\[}"'                   '1|c]/'
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/trim3.0 b/sh/tests/expansion/trim3.0
new file mode 100644 (file)
index 0000000..b89a041
--- /dev/null
@@ -0,0 +1,46 @@
+# $FreeBSD$
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##' c='\\\\'
+failures=''
+ok=''
+
+testcase() {
+       code="$1"
+       expected="$2"
+       oIFS="$IFS"
+       eval "$code"
+       IFS='|'
+       result="$#|$*"
+       IFS="$oIFS"
+       if [ "x$result" = "x$expected" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $code, expected $expected actual $result"
+       fi
+}
+
+# This doesn't make much sense, but it fails in dash so I'm adding it here:
+testcase 'set -- "${w%${w#???}}"'              '1|a b'
+
+testcase 'set -- ${p#/et[}'                    '1|c]/'
+testcase 'set -- "${p#/et[}"'                  '1|c]/'
+testcase 'set -- "${p%${p#????}}"'             '1|/et['
+
+testcase 'set -- ${b%'\'}\''}'                 '1|{{(#)}'
+
+testcase 'set -- ${c#\\}'                      '1|\\\'
+testcase 'set -- ${c#\\\\}'                    '1|\\'
+testcase 'set -- ${c#\\\\\\}'                  '1|\'
+testcase 'set -- ${c#\\\\\\\\}'                        '0|'
+testcase 'set -- "${c#\\}"'                    '1|\\\'
+testcase 'set -- "${c#\\\\}"'                  '1|\\'
+testcase 'set -- "${c#\\\\\\}"'                        '1|\'
+testcase 'set -- "${c#\\\\\\\\}"'              '1|'
+testcase 'set -- "${c#"$c"}"'                  '1|'
+testcase 'set -- ${c#"$c"}'                    '0|'
+testcase 'set -- "${c%"$c"}"'                  '1|'
+testcase 'set -- ${c%"$c"}'                    '0|'
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/trim4.0 b/sh/tests/expansion/trim4.0
new file mode 100644 (file)
index 0000000..1000bd3
--- /dev/null
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+v1=/homes/SOME_USER
+v2=
+v3=C123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
+
+# Trigger bug in VSTRIMRIGHT processing STADJUST() call in expand.c:subevalvar()
+while [ ${#v2} -lt 2000 ]; do
+       v4="${v2} ${v1%/*} $v3"
+       if [ ${#v4} -ne $((${#v2} + ${#v3} + 8)) ]; then
+               echo bad: ${#v4} -ne $((${#v2} + ${#v3} + 8))
+       fi
+       v2=x$v2
+       v3=y$v3
+done
diff --git a/sh/tests/expansion/trim5.0 b/sh/tests/expansion/trim5.0
new file mode 100644 (file)
index 0000000..937ec9a
--- /dev/null
@@ -0,0 +1,28 @@
+# $FreeBSD$
+
+e= q='?' a='*' t=texttext s='ast*que?non' p='/et[c]/' w='a b c' b='{{(#)}}'
+h='##'
+failures=''
+ok=''
+
+testcase() {
+       code="$1"
+       expected="$2"
+       oIFS="$IFS"
+       eval "$code"
+       IFS='|'
+       result="$#|$*"
+       IFS="$oIFS"
+       if [ "x$result" = "x$expected" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $code, expected $expected actual $result"
+       fi
+}
+
+testcase 'set -- "${b%'\'}\''}"'               '1|{{(#)}'
+testcase 'set -- ${b%"}"}'                     '1|{{(#)}'
+testcase 'set -- "${b%"}"}"'                   '1|{{(#)}'
+
+test "x$failures" = x
diff --git a/sh/tests/expansion/trim6.0 b/sh/tests/expansion/trim6.0
new file mode 100644 (file)
index 0000000..3f753c4
--- /dev/null
@@ -0,0 +1,22 @@
+# $FreeBSD$
+
+e=
+for i in 0 1 2 3; do
+       for j in 0 1 2 3 4 5 6 7; do
+               for k in 0 1 2 3 4 5 6 7; do
+                       case $i$j$k in
+                       000) continue ;;
+                       esac
+                       e="$e\\$i$j$k"
+               done
+       done
+done
+e=$(printf "$e")
+v=@$e@$e@
+y=${v##*"$e"}
+yq="${v##*"$e"}"
+[ "$y" = @ ] || echo "error when unquoted in non-splitting context"
+[ "$yq" = @ ] || echo "error when quoted in non-splitting context"
+[ "${v##*"$e"}" = @ ] || echo "error when quoted in splitting context"
+IFS=
+[ ${v##*"$e"} = @ ] || echo "error when unquoted in splitting context"
diff --git a/sh/tests/expansion/trim7.0 b/sh/tests/expansion/trim7.0
new file mode 100644 (file)
index 0000000..352bdea
--- /dev/null
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+set -- 1 2 3 4 5 6 7 8 9 10 11 12 13
+[ "${##1}" = 3 ] || echo '${##1} wrong'
+[ "${###1}" = 3 ] || echo '${###1} wrong'
+[ "${###}" = 13 ] || echo '${###} wrong'
+[ "${#%3}" = 1 ] || echo '${#%3} wrong'
+[ "${#%%3}" = 1 ] || echo '${#%%3} wrong'
+[ "${#%%}" = 13 ] || echo '${#%%} wrong'
+set --
+[ "${##0}" = "" ] || echo '${##0} wrong'
+[ "${###0}" = "" ] || echo '${###0} wrong'
+[ "${###}" = 0 ] || echo '${###} wrong'
+[ "${#%0}" = "" ] || echo '${#%0} wrong'
+[ "${#%%0}" = "" ] || echo '${#%%0} wrong'
+[ "${#%%}" = 0 ] || echo '${#%%} wrong'
diff --git a/sh/tests/expansion/trim8.0 b/sh/tests/expansion/trim8.0
new file mode 100644 (file)
index 0000000..f7272f3
--- /dev/null
@@ -0,0 +1,75 @@
+# $FreeBSD$
+
+unset LC_ALL
+LC_CTYPE=en_US.UTF-8
+export LC_CTYPE
+
+c1=e
+# a umlaut
+c2=$(printf '\303\244')
+# euro sign
+c3=$(printf '\342\202\254')
+# some sort of 't' outside BMP
+c4=$(printf '\360\235\225\245')
+
+s=$c1$c2$c3$c4
+
+testcase() {
+       code="$1"
+       expected="$2"
+       oIFS="$IFS"
+       eval "$code"
+       IFS='|'
+       result="$#|$*"
+       IFS="$oIFS"
+       if [ "x$result" = "x$expected" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $code, expected $expected actual $result"
+       fi
+}
+
+testcase 'set -- "$s"'                         "1|$s"
+testcase 'set -- "${s#$c2}"'                   "1|$s"
+testcase 'set -- "${s#*}"'                     "1|$s"
+testcase 'set -- "${s#$c1}"'                   "1|$c2$c3$c4"
+testcase 'set -- "${s#$c1$c2}"'                        "1|$c3$c4"
+testcase 'set -- "${s#$c1$c2$c3}"'             "1|$c4"
+testcase 'set -- "${s#$c1$c2$c3$c4}"'          "1|"
+testcase 'set -- "${s#?}"'                     "1|$c2$c3$c4"
+testcase 'set -- "${s#??}"'                    "1|$c3$c4"
+testcase 'set -- "${s#???}"'                   "1|$c4"
+testcase 'set -- "${s#????}"'                  "1|"
+testcase 'set -- "${s#*$c3}"'                  "1|$c4"
+testcase 'set -- "${s%$c4}"'                   "1|$c1$c2$c3"
+testcase 'set -- "${s%$c3$c4}"'                        "1|$c1$c2"
+testcase 'set -- "${s%$c2$c3$c4}"'             "1|$c1"
+testcase 'set -- "${s%$c1$c2$c3$c4}"'          "1|"
+testcase 'set -- "${s%?}"'                     "1|$c1$c2$c3"
+testcase 'set -- "${s%??}"'                    "1|$c1$c2"
+testcase 'set -- "${s%???}"'                   "1|$c1"
+testcase 'set -- "${s%????}"'                  "1|"
+testcase 'set -- "${s%$c2*}"'                  "1|$c1"
+testcase 'set -- "${s##$c2}"'                  "1|$s"
+testcase 'set -- "${s##*}"'                    "1|"
+testcase 'set -- "${s##$c1}"'                  "1|$c2$c3$c4"
+testcase 'set -- "${s##$c1$c2}"'               "1|$c3$c4"
+testcase 'set -- "${s##$c1$c2$c3}"'            "1|$c4"
+testcase 'set -- "${s##$c1$c2$c3$c4}"'         "1|"
+testcase 'set -- "${s##?}"'                    "1|$c2$c3$c4"
+testcase 'set -- "${s##??}"'                   "1|$c3$c4"
+testcase 'set -- "${s##???}"'                  "1|$c4"
+testcase 'set -- "${s##????}"'                 "1|"
+testcase 'set -- "${s##*$c3}"'                 "1|$c4"
+testcase 'set -- "${s%%$c4}"'                  "1|$c1$c2$c3"
+testcase 'set -- "${s%%$c3$c4}"'               "1|$c1$c2"
+testcase 'set -- "${s%%$c2$c3$c4}"'            "1|$c1"
+testcase 'set -- "${s%%$c1$c2$c3$c4}"'         "1|"
+testcase 'set -- "${s%%?}"'                    "1|$c1$c2$c3"
+testcase 'set -- "${s%%??}"'                   "1|$c1$c2"
+testcase 'set -- "${s%%???}"'                  "1|$c1"
+testcase 'set -- "${s%%????}"'                 "1|"
+testcase 'set -- "${s%%$c2*}"'                 "1|$c1"
+
+test "x$failures" = x
diff --git a/sh/tests/functional_test.sh b/sh/tests/functional_test.sh
new file mode 100755 (executable)
index 0000000..6980538
--- /dev/null
@@ -0,0 +1,72 @@
+#
+# Copyright 2014 EMC Corp.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+# $FreeBSD$
+
+SRCDIR=$(atf_get_srcdir)
+
+check()
+{
+       local tc=${1}; shift
+
+       export SH=$(atf_config_get bin.sh.test_shell /bin/sh)
+
+       local err_file="${SRCDIR}/${tc}.stderr"
+       [ -f "${err_file}" ] && err_flag="-e file:${err_file}"
+       local out_file="${SRCDIR}/${tc}.stdout"
+       [ -f "${out_file}" ] && out_flag="-o file:${out_file}"
+
+       atf_check -s exit:${tc##*.} ${err_flag} ${out_flag} ${SH} "${SRCDIR}/${tc}"
+}
+
+add_testcase()
+{
+       local tc=${1}
+       local tc_escaped word
+
+       case "${tc%.*}" in
+       *-*)
+               local IFS="-"
+               for word in ${tc%.*}; do
+                       tc_escaped="${tc_escaped:+${tc_escaped}_}${word}"
+               done
+               ;;
+       *)
+               tc_escaped=${tc%.*}
+               ;;
+       esac
+
+       atf_test_case ${tc_escaped}
+       eval "${tc_escaped}_body() { check ${tc}; }"
+       atf_add_test_case ${tc_escaped}
+}
+
+atf_init_test_cases()
+{
+       for path in $(find -Es "${SRCDIR}" -regex '.*\.[0-9]+$'); do
+               add_testcase ${path##*/}
+       done
+}
diff --git a/sh/tests/parameters/Makefile b/sh/tests/parameters/Makefile
new file mode 100644 (file)
index 0000000..231ed4d
--- /dev/null
@@ -0,0 +1,26 @@
+# $FreeBSD$
+
+TESTSDIR=      ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH=  functional_test
+
+FILESDIR=      ${TESTSDIR}
+
+FILES=         env1.0
+FILES+=                exitstatus1.0
+FILES+=                mail1.0
+FILES+=                mail2.0
+FILES+=                optind1.0
+FILES+=                optind2.0
+FILES+=                positional1.0
+FILES+=                positional2.0
+FILES+=                positional3.0
+FILES+=                positional4.0
+FILES+=                positional5.0
+FILES+=                positional6.0
+FILES+=                positional7.0
+FILES+=                pwd1.0
+FILES+=                pwd2.0
+
+.include <bsd.test.mk>
diff --git a/sh/tests/parameters/env1.0 b/sh/tests/parameters/env1.0
new file mode 100644 (file)
index 0000000..c0d4a2c
--- /dev/null
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+export key='must contain this'
+unset x
+r=$(ENV="\${x?\$key}" ${SH} -i +m 2>&1 >/dev/null <<\EOF
+exit 0
+EOF
+) && case $r in
+*"$key"*) true ;;
+*) false ;;
+esac
diff --git a/sh/tests/parameters/exitstatus1.0 b/sh/tests/parameters/exitstatus1.0
new file mode 100644 (file)
index 0000000..696823d
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+f() {
+       [ $? = $1 ] || exit 1
+}
+
+true
+f 0
+false
+f 1
diff --git a/sh/tests/parameters/mail1.0 b/sh/tests/parameters/mail1.0
new file mode 100644 (file)
index 0000000..5791a5a
--- /dev/null
@@ -0,0 +1,15 @@
+# $FreeBSD$
+# Test that a non-interactive shell does not access $MAIL.
+
+goodfile=/var/empty/sh-test-goodfile
+mailfile=/var/empty/sh-test-mailfile
+T=$(mktemp sh-test.XXXXXX) || exit
+MAIL=$mailfile ktrace -i -f "$T" ${SH} -c "[ -s $goodfile ]" 2>/dev/null
+if ! grep -q $goodfile "$T"; then
+       # ktrace problem
+       rc=0
+elif ! grep -q $mailfile "$T"; then
+       rc=0
+fi
+rm "$T"
+exit ${rc:-3}
diff --git a/sh/tests/parameters/mail2.0 b/sh/tests/parameters/mail2.0
new file mode 100644 (file)
index 0000000..343c99d
--- /dev/null
@@ -0,0 +1,15 @@
+# $FreeBSD$
+# Test that an interactive shell accesses $MAIL.
+
+goodfile=/var/empty/sh-test-goodfile
+mailfile=/var/empty/sh-test-mailfile
+T=$(mktemp sh-test.XXXXXX) || exit
+ENV=$goodfile MAIL=$mailfile ktrace -i -f "$T" ${SH} +m -i </dev/null >/dev/null 2>&1
+if ! grep -q $goodfile "$T"; then
+       # ktrace problem
+       rc=0
+elif grep -q $mailfile "$T"; then
+       rc=0
+fi
+rm "$T"
+exit ${rc:-3}
diff --git a/sh/tests/parameters/optind1.0 b/sh/tests/parameters/optind1.0
new file mode 100644 (file)
index 0000000..33e0288
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+unset OPTIND && [ -z "$OPTIND" ]
diff --git a/sh/tests/parameters/optind2.0 b/sh/tests/parameters/optind2.0
new file mode 100644 (file)
index 0000000..a7689f6
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+[ "$(OPTIND=42 ${SH} -c 'printf %s "$OPTIND"')" = 1 ]
diff --git a/sh/tests/parameters/positional1.0 b/sh/tests/parameters/positional1.0
new file mode 100644 (file)
index 0000000..67d1951
--- /dev/null
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+set -- a b c d e f g h i j
+[ "$1" = a ] || echo "error at line $LINENO"
+[ "${1}" = a ] || echo "error at line $LINENO"
+[ "${1-foo}" = a ] || echo "error at line $LINENO"
+[ "${1+foo}" = foo ] || echo "error at line $LINENO"
+[ "$1+foo" = a+foo ] || echo "error at line $LINENO"
+[ "$10" = a0 ] || echo "error at line $LINENO"
+[ "$100" = a00 ] || echo "error at line $LINENO"
+[ "${10}" = j ] || echo "error at line $LINENO"
+[ "${10-foo}" = j ] || echo "error at line $LINENO"
+[ "${100-foo}" = foo ] || echo "error at line $LINENO"
diff --git a/sh/tests/parameters/positional2.0 b/sh/tests/parameters/positional2.0
new file mode 100644 (file)
index 0000000..fcec2a4
--- /dev/null
@@ -0,0 +1,65 @@
+# $FreeBSD$
+
+failures=''
+ok=''
+
+testcase() {
+       code="$1"
+       expected="$2"
+       oIFS="$IFS"
+       eval "$code"
+       IFS='|'
+       result="$#|$*"
+       IFS="$oIFS"
+       if [ "x$result" = "x$expected" ]; then
+               ok=x$ok
+       else
+               failures=x$failures
+               echo "For $code, expected $expected actual $result"
+       fi
+}
+
+testcase 'set -- a b; set -- p$@q'             '2|pa|bq'
+testcase 'set -- a b; set -- $@q'              '2|a|bq'
+testcase 'set -- a b; set -- p$@'              '2|pa|b'
+testcase 'set -- a b; set -- p$@q'             '2|pa|bq'
+testcase 'set -- a b; set -- $@q'              '2|a|bq'
+testcase 'set -- a b; set -- p$@'              '2|pa|b'
+testcase 'set -- a b; set -- p$*q'             '2|pa|bq'
+testcase 'set -- a b; set -- $*q'              '2|a|bq'
+testcase 'set -- a b; set -- p$*'              '2|pa|b'
+testcase 'set -- a b; set -- p$*q'             '2|pa|bq'
+testcase 'set -- a b; set -- $*q'              '2|a|bq'
+testcase 'set -- a b; set -- p$*'              '2|pa|b'
+testcase 'set -- a b; set -- "p$@q"'           '2|pa|bq'
+testcase 'set -- a b; set -- "$@q"'            '2|a|bq'
+testcase 'set -- a b; set -- "p$@"'            '2|pa|b'
+testcase 'set -- a b; set -- p"$@"q'           '2|pa|bq'
+testcase 'set -- a b; set -- "$@"q'            '2|a|bq'
+testcase 'set -- a b; set -- p"$@"'            '2|pa|b'
+testcase 'set -- "" a b; set -- "p$@q"'                '3|p|a|bq'
+testcase 'set -- "" a b; set -- "$@q"'         '3||a|bq'
+testcase 'set -- "" a b; set -- "p$@"'         '3|p|a|b'
+testcase 'set -- "" a b; set -- p"$@"q'                '3|p|a|bq'
+testcase 'set -- "" a b; set -- "$@"q'         '3||a|bq'
+testcase 'set -- "" a b; set -- p"$@"'         '3|p|a|b'
+testcase 'set -- a; set -- p$@q'               '1|paq'
+testcase 'set -- a; set -- $@q'                        '1|aq'
+testcase 'set -- a; set -- p$@'                        '1|pa'
+testcase 'set -- a; set -- p$@q'               '1|paq'
+testcase 'set -- a; set -- $@q'                        '1|aq'
+testcase 'set -- a; set -- p$@'                        '1|pa'
+testcase 'set -- a; set -- p$*q'               '1|paq'
+testcase 'set -- a; set -- $*q'                        '1|aq'
+testcase 'set -- a; set -- p$*'                        '1|pa'
+testcase 'set -- a; set -- p$*q'               '1|paq'
+testcase 'set -- a; set -- $*q'                        '1|aq'
+testcase 'set -- a; set -- p$*'                        '1|pa'
+testcase 'set -- a; set -- "p$@q"'             '1|paq'
+testcase 'set -- a; set -- "$@q"'              '1|aq'
+testcase 'set -- a; set -- "p$@"'              '1|pa'
+testcase 'set -- a; set -- p"$@"q'             '1|paq'
+testcase 'set -- a; set -- "$@"q'              '1|aq'
+testcase 'set -- a; set -- p"$@"'              '1|pa'
+
+test "x$failures" = x
diff --git a/sh/tests/parameters/positional3.0 b/sh/tests/parameters/positional3.0
new file mode 100644 (file)
index 0000000..1200469
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+r=$(${SH} -c 'echo ${01:+yes}${010:+yes}' '' a '' '' '' '' '' '' '' '' b)
+[ "$r" = yesyes ]
diff --git a/sh/tests/parameters/positional4.0 b/sh/tests/parameters/positional4.0
new file mode 100644 (file)
index 0000000..c1c380c
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+set -- "x$0" 2 3 4 5 6 7 8 9 "y$0"
+[ "${01}.${010}" = "$1.${10}" ]
diff --git a/sh/tests/parameters/positional5.0 b/sh/tests/parameters/positional5.0
new file mode 100644 (file)
index 0000000..eeaaba5
--- /dev/null
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+i=1
+r=0
+while [ $i -lt $((0x100000000)) ]; do
+       t=
+       eval t=\${$i-x}
+       case $t in
+       x) ;;
+       *) echo "Problem with \${$i}" >&2; r=1 ;;
+       esac
+       i=$((i + 0x10000000))
+done
+exit $r
diff --git a/sh/tests/parameters/positional6.0 b/sh/tests/parameters/positional6.0
new file mode 100644 (file)
index 0000000..1410668
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+IFS=?
+set p r
+v=pqrs
+r=${v#"$*"}
+[ "$r" = pqrs ]
diff --git a/sh/tests/parameters/positional7.0 b/sh/tests/parameters/positional7.0
new file mode 100644 (file)
index 0000000..f170ad3
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+set -- / ''
+IFS=*
+set -- "$*"
+IFS=:
+args="$*"
+[ "$#:$args" = "1:/*" ]
diff --git a/sh/tests/parameters/pwd1.0 b/sh/tests/parameters/pwd1.0
new file mode 100644 (file)
index 0000000..0099379
--- /dev/null
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Check that bogus PWD values are not accepted from the environment.
+
+cd / || exit 3
+failures=0
+[ "$(PWD=foo ${SH} -c 'pwd')" = / ] || : $((failures += 1))
+[ "$(PWD=/var/empty ${SH} -c 'pwd')" = / ] || : $((failures += 1))
+[ "$(PWD=/var/empty/foo ${SH} -c 'pwd')" = / ] || : $((failures += 1))
+[ "$(PWD=/bin/ls ${SH} -c 'pwd')" = / ] || : $((failures += 1))
+
+exit $((failures != 0))
diff --git a/sh/tests/parameters/pwd2.0 b/sh/tests/parameters/pwd2.0
new file mode 100644 (file)
index 0000000..2297f8b
--- /dev/null
@@ -0,0 +1,24 @@
+# $FreeBSD$
+# Check that PWD is exported and accepted from the environment.
+set -e
+
+T=$(mktemp -d ${TMPDIR:-/tmp}/sh-test.XXXXXX)
+trap 'rm -rf $T' 0
+cd -P $T
+TP=$(pwd)
+mkdir test1
+ln -s test1 link
+cd link
+[ "$PWD" = "$TP/link" ]
+[ "$(pwd)" = "$TP/link" ]
+[ "$(pwd -P)" = "$TP/test1" ]
+[ "$(${SH} -c pwd)" = "$TP/link" ]
+[ "$(${SH} -c pwd\ -P)" = "$TP/test1" ]
+cd ..
+[ "$(pwd)" = "$TP" ]
+cd -P link
+[ "$PWD" = "$TP/test1" ]
+[ "$(pwd)" = "$TP/test1" ]
+[ "$(pwd -P)" = "$TP/test1" ]
+[ "$(${SH} -c pwd)" = "$TP/test1" ]
+[ "$(${SH} -c pwd\ -P)" = "$TP/test1" ]
diff --git a/sh/tests/parser/Makefile b/sh/tests/parser/Makefile
new file mode 100644 (file)
index 0000000..3c9e62c
--- /dev/null
@@ -0,0 +1,78 @@
+# $FreeBSD$
+
+TESTSDIR=      ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH=  functional_test
+
+FILESDIR=      ${TESTSDIR}
+
+FILES=         alias1.0
+FILES+=                alias2.0
+FILES+=                alias3.0
+FILES+=                alias4.0
+FILES+=                alias5.0
+FILES+=                alias6.0
+FILES+=                alias7.0
+FILES+=                alias8.0
+FILES+=                alias9.0
+FILES+=                alias10.0
+FILES+=                alias11.0
+FILES+=                alias12.0
+FILES+=                alias13.0
+FILES+=                alias14.0
+FILES+=                alias15.0 alias15.0.stdout
+FILES+=                and-pipe-not.0
+FILES+=                case1.0
+FILES+=                case2.0
+FILES+=                dollar-quote1.0
+FILES+=                dollar-quote2.0
+FILES+=                dollar-quote3.0
+FILES+=                dollar-quote4.0
+FILES+=                dollar-quote5.0
+FILES+=                dollar-quote6.0
+FILES+=                dollar-quote7.0
+FILES+=                dollar-quote8.0
+FILES+=                dollar-quote9.0
+FILES+=                dollar-quote10.0
+FILES+=                dollar-quote11.0
+FILES+=                empty-braces1.0
+FILES+=                empty-cmd1.0
+FILES+=                for1.0
+FILES+=                for2.0
+FILES+=                func1.0
+FILES+=                func2.0
+FILES+=                func3.0
+FILES+=                heredoc1.0
+FILES+=                heredoc2.0
+FILES+=                heredoc3.0
+FILES+=                heredoc4.0
+FILES+=                heredoc5.0
+FILES+=                heredoc6.0
+FILES+=                heredoc7.0
+FILES+=                heredoc8.0
+FILES+=                heredoc9.0
+FILES+=                heredoc10.0
+FILES+=                heredoc11.0
+FILES+=                heredoc12.0
+FILES+=                line-cont1.0
+FILES+=                line-cont2.0
+FILES+=                line-cont3.0
+FILES+=                line-cont4.0
+FILES+=                line-cont5.0
+FILES+=                line-cont6.0
+FILES+=                line-cont7.0
+FILES+=                line-cont8.0
+FILES+=                line-cont9.0
+FILES+=                line-cont10.0
+FILES+=                line-cont11.0
+FILES+=                no-space1.0
+FILES+=                no-space2.0
+FILES+=                only-redir1.0
+FILES+=                only-redir2.0
+FILES+=                only-redir3.0
+FILES+=                only-redir4.0
+FILES+=                pipe-not1.0
+FILES+=                var-assign1.0
+
+.include <bsd.test.mk>
diff --git a/sh/tests/parser/alias1.0 b/sh/tests/parser/alias1.0
new file mode 100644 (file)
index 0000000..75dd9ab
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+alias alias0=exit
+eval 'alias0 0'
+exit 1
diff --git a/sh/tests/parser/alias10.0 b/sh/tests/parser/alias10.0
new file mode 100644 (file)
index 0000000..30d99f4
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+# This test may start consuming memory indefinitely if it fails.
+ulimit -t 5 2>/dev/null
+ulimit -v 100000 2>/dev/null
+
+alias echo='echo'
+alias echo='echo'
+[ "`eval echo b`" = b ]
diff --git a/sh/tests/parser/alias11.0 b/sh/tests/parser/alias11.0
new file mode 100644 (file)
index 0000000..522264f
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+alias alias0=alias1
+alias alias1=exit
+eval 'alias0 0'
+exit 3
diff --git a/sh/tests/parser/alias12.0 b/sh/tests/parser/alias12.0
new file mode 100644 (file)
index 0000000..2e43791
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+unalias -a
+alias alias0=command
+alias true='echo bad'
+eval 'alias0 true'
diff --git a/sh/tests/parser/alias13.0 b/sh/tests/parser/alias13.0
new file mode 100644 (file)
index 0000000..53b949d
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+unalias -a
+alias command=command
+alias true='echo bad'
+eval 'command true'
diff --git a/sh/tests/parser/alias14.0 b/sh/tests/parser/alias14.0
new file mode 100644 (file)
index 0000000..1b92fc0
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+alias command='command '
+alias alias0=exit
+eval 'command alias0 0'
+exit 3
diff --git a/sh/tests/parser/alias15.0 b/sh/tests/parser/alias15.0
new file mode 100644 (file)
index 0000000..f0fbadb
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+f_echoanddo() {
+       printf '%s\n' "$*"
+       "$@"
+}
+
+alias echoanddo='f_echoanddo '
+alias alias0='echo test2'
+eval 'echoanddo echo test1'
+eval 'echoanddo alias0'
+exit 0
diff --git a/sh/tests/parser/alias15.0.stdout b/sh/tests/parser/alias15.0.stdout
new file mode 100644 (file)
index 0000000..6dd179c
--- /dev/null
@@ -0,0 +1,4 @@
+echo test1
+test1
+echo test2
+test2
diff --git a/sh/tests/parser/alias2.0 b/sh/tests/parser/alias2.0
new file mode 100644 (file)
index 0000000..ae99b8a
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+alias alias0=exit
+x=alias0
+eval 'case $x in alias0) exit 0;; esac'
+exit 1
diff --git a/sh/tests/parser/alias3.0 b/sh/tests/parser/alias3.0
new file mode 100644 (file)
index 0000000..e0721e2
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+alias alias0=exit
+x=alias0
+eval 'case $x in "alias0") alias0 0;; esac'
+exit 1
diff --git a/sh/tests/parser/alias4.0 b/sh/tests/parser/alias4.0
new file mode 100644 (file)
index 0000000..19332ed
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+alias alias0=exit
+eval 'x=1 alias0 0'
+exit 1
diff --git a/sh/tests/parser/alias5.0 b/sh/tests/parser/alias5.0
new file mode 100644 (file)
index 0000000..3d0205f
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+alias alias0=exit
+eval '</dev/null alias0 0'
+exit 1
diff --git a/sh/tests/parser/alias6.0 b/sh/tests/parser/alias6.0
new file mode 100644 (file)
index 0000000..c723d08
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+alias alias0='| cat >/dev/null'
+
+eval '{ echo bad; } alias0'
+eval '(echo bad)alias0'
diff --git a/sh/tests/parser/alias7.0 b/sh/tests/parser/alias7.0
new file mode 100644 (file)
index 0000000..b26f0dd
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+alias echo='echo a'
+[ "`eval echo b`" = "a b" ]
diff --git a/sh/tests/parser/alias8.0 b/sh/tests/parser/alias8.0
new file mode 100644 (file)
index 0000000..7fc2f15
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+alias echo='echo'
+[ "`eval echo b`" = b ]
diff --git a/sh/tests/parser/alias9.0 b/sh/tests/parser/alias9.0
new file mode 100644 (file)
index 0000000..6bd8808
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+alias alias0=:
+alias alias0=exit
+eval 'alias0 0'
+exit 1
diff --git a/sh/tests/parser/and-pipe-not.0 b/sh/tests/parser/and-pipe-not.0
new file mode 100644 (file)
index 0000000..35b125c
--- /dev/null
@@ -0,0 +1,2 @@
+# $FreeBSD$
+true && ! true | false
diff --git a/sh/tests/parser/case1.0 b/sh/tests/parser/case1.0
new file mode 100644 (file)
index 0000000..49b4c45
--- /dev/null
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+keywords='if then else elif fi while until for do done { } case esac ! in'
+
+# Keywords can be used unquoted in case statements, except the keyword
+# esac as the first pattern of a '|' alternation without a starting '('.
+# (POSIX doesn't seem to require (esac) to work.)
+for k in $keywords; do
+       eval "case $k in (foo|$k) ;; *) echo bad ;; esac"
+       eval "case $k in ($k) ;; *) echo bad ;; esac"
+       eval "case $k in foo|$k) ;; *) echo bad ;; esac"
+       [ "$k" = esac ] && continue
+       eval "case $k in $k) ;; *) echo bad ;; esac"
+done
diff --git a/sh/tests/parser/case2.0 b/sh/tests/parser/case2.0
new file mode 100644 (file)
index 0000000..14610e4
--- /dev/null
@@ -0,0 +1,32 @@
+# $FreeBSD$
+
+# Pretty much only ash derivatives can parse all of this.
+
+f1() {
+       x=$(case x in
+               (x|esac) ;;
+               (*) echo bad >&2 ;;
+       esac)
+}
+f1
+f2() {
+       x=$(case x in
+               (x|esac) ;;
+               (*) echo bad >&2
+       esac)
+}
+f2
+f3() {
+       x=$(case x in
+               x|esac) ;;
+               *) echo bad >&2 ;;
+       esac)
+}
+f3
+f4() {
+       x=$(case x in
+               x|esac) ;;
+               *) echo bad >&2
+       esac)
+}
+f4
diff --git a/sh/tests/parser/dollar-quote1.0 b/sh/tests/parser/dollar-quote1.0
new file mode 100644 (file)
index 0000000..1206141
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+set -e
+
+[ $'hi' = hi ]
+[ $'hi
+there' = 'hi
+there' ]
+[ $'\"\'\\\a\b\f\t\v' = "\"'\\$(printf "\a\b\f\t\v")" ]
+[ $'hi\nthere' = 'hi
+there' ]
+[ $'a\rb' = "$(printf "a\rb")" ]
diff --git a/sh/tests/parser/dollar-quote10.0 b/sh/tests/parser/dollar-quote10.0
new file mode 100644 (file)
index 0000000..ad166da
--- /dev/null
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+# a umlaut
+s=$(printf '\303\244')
+# euro sign
+s=$s$(printf '\342\202\254')
+
+# Start a new shell so the locale change is picked up.
+ss="$(LC_ALL=en_US.UTF-8 ${SH} -c "printf %s \$'\u00e4\u20ac'")"
+[ "$s" = "$ss" ]
diff --git a/sh/tests/parser/dollar-quote11.0 b/sh/tests/parser/dollar-quote11.0
new file mode 100644 (file)
index 0000000..2e872ab
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+# some sort of 't' outside BMP
+s=$s$(printf '\360\235\225\245')
+
+# Start a new shell so the locale change is picked up.
+ss="$(LC_ALL=en_US.UTF-8 ${SH} -c "printf %s \$'\U0001d565'")"
+[ "$s" = "$ss" ]
diff --git a/sh/tests/parser/dollar-quote2.0 b/sh/tests/parser/dollar-quote2.0
new file mode 100644 (file)
index 0000000..4617ed8
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+# This depends on the ASCII character set.
+
+[ $'\e' = "$(printf "\033")" ]
diff --git a/sh/tests/parser/dollar-quote3.0 b/sh/tests/parser/dollar-quote3.0
new file mode 100644 (file)
index 0000000..a7e6852
--- /dev/null
@@ -0,0 +1,22 @@
+# $FreeBSD$
+
+unset LC_ALL
+LC_CTYPE=en_US.ISO8859-1
+export LC_CTYPE
+
+e=
+for i in 0 1 2 3; do
+       for j in 0 1 2 3 4 5 6 7; do
+               for k in 0 1 2 3 4 5 6 7; do
+                       case $i$j$k in
+                       000) continue ;;
+                       esac
+                       e="$e\\$i$j$k"
+               done
+       done
+done
+ee=`printf "$e"`
+[ "${#ee}" = 255 ] || echo length bad
+
+# Start a new shell so the locale change is picked up.
+[ "$(${SH} -c "printf %s \$'$e'")" = "$ee" ]
diff --git a/sh/tests/parser/dollar-quote4.0 b/sh/tests/parser/dollar-quote4.0
new file mode 100644 (file)
index 0000000..f620af5
--- /dev/null
@@ -0,0 +1,19 @@
+# $FreeBSD$
+
+unset LC_ALL
+LC_CTYPE=en_US.ISO8859-1
+export LC_CTYPE
+
+e=
+for i in 0 1 2 3 4 5 6 7 8 9 a b c d e f; do
+       for j in 0 1 2 3 4 5 6 7 8 9 a b c d e f; do
+               case $i$j in
+               00) continue ;;
+               esac
+               e="$e\x$i$j"
+       done
+done
+
+# Start a new shell so the locale change is picked up.
+ee="$(${SH} -c "printf %s \$'$e'")"
+[ "${#ee}" = 255 ] || echo length bad
diff --git a/sh/tests/parser/dollar-quote5.0 b/sh/tests/parser/dollar-quote5.0
new file mode 100644 (file)
index 0000000..c2c44ca
--- /dev/null
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+# This depends on the ASCII character set.
+
+set -e
+
+[ $'\ca\cb\cc\cd\ce\cf\cg\ch\ci\cj\ck\cl\cm\cn\co\cp\cq\cr\cs\ct\cu\cv\cw\cx\cy\cz' = $'\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032' ]
+[ $'\cA\cB\cC\cD\cE\cF\cG\cH\cI\cJ\cK\cL\cM\cN\cO\cP\cQ\cR\cS\cT\cU\cV\cW\cX\cY\cZ' = $'\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032' ]
+[ $'\c[' = $'\033' ]
+[ $'\c]' = $'\035' ]
+[ $'\c^' = $'\036' ]
+[ $'\c_' = $'\037' ]
diff --git a/sh/tests/parser/dollar-quote6.0 b/sh/tests/parser/dollar-quote6.0
new file mode 100644 (file)
index 0000000..a4b1e3f
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+# This depends on the ASCII character set.
+
+[ $'\c\\' = $'\034' ]
diff --git a/sh/tests/parser/dollar-quote7.0 b/sh/tests/parser/dollar-quote7.0
new file mode 100644 (file)
index 0000000..c866b1a
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+set -e
+
+[ $'\u0024\u0040\u0060' = '$@`' ]
+[ $'\U00000024\U00000040\U00000060' = '$@`' ]
diff --git a/sh/tests/parser/dollar-quote8.0 b/sh/tests/parser/dollar-quote8.0
new file mode 100644 (file)
index 0000000..8f0b41a
--- /dev/null
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+[ $'hello\0' = hello ]
+[ $'hello\0world' = hello ]
+[ $'hello\0'$'world' = helloworld ]
+[ $'hello\000' = hello ]
+[ $'hello\000world' = hello ]
+[ $'hello\000'$'world' = helloworld ]
+[ $'hello\x00' = hello ]
+[ $'hello\x00world' = hello ]
+[ $'hello\x00'$'world' = helloworld ]
diff --git a/sh/tests/parser/dollar-quote9.0 b/sh/tests/parser/dollar-quote9.0
new file mode 100644 (file)
index 0000000..df64b7d
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+# POSIX and C99 say D800-DFFF are undefined in a universal character name.
+# We reject this but many other shells expand to something that looks like
+# CESU-8.
+
+v=$( (eval ": \$'\uD800'") 2>&1 >/dev/null)
+[ $? -ne 0 ] && [ -n "$v" ]
diff --git a/sh/tests/parser/empty-braces1.0 b/sh/tests/parser/empty-braces1.0
new file mode 100644 (file)
index 0000000..5ab443c
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+# Unfortunately, some scripts depend on the extension of allowing an empty
+# pair of braces.
+
+{ } &
+wait $!
diff --git a/sh/tests/parser/empty-cmd1.0 b/sh/tests/parser/empty-cmd1.0
new file mode 100644 (file)
index 0000000..f8b01e9
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+! (eval ': || f()') 2>/dev/null
diff --git a/sh/tests/parser/for1.0 b/sh/tests/parser/for1.0
new file mode 100644 (file)
index 0000000..eb7c881
--- /dev/null
@@ -0,0 +1,29 @@
+# $FreeBSD$
+
+nl='
+'
+list=' a b c'
+for s1 in "$nl" " "; do
+       for s2 in "$nl" ";" ";$nl"; do
+               for s3 in "$nl" " "; do
+                       r=''
+                       eval "for i${s1}in ${list}${s2}do${s3}r=\"\$r \$i\"; done"
+                       [ "$r" = "$list" ] || exit 1
+               done
+       done
+done
+set -- $list
+for s2 in "$nl" " "; do
+       for s3 in "$nl" " "; do
+               r=''
+               eval "for i${s2}do${s3}r=\"\$r \$i\"; done"
+               [ "$r" = "$list" ] || exit 1
+       done
+done
+for s1 in "$nl" " "; do
+       for s2 in "$nl" ";" ";$nl"; do
+               for s3 in "$nl" " "; do
+                       eval "for i${s1}in${s2}do${s3}exit 1; done"
+               done
+       done
+done
diff --git a/sh/tests/parser/for2.0 b/sh/tests/parser/for2.0
new file mode 100644 (file)
index 0000000..54ebfc3
--- /dev/null
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+# Common extensions to the 'for' syntax.
+
+nl='
+'
+list=' a b c'
+set -- $list
+for s2 in ";" ";$nl"; do
+       for s3 in "$nl" " "; do
+               r=''
+               eval "for i${s2}do${s3}r=\"\$r \$i\"; done"
+               [ "$r" = "$list" ] || exit 1
+       done
+done
diff --git a/sh/tests/parser/func1.0 b/sh/tests/parser/func1.0
new file mode 100644 (file)
index 0000000..4e887b2
--- /dev/null
@@ -0,0 +1,25 @@
+# $FreeBSD$
+# POSIX does not require these bytes to work in function names,
+# but making them all work seems a good goal.
+
+failures=0
+unset LC_ALL
+export LC_CTYPE=en_US.ISO8859-1
+i=128
+set -f
+while [ "$i" -le 255 ]; do
+       c=$(printf \\"$(printf %o "$i")")
+       ok=0
+       eval "$c() { ok=1; }"
+       $c
+       ok1=$ok
+       ok=0
+       "$c"
+       if [ "$ok" != 1 ] || [ "$ok1" != 1 ]; then
+               echo "Bad results for character $i" >&2
+               : $((failures += 1))
+       fi
+       unset -f $c
+       i=$((i+1))
+done
+exit $((failures > 0))
diff --git a/sh/tests/parser/func2.0 b/sh/tests/parser/func2.0
new file mode 100644 (file)
index 0000000..5fd4dda
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+f() { return 42; }
+f() { return 3; } &
+f
+[ $? -eq 42 ]
diff --git a/sh/tests/parser/func3.0 b/sh/tests/parser/func3.0
new file mode 100644 (file)
index 0000000..dcac732
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+name=/var/empty/nosuch
+f() { true; } <$name
+name=/dev/null
+f
diff --git a/sh/tests/parser/heredoc1.0 b/sh/tests/parser/heredoc1.0
new file mode 100644 (file)
index 0000000..5ce3897
--- /dev/null
@@ -0,0 +1,85 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+check '"$(cat <<EOF
+hi
+EOF
+)" = hi'
+
+check '"$(cat <<EOF
+${$+hi}
+EOF
+)" = hi'
+
+unset yy
+check '"$(cat <<EOF
+${yy-hi}
+EOF
+)" = hi'
+
+check '"$(cat <<EOF
+${$+hi
+there}
+EOF
+)" = "hi
+there"'
+
+check '"$(cat <<EOF
+$((1+1))
+EOF
+)" = 2'
+
+check '"$(cat <<EOF
+$(echo hi)
+EOF
+)" = hi'
+
+check '"$(cat <<EOF
+`echo hi`
+EOF
+)" = hi'
+
+check '"$(cat <<\EOF
+${$+hi}
+EOF
+)" = "\${\$+hi}"'
+
+check '"$(cat <<\EOF
+$(
+EOF
+)" = \$\('
+
+check '"$(cat <<\EOF
+`
+EOF
+)" = \`'
+
+check '"$(cat <<EOF
+"
+EOF
+)" = \"'
+
+check '"$(cat <<\EOF
+"
+EOF
+)" = \"'
+
+check '"$(cat <<esac
+'"'"'
+esac
+)" = "'"'"'"'
+
+check '"$(cat <<\)
+'"'"'
+)
+)" = "'"'"'"'
+
+exit $((failures != 0))
diff --git a/sh/tests/parser/heredoc10.0 b/sh/tests/parser/heredoc10.0
new file mode 100644 (file)
index 0000000..27369a0
--- /dev/null
@@ -0,0 +1,49 @@
+# $FreeBSD$
+
+# It may be argued that
+#   x=$(cat <<EOF
+#   foo
+#   EOF)
+# is a valid complete command that sets x to foo, because
+#   cat <<EOF
+#   foo
+#   EOF
+# is a valid script even without the final newline.
+# However, if the here-document is not within a new-style command substitution
+# or there are other constructs nested inside the command substitution that
+# need terminators, the delimiter at the start of a line followed by a close
+# parenthesis is clearly a literal part of the here-document.
+
+# This file contains tests that may not work with simplistic $(...) parsers.
+# The open parentheses in comments help mksh, but not zsh.
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+check '"$(cat <<EOF # (
+EOF )
+EOF
+)" = "EOF )"'
+
+check '"$({ cat <<EOF # (
+EOF)
+EOF
+})" = "EOF)"'
+
+check '"$(if :; then cat <<EOF # (
+EOF)
+EOF
+fi)" = "EOF)"'
+
+check '"$( (cat <<EOF # (
+EOF)
+EOF
+))" = "EOF)"'
+
+exit $((failures != 0))
diff --git a/sh/tests/parser/heredoc11.0 b/sh/tests/parser/heredoc11.0
new file mode 100644 (file)
index 0000000..5839e46
--- /dev/null
@@ -0,0 +1,26 @@
+# $FreeBSD$
+
+failures=''
+
+check() {
+       if eval "[ $* ]"; then
+               :
+       else
+               echo "Failed: $*"
+               failures=x$failures
+       fi
+}
+
+check '`cat <<EOF
+foo
+EOF` = foo'
+
+check '"`cat <<EOF
+foo
+EOF`" = foo'
+
+check '`eval "cat <<EOF
+foo
+EOF"` = foo'
+
+test "x$failures" = x
diff --git a/sh/tests/parser/heredoc12.0 b/sh/tests/parser/heredoc12.0
new file mode 100644 (file)
index 0000000..9648384
--- /dev/null
@@ -0,0 +1,47 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+longmark=`printf %01000d 4`
+longmarkstripped=`printf %0999d 0`
+
+check '"$(cat <<'"$longmark
+$longmark"'
+echo yes)" = "yes"'
+
+check '"$(cat <<\'"$longmark
+$longmark"'
+echo yes)" = "yes"'
+
+check '"$(cat <<'"$longmark
+yes
+$longmark"'
+)" = "yes"'
+
+check '"$(cat <<\'"$longmark
+yes
+$longmark"'
+)" = "yes"'
+
+check '"$(cat <<'"$longmark
+$longmarkstripped
+$longmark.
+$longmark"'
+)" = "'"$longmarkstripped
+$longmark."'"'
+
+check '"$(cat <<\'"$longmark
+$longmarkstripped
+$longmark.
+$longmark"'
+)" = "'"$longmarkstripped
+$longmark."'"'
+
+exit $((failures != 0))
diff --git a/sh/tests/parser/heredoc2.0 b/sh/tests/parser/heredoc2.0
new file mode 100644 (file)
index 0000000..4bb85ad
--- /dev/null
@@ -0,0 +1,39 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+s='ast*que?non' sq=\' dq=\"
+
+check '"$(cat <<EOF
+${s}
+EOF
+)" = "ast*que?non"'
+
+check '"$(cat <<EOF
+${s+'$sq'x'$sq'}
+EOF
+)" = ${sq}x${sq}'
+
+check '"$(cat <<EOF
+${s#ast}
+EOF
+)" = "*que?non"'
+
+check '"$(cat <<EOF
+${s##"ast"}
+EOF
+)" = "*que?non"'
+
+check '"$(cat <<EOF
+${s##'$sq'ast'$sq'}
+EOF
+)" = "*que?non"'
+
+exit $((failures != 0))
diff --git a/sh/tests/parser/heredoc3.0 b/sh/tests/parser/heredoc3.0
new file mode 100644 (file)
index 0000000..b250272
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+# This may be expected to work, but pretty much only ash derivatives allow it.
+
+test "$(cat <<EOF)" = "hi there"
+hi there
+EOF
diff --git a/sh/tests/parser/heredoc4.0 b/sh/tests/parser/heredoc4.0
new file mode 100644 (file)
index 0000000..fa3af5f
--- /dev/null
@@ -0,0 +1,44 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+f() {
+       cat <<EOF && echo `echo bar`
+foo
+EOF
+}
+check '"`f`" = "foo
+bar"'
+
+f() {
+       cat <<EOF && echo $(echo bar)
+foo
+EOF
+}
+check '"$(f)" = "foo
+bar"'
+
+f() {
+       echo `echo bar` && cat <<EOF
+foo
+EOF
+}
+check '"`f`" = "bar
+foo"'
+
+f() {
+       echo $(echo bar) && cat <<EOF
+foo
+EOF
+}
+check '"$(f)" = "bar
+foo"'
+
+exit $((failures != 0))
diff --git a/sh/tests/parser/heredoc5.0 b/sh/tests/parser/heredoc5.0
new file mode 100644 (file)
index 0000000..84b0eb2
--- /dev/null
@@ -0,0 +1,56 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+f() {
+       cat <<EOF && echo `cat <<EOF
+bar
+EOF
+`
+foo
+EOF
+}
+check '"`f`" = "foo
+bar"'
+
+f() {
+       cat <<EOF && echo $(cat <<EOF
+bar
+EOF
+)
+foo
+EOF
+}
+check '"$(f)" = "foo
+bar"'
+
+f() {
+       echo `cat <<EOF
+bar
+EOF
+` && cat <<EOF
+foo
+EOF
+}
+check '"`f`" = "bar
+foo"'
+
+f() {
+       echo $(cat <<EOF
+bar
+EOF
+) && cat <<EOF
+foo
+EOF
+}
+check '"$(f)" = "bar
+foo"'
+
+exit $((failures != 0))
diff --git a/sh/tests/parser/heredoc6.0 b/sh/tests/parser/heredoc6.0
new file mode 100644 (file)
index 0000000..3a634de
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+r=
+! command eval ": <<EOF; )" 2>/dev/null; command eval : hi \${r:=0}
+exit ${r:-3}
diff --git a/sh/tests/parser/heredoc7.0 b/sh/tests/parser/heredoc7.0
new file mode 100644 (file)
index 0000000..a150106
--- /dev/null
@@ -0,0 +1,19 @@
+# $FreeBSD$
+
+# Some of these created malformed parse trees with null pointers for here
+# documents, causing the here document writing process to segfault.
+eval ': <<EOF'
+eval ': <<EOF;'
+eval '`: <<EOF`'
+eval '`: <<EOF;`'
+eval '`: <<EOF`;'
+eval '`: <<EOF;`;'
+
+# Some of these created malformed parse trees with null pointers for here
+# documents, causing sh to segfault.
+eval ': <<\EOF'
+eval ': <<\EOF;'
+eval '`: <<\EOF`'
+eval '`: <<\EOF;`'
+eval '`: <<\EOF`;'
+eval '`: <<\EOF;`;'
diff --git a/sh/tests/parser/heredoc8.0 b/sh/tests/parser/heredoc8.0
new file mode 100644 (file)
index 0000000..598358a
--- /dev/null
@@ -0,0 +1,20 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+s='ast*que?non' sq=\' dq=\"
+
+# This is possibly useful but differs from other shells.
+check '"$(cat <<EOF
+${s+"x"}
+EOF
+)" = ${dq}x${dq}'
+
+exit $((failures != 0))
diff --git a/sh/tests/parser/heredoc9.0 b/sh/tests/parser/heredoc9.0
new file mode 100644 (file)
index 0000000..125a542
--- /dev/null
@@ -0,0 +1,58 @@
+# $FreeBSD$
+
+# It may be argued that
+#   x=$(cat <<EOF
+#   foo
+#   EOF)
+# is a valid complete command that sets x to foo, because
+#   cat <<EOF
+#   foo
+#   EOF
+# is a valid script even without the final newline.
+# However, if the here-document is not within a new-style command substitution
+# or there are other constructs nested inside the command substitution that
+# need terminators, the delimiter at the start of a line followed by a close
+# parenthesis is clearly a literal part of the here-document.
+
+# This file contains tests that also work with simplistic $(...) parsers.
+
+failures=0
+
+check() {
+       if ! eval "[ $* ]"; then
+               echo "Failed: $*"
+               : $((failures += 1))
+       fi
+}
+
+check '`${SH} -c "cat <<EOF
+EOF)
+EOF
+"` = "EOF)"'
+
+check '`${SH} -c "(cat <<EOF
+EOF)
+EOF
+)"` = "EOF)"'
+
+check '"`cat <<EOF
+EOF x
+EOF
+`" = "EOF x"'
+
+check '"`cat <<EOF
+EOF )
+EOF
+`" = "EOF )"'
+
+check '"`cat <<EOF
+EOF)
+EOF
+`" = "EOF)"'
+
+check '"$(cat <<EOF
+EOF x
+EOF
+)" = "EOF x"'
+
+exit $((failures != 0))
diff --git a/sh/tests/parser/line-cont1.0 b/sh/tests/parser/line-cont1.0
new file mode 100644 (file)
index 0000000..7ef5eba
--- /dev/null
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+i\
+f
+t\
+r\
+u\
+e
+t\
+h\
+e\
+n
+:
+\
+f\
+i
diff --git a/sh/tests/parser/line-cont10.0 b/sh/tests/parser/line-cont10.0
new file mode 100644 (file)
index 0000000..1e74108
--- /dev/null
@@ -0,0 +1,18 @@
+# $FreeBSD$
+
+v=XaaaXbbbX
+[ "${v\
+#\
+*\
+a}.${v\
+#\
+#\
+*\
+a}.${v\
+%\
+b\
+*}.${v\
+%\
+%\
+b\
+*}" = aaXbbbX.XbbbX.XaaaXbb.XaaaX ]
diff --git a/sh/tests/parser/line-cont11.0 b/sh/tests/parser/line-cont11.0
new file mode 100644 (file)
index 0000000..22e4975
--- /dev/null
@@ -0,0 +1,23 @@
+# $FreeBSD$
+
+T=$(mktemp "${TMPDIR:-/tmp}/sh-test.XXXXXXXX") || exit
+trap 'rm -f -- "$T"' 0
+w='#A'
+# A naive pgetc_linecont() would push back two characters here, which
+# fails if a new buffer is read between the two characters.
+c='${w#\#}'
+c=$c$c$c$c
+c=$c$c$c$c
+c=$c$c$c$c
+c=$c$c$c$c
+c=$c$c$c$c
+c=$c$c$c$c
+printf 'v=%s\n' "$c" >"$T"
+. "$T"
+if [ "${#v}" != 4096 ]; then
+       echo "Length is bad (${#v})"
+       exit 3
+fi
+case $v in
+*[!A]*) echo "Content is bad"; exit 3 ;;
+esac
diff --git a/sh/tests/parser/line-cont2.0 b/sh/tests/parser/line-cont2.0
new file mode 100644 (file)
index 0000000..9a293fa
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+[ "a\
+b" = ab ]
diff --git a/sh/tests/parser/line-cont3.0 b/sh/tests/parser/line-cont3.0
new file mode 100644 (file)
index 0000000..09d3aa8
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+v=`printf %s 'a\
+b'`
+w="`printf %s 'c\
+d'`"
+[ "$v$w" = abcd ]
diff --git a/sh/tests/parser/line-cont4.0 b/sh/tests/parser/line-cont4.0
new file mode 100644 (file)
index 0000000..5803276
--- /dev/null
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+v=abcd
+[ "$\
+v.$\
+{v}.${\
+v}.${v\
+}" = abcd.abcd.abcd.abcd ]
diff --git a/sh/tests/parser/line-cont5.0 b/sh/tests/parser/line-cont5.0
new file mode 100644 (file)
index 0000000..a7aa026
--- /dev/null
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+bad=1
+case x in
+x\
+) ;\
+; *) exit 7
+esac &\
+& bad= &\
+& : >\
+>/dev/null
+
+false |\
+| [ -z "$bad" ]
diff --git a/sh/tests/parser/line-cont6.0 b/sh/tests/parser/line-cont6.0
new file mode 100644 (file)
index 0000000..b12125b
--- /dev/null
@@ -0,0 +1,23 @@
+# $FreeBSD$
+
+v0\
+=abc
+
+v=$(cat <\
+<\
+E\
+O\
+F
+${v0}d
+EOF
+)
+
+w=$(cat <\
+<\
+-\
+EOF
+       efgh
+EOF
+)
+
+[ "$v.$w" = "abcd.efgh" ]
diff --git a/sh/tests/parser/line-cont7.0 b/sh/tests/parser/line-cont7.0
new file mode 100644 (file)
index 0000000..27f8aec
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+[ "$(\
+(
+1\
++ 1)\
+)" = 2 ]
diff --git a/sh/tests/parser/line-cont8.0 b/sh/tests/parser/line-cont8.0
new file mode 100644 (file)
index 0000000..8866776
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+set -- a b c d e f g h i j
+[ "${1\
+0\
+}" = j ]
diff --git a/sh/tests/parser/line-cont9.0 b/sh/tests/parser/line-cont9.0
new file mode 100644 (file)
index 0000000..4e73c8f
--- /dev/null
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+[ "${$\
+:\
++\
+xyz}" = xyz ]
diff --git a/sh/tests/parser/no-space1.0 b/sh/tests/parser/no-space1.0
new file mode 100644 (file)
index 0000000..6df9f63
--- /dev/null
@@ -0,0 +1,18 @@
+# $FreeBSD$
+
+# These are ugly but are required to work.
+
+set -e
+
+while(false)do(:)done
+if(false)then(:)fi
+if(false)then(:)else(:)fi
+(:&&:)||:
+until(:)do(:)done
+case x in(x);;*)exit 1;(:)esac
+case x in(x);;*)exit 1;;esac
+for i do(:)done
+{(:)}
+f(){(:)}
+:|:
+(:)|(:)
diff --git a/sh/tests/parser/no-space2.0 b/sh/tests/parser/no-space2.0
new file mode 100644 (file)
index 0000000..4e8447b
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+# This conflicts with ksh extended patterns but occurs in the wild.
+
+set -e
+
+!(false)
diff --git a/sh/tests/parser/only-redir1.0 b/sh/tests/parser/only-redir1.0
new file mode 100644 (file)
index 0000000..46076c8
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+</dev/null &
+wait $!
diff --git a/sh/tests/parser/only-redir2.0 b/sh/tests/parser/only-redir2.0
new file mode 100644 (file)
index 0000000..b9e9501
--- /dev/null
@@ -0,0 +1,2 @@
+# $FreeBSD$
+</dev/null | :
diff --git a/sh/tests/parser/only-redir3.0 b/sh/tests/parser/only-redir3.0
new file mode 100644 (file)
index 0000000..128a483
--- /dev/null
@@ -0,0 +1,2 @@
+# $FreeBSD$
+case x in x) </dev/null ;; esac
diff --git a/sh/tests/parser/only-redir4.0 b/sh/tests/parser/only-redir4.0
new file mode 100644 (file)
index 0000000..d804e12
--- /dev/null
@@ -0,0 +1,2 @@
+# $FreeBSD$
+case x in x) </dev/null ;& esac
diff --git a/sh/tests/parser/pipe-not1.0 b/sh/tests/parser/pipe-not1.0
new file mode 100644 (file)
index 0000000..9842ff0
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+: | ! : | false
diff --git a/sh/tests/parser/var-assign1.0 b/sh/tests/parser/var-assign1.0
new file mode 100644 (file)
index 0000000..1fd3b26
--- /dev/null
@@ -0,0 +1,19 @@
+# $FreeBSD$
+# In a variable assignment, both the name and the equals sign must be entirely
+# unquoted. Therefore, there is only one assignment below; the other words
+# containing equals signs are command words.
+
+abc=0
+\abc=1 2>/dev/null
+a\bc=2 2>/dev/null
+abc\=3 2>/dev/null
+a\bc\=4 2>/dev/null
+'abc'=5 2>/dev/null
+a'b'c=6 2>/dev/null
+abc'='7 2>/dev/null
+'abc=8' 2>/dev/null
+"abc"=9 2>/dev/null
+a"b"c=10 2>/dev/null
+abc"="11 2>/dev/null
+"abc=12" 2>/dev/null
+[ "$abc" = 0 ]
diff --git a/sh/tests/set-e/Makefile b/sh/tests/set-e/Makefile
new file mode 100644 (file)
index 0000000..f733b60
--- /dev/null
@@ -0,0 +1,46 @@
+# $FreeBSD$
+
+TESTSDIR=      ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH=  functional_test
+
+FILESDIR=      ${TESTSDIR}
+
+FILES=         and1.0
+FILES+=                and2.1
+FILES+=                and3.0
+FILES+=                and4.0
+FILES+=                background1.0
+FILES+=                cmd1.0
+FILES+=                cmd2.1
+FILES+=                elif1.0
+FILES+=                elif2.0
+FILES+=                eval1.0
+FILES+=                eval2.1
+FILES+=                for1.0
+FILES+=                func1.0
+FILES+=                func2.1
+FILES+=                if1.0
+FILES+=                if2.0
+FILES+=                if3.0
+FILES+=                not1.0
+FILES+=                not2.0
+FILES+=                or1.0
+FILES+=                or2.0
+FILES+=                or3.1
+FILES+=                pipe1.1
+FILES+=                pipe2.0
+FILES+=                return1.0
+FILES+=                semi1.1
+FILES+=                semi2.1
+FILES+=                subshell1.0
+FILES+=                subshell2.1
+FILES+=                until1.0
+FILES+=                until2.0
+FILES+=                until3.0
+FILES+=                while1.0
+FILES+=                while2.0
+FILES+=                while3.0
+
+.include <bsd.test.mk>
diff --git a/sh/tests/set-e/and1.0 b/sh/tests/set-e/and1.0
new file mode 100644 (file)
index 0000000..607b7c3
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+true && true
diff --git a/sh/tests/set-e/and2.1 b/sh/tests/set-e/and2.1
new file mode 100644 (file)
index 0000000..78e203a
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+true && false
+exit 0
diff --git a/sh/tests/set-e/and3.0 b/sh/tests/set-e/and3.0
new file mode 100644 (file)
index 0000000..9fafb1c
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+false && true
+exit 0
diff --git a/sh/tests/set-e/and4.0 b/sh/tests/set-e/and4.0
new file mode 100644 (file)
index 0000000..25d0e61
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+false && false
+exit 0
diff --git a/sh/tests/set-e/background1.0 b/sh/tests/set-e/background1.0
new file mode 100644 (file)
index 0000000..21577f4
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+false &
diff --git a/sh/tests/set-e/cmd1.0 b/sh/tests/set-e/cmd1.0
new file mode 100644 (file)
index 0000000..67fdcbc
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+true
diff --git a/sh/tests/set-e/cmd2.1 b/sh/tests/set-e/cmd2.1
new file mode 100644 (file)
index 0000000..7cd8b09
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+false
+exit 0
diff --git a/sh/tests/set-e/elif1.0 b/sh/tests/set-e/elif1.0
new file mode 100644 (file)
index 0000000..6a5937d
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+set -e
+if false; then
+       :
+elif false; then
+       :
+fi
diff --git a/sh/tests/set-e/elif2.0 b/sh/tests/set-e/elif2.0
new file mode 100644 (file)
index 0000000..9dbb4bf
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+set -e
+if false; then
+       :
+elif false; false; then
+       :
+fi
diff --git a/sh/tests/set-e/eval1.0 b/sh/tests/set-e/eval1.0
new file mode 100644 (file)
index 0000000..9b7f67b
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+eval false || true
diff --git a/sh/tests/set-e/eval2.1 b/sh/tests/set-e/eval2.1
new file mode 100644 (file)
index 0000000..8bb7f3a
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+eval false
+exit 0
diff --git a/sh/tests/set-e/for1.0 b/sh/tests/set-e/for1.0
new file mode 100644 (file)
index 0000000..67eb718
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+set -e
+f() {
+       for i in a b c; do
+               false
+               true
+       done
+}
+f || true
diff --git a/sh/tests/set-e/func1.0 b/sh/tests/set-e/func1.0
new file mode 100644 (file)
index 0000000..3c6b704
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+set -e
+f() {
+       false
+       true
+}
+f || true
diff --git a/sh/tests/set-e/func2.1 b/sh/tests/set-e/func2.1
new file mode 100644 (file)
index 0000000..cc76d6e
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+set -e
+f() {
+       false
+       exit 0
+}
+f
diff --git a/sh/tests/set-e/if1.0 b/sh/tests/set-e/if1.0
new file mode 100644 (file)
index 0000000..36aa4bd
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+set -e
+if false; then
+       :
+fi
diff --git a/sh/tests/set-e/if2.0 b/sh/tests/set-e/if2.0
new file mode 100644 (file)
index 0000000..4955408
--- /dev/null
@@ -0,0 +1,7 @@
+# $FreeBSD$
+set -e
+# PR 28852
+if true; then
+       false && true
+fi
+exit 0
diff --git a/sh/tests/set-e/if3.0 b/sh/tests/set-e/if3.0
new file mode 100644 (file)
index 0000000..a4916a8
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+set -e
+if false; false; then
+       :
+fi
diff --git a/sh/tests/set-e/not1.0 b/sh/tests/set-e/not1.0
new file mode 100644 (file)
index 0000000..21c089a
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+! true
+exit 0
diff --git a/sh/tests/set-e/not2.0 b/sh/tests/set-e/not2.0
new file mode 100644 (file)
index 0000000..7d93b4d
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+! false
+! eval false
diff --git a/sh/tests/set-e/or1.0 b/sh/tests/set-e/or1.0
new file mode 100644 (file)
index 0000000..c2dcbe9
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+true || false
diff --git a/sh/tests/set-e/or2.0 b/sh/tests/set-e/or2.0
new file mode 100644 (file)
index 0000000..934e2a6
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+false || true
diff --git a/sh/tests/set-e/or3.1 b/sh/tests/set-e/or3.1
new file mode 100644 (file)
index 0000000..7a617a1
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+false || false
+exit 0
diff --git a/sh/tests/set-e/pipe1.1 b/sh/tests/set-e/pipe1.1
new file mode 100644 (file)
index 0000000..c0bad0f
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+true | false
+exit 0
diff --git a/sh/tests/set-e/pipe2.0 b/sh/tests/set-e/pipe2.0
new file mode 100644 (file)
index 0000000..1e25566
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+false | true
diff --git a/sh/tests/set-e/return1.0 b/sh/tests/set-e/return1.0
new file mode 100644 (file)
index 0000000..961bd41
--- /dev/null
@@ -0,0 +1,11 @@
+# $FreeBSD$
+set -e
+
+# PR 77067, 85267
+f() {
+       return 1
+       true
+}
+
+f || true
+exit 0
diff --git a/sh/tests/set-e/semi1.1 b/sh/tests/set-e/semi1.1
new file mode 100644 (file)
index 0000000..90476a9
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+false; true
+exit 0
diff --git a/sh/tests/set-e/semi2.1 b/sh/tests/set-e/semi2.1
new file mode 100644 (file)
index 0000000..8f510ac
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+true; false
+exit 0
diff --git a/sh/tests/set-e/subshell1.0 b/sh/tests/set-e/subshell1.0
new file mode 100644 (file)
index 0000000..8e5831b
--- /dev/null
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+(true)
diff --git a/sh/tests/set-e/subshell2.1 b/sh/tests/set-e/subshell2.1
new file mode 100644 (file)
index 0000000..619e98a
--- /dev/null
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+(false)
+exit 0
diff --git a/sh/tests/set-e/until1.0 b/sh/tests/set-e/until1.0
new file mode 100644 (file)
index 0000000..71ea7f2
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+set -e
+until false; do
+       break
+done
diff --git a/sh/tests/set-e/until2.0 b/sh/tests/set-e/until2.0
new file mode 100644 (file)
index 0000000..24ea276
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+set -e
+until false; false; do
+       break
+done
diff --git a/sh/tests/set-e/until3.0 b/sh/tests/set-e/until3.0
new file mode 100644 (file)
index 0000000..597db59
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+set -e
+f() {
+       until false; do
+               false
+               break
+       done
+}
+f || true
diff --git a/sh/tests/set-e/while1.0 b/sh/tests/set-e/while1.0
new file mode 100644 (file)
index 0000000..371c94a
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+set -e
+while false; do
+       :
+done
diff --git a/sh/tests/set-e/while2.0 b/sh/tests/set-e/while2.0
new file mode 100644 (file)
index 0000000..124966c
--- /dev/null
@@ -0,0 +1,5 @@
+# $FreeBSD$
+set -e
+while false; false; do
+       :
+done
diff --git a/sh/tests/set-e/while3.0 b/sh/tests/set-e/while3.0
new file mode 100644 (file)
index 0000000..dd3c790
--- /dev/null
@@ -0,0 +1,9 @@
+# $FreeBSD$
+set -e
+f() {
+       while true; do
+               false
+               break
+       done
+}
+f || true
diff --git a/sh/trap.c b/sh/trap.c
new file mode 100644 (file)
index 0000000..b90fee8
--- /dev/null
+++ b/sh/trap.c
@@ -0,0 +1,554 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)trap.c     8.5 (Berkeley) 6/5/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "shell.h"
+#include "main.h"
+#include "nodes.h"     /* for other headers */
+#include "eval.h"
+#include "jobs.h"
+#include "show.h"
+#include "options.h"
+#include "syntax.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "trap.h"
+#include "mystring.h"
+#include "builtins.h"
+#include "myhistedit.h"
+
+#ifdef __APPLE__
+#define sys_nsig (NSIG)
+#endif /* __APPLE__ */
+
+/*
+ * Sigmode records the current value of the signal handlers for the various
+ * modes.  A value of zero means that the current handler is not known.
+ * S_HARD_IGN indicates that the signal was ignored on entry to the shell,
+ */
+
+#define S_DFL 1                        /* default signal handling (SIG_DFL) */
+#define S_CATCH 2              /* signal is caught */
+#define S_IGN 3                        /* signal is ignored (SIG_IGN) */
+#define S_HARD_IGN 4           /* signal is ignored permanently */
+#define S_RESET 5              /* temporary - to reset a hard ignored sig */
+
+
+static char sigmode[NSIG];     /* current value of signal */
+volatile sig_atomic_t pendingsig;      /* indicates some signal received */
+volatile sig_atomic_t pendingsig_waitcmd;      /* indicates wait builtin should be interrupted */
+static int in_dotrap;                  /* do we execute in a trap handler? */
+static char *volatile trap[NSIG];      /* trap handler commands */
+static volatile sig_atomic_t gotsig[NSIG];
+                               /* indicates specified signal received */
+static int ignore_sigchld;     /* Used while handling SIGCHLD traps. */
+static int last_trapsig;
+
+static int exiting;            /* exitshell() has been called */
+static int exiting_exitstatus; /* value passed to exitshell() */
+
+static int getsigaction(int, sig_t *);
+
+
+/*
+ * Map a string to a signal number.
+ *
+ * Note: the signal number may exceed NSIG.
+ */
+static int
+sigstring_to_signum(char *sig)
+{
+
+       if (is_number(sig)) {
+               int signo;
+
+               signo = atoi(sig);
+               return ((signo >= 0 && signo < NSIG) ? signo : (-1));
+       } else if (strcasecmp(sig, "EXIT") == 0) {
+               return (0);
+       } else {
+               int n;
+
+               if (strncasecmp(sig, "SIG", 3) == 0)
+                       sig += 3;
+               for (n = 1; n < sys_nsig; n++)
+                       if (sys_signame[n] &&
+                           strcasecmp(sys_signame[n], sig) == 0)
+                               return (n);
+       }
+       return (-1);
+}
+
+
+/*
+ * Print a list of valid signal names.
+ */
+static void
+printsignals(void)
+{
+       int n, outlen;
+
+       outlen = 0;
+       for (n = 1; n < sys_nsig; n++) {
+               if (sys_signame[n]) {
+                       out1fmt("%s", sys_signame[n]);
+                       outlen += strlen(sys_signame[n]);
+               } else {
+                       out1fmt("%d", n);
+                       outlen += 3;    /* good enough */
+               }
+               ++outlen;
+               if (outlen > 71 || n == sys_nsig - 1) {
+                       out1str("\n");
+                       outlen = 0;
+               } else {
+                       out1c(' ');
+               }
+       }
+}
+
+
+/*
+ * The trap builtin.
+ */
+int
+trapcmd(int argc __unused, char **argv)
+{
+       char *action;
+       int signo;
+       int errors = 0;
+       int i;
+
+       while ((i = nextopt("l")) != '\0') {
+               switch (i) {
+               case 'l':
+                       printsignals();
+                       return (0);
+               }
+       }
+       argv = argptr;
+
+       if (*argv == NULL) {
+               for (signo = 0 ; signo < sys_nsig ; signo++) {
+                       if (signo < NSIG && trap[signo] != NULL) {
+                               out1str("trap -- ");
+                               out1qstr(trap[signo]);
+                               if (signo == 0) {
+                                       out1str(" EXIT\n");
+                               } else if (sys_signame[signo]) {
+                                       out1fmt(" %s\n", sys_signame[signo]);
+                               } else {
+                                       out1fmt(" %d\n", signo);
+                               }
+                       }
+               }
+               return 0;
+       }
+       action = NULL;
+       if (*argv && !is_number(*argv)) {
+               if (strcmp(*argv, "-") == 0)
+                       argv++;
+               else {
+                       action = *argv;
+                       argv++;
+               }
+       }
+       for (; *argv; argv++) {
+               if ((signo = sigstring_to_signum(*argv)) == -1) {
+                       warning("bad signal %s", *argv);
+                       errors = 1;
+                       continue;
+               }
+               INTOFF;
+               if (action)
+                       action = savestr(action);
+               if (trap[signo])
+                       ckfree(trap[signo]);
+               trap[signo] = action;
+               if (signo != 0)
+                       setsignal(signo);
+               INTON;
+       }
+       return errors;
+}
+
+
+/*
+ * Clear traps on a fork.
+ */
+void
+clear_traps(void)
+{
+       char *volatile *tp;
+
+       for (tp = trap ; tp <= &trap[NSIG - 1] ; tp++) {
+               if (*tp && **tp) {      /* trap not NULL or SIG_IGN */
+                       INTOFF;
+                       ckfree(*tp);
+                       *tp = NULL;
+                       if (tp != &trap[0])
+                               setsignal(tp - trap);
+                       INTON;
+               }
+       }
+}
+
+
+/*
+ * Check if we have any traps enabled.
+ */
+int
+have_traps(void)
+{
+       char *volatile *tp;
+
+       for (tp = trap ; tp <= &trap[NSIG - 1] ; tp++) {
+               if (*tp && **tp)        /* trap not NULL or SIG_IGN */
+                       return 1;
+       }
+       return 0;
+}
+
+/*
+ * Set the signal handler for the specified signal.  The routine figures
+ * out what it should be set to.
+ */
+void
+setsignal(int signo)
+{
+       int action;
+       sig_t sigact = SIG_DFL;
+       struct sigaction sa;
+       char *t;
+
+       if ((t = trap[signo]) == NULL)
+               action = S_DFL;
+       else if (*t != '\0')
+               action = S_CATCH;
+       else
+               action = S_IGN;
+       if (action == S_DFL) {
+               switch (signo) {
+               case SIGINT:
+                       action = S_CATCH;
+                       break;
+               case SIGQUIT:
+#ifdef DEBUG
+                       {
+                       extern int debug;
+
+                       if (debug)
+                               break;
+                       }
+#endif
+                       action = S_CATCH;
+                       break;
+               case SIGTERM:
+                       if (rootshell && iflag)
+                               action = S_IGN;
+                       break;
+#if JOBS
+               case SIGTSTP:
+               case SIGTTOU:
+                       if (rootshell && mflag)
+                               action = S_IGN;
+                       break;
+#endif
+               }
+       }
+
+       t = &sigmode[signo];
+       if (*t == 0) {
+               /*
+                * current setting unknown
+                */
+               if (!getsigaction(signo, &sigact)) {
+                       /*
+                        * Pretend it worked; maybe we should give a warning
+                        * here, but other shells don't. We don't alter
+                        * sigmode, so that we retry every time.
+                        */
+                       return;
+               }
+               if (sigact == SIG_IGN) {
+                       if (mflag && (signo == SIGTSTP ||
+                            signo == SIGTTIN || signo == SIGTTOU)) {
+                               *t = S_IGN;     /* don't hard ignore these */
+                       } else
+                               *t = S_HARD_IGN;
+               } else {
+                       *t = S_RESET;   /* force to be set */
+               }
+       }
+       if (*t == S_HARD_IGN || *t == action)
+               return;
+       switch (action) {
+               case S_DFL:     sigact = SIG_DFL;       break;
+               case S_CATCH:   sigact = onsig;         break;
+               case S_IGN:     sigact = SIG_IGN;       break;
+       }
+       *t = action;
+       sa.sa_handler = sigact;
+       sa.sa_flags = 0;
+       sigemptyset(&sa.sa_mask);
+       sigaction(signo, &sa, NULL);
+}
+
+
+/*
+ * Return the current setting for sig w/o changing it.
+ */
+static int
+getsigaction(int signo, sig_t *sigact)
+{
+       struct sigaction sa;
+
+       if (sigaction(signo, (struct sigaction *)0, &sa) == -1)
+               return 0;
+       *sigact = (sig_t) sa.sa_handler;
+       return 1;
+}
+
+
+/*
+ * Ignore a signal.
+ */
+void
+ignoresig(int signo)
+{
+
+       if (sigmode[signo] == 0)
+               setsignal(signo);
+       if (sigmode[signo] != S_IGN && sigmode[signo] != S_HARD_IGN) {
+               signal(signo, SIG_IGN);
+               sigmode[signo] = S_IGN;
+       }
+}
+
+
+int
+issigchldtrapped(void)
+{
+
+       return (trap[SIGCHLD] != NULL && *trap[SIGCHLD] != '\0');
+}
+
+
+/*
+ * Signal handler.
+ */
+void
+onsig(int signo)
+{
+
+       if (signo == SIGINT && trap[SIGINT] == NULL) {
+               /*
+                * The !in_dotrap here is safe.  The only way we can arrive
+                * here with in_dotrap set is that a trap handler set SIGINT to
+                * SIG_DFL and killed itself.
+                */
+               if (suppressint && !in_dotrap)
+                       SET_PENDING_INT;
+               else
+                       onint();
+               return;
+       }
+
+       /* If we are currently in a wait builtin, prepare to break it */
+       if (signo == SIGINT || signo == SIGQUIT)
+               pendingsig_waitcmd = signo;
+
+       if (trap[signo] != NULL && trap[signo][0] != '\0' &&
+           (signo != SIGCHLD || !ignore_sigchld)) {
+               gotsig[signo] = 1;
+               pendingsig = signo;
+               pendingsig_waitcmd = signo;
+       }
+}
+
+
+/*
+ * Called to execute a trap.  Perhaps we should avoid entering new trap
+ * handlers while we are executing a trap handler.
+ */
+void
+dotrap(void)
+{
+       int i;
+       int savestatus, prev_evalskip, prev_skipcount;
+
+       in_dotrap++;
+       for (;;) {
+               pendingsig = 0;
+               pendingsig_waitcmd = 0;
+               for (i = 1; i < NSIG; i++) {
+                       if (gotsig[i]) {
+                               gotsig[i] = 0;
+                               if (trap[i]) {
+                                       /*
+                                        * Ignore SIGCHLD to avoid infinite
+                                        * recursion if the trap action does
+                                        * a fork.
+                                        */
+                                       if (i == SIGCHLD)
+                                               ignore_sigchld++;
+
+                                       /*
+                                        * Backup current evalskip
+                                        * state and reset it before
+                                        * executing a trap, so that the
+                                        * trap is not disturbed by an
+                                        * ongoing break/continue/return
+                                        * statement.
+                                        */
+                                       prev_evalskip  = evalskip;
+                                       prev_skipcount = skipcount;
+                                       evalskip = 0;
+
+                                       last_trapsig = i;
+                                       savestatus = exitstatus;
+                                       evalstring(trap[i], 0);
+
+                                       /*
+                                        * If such a command was not
+                                        * already in progress, allow a
+                                        * break/continue/return in the
+                                        * trap action to have an effect
+                                        * outside of it.
+                                        */
+                                       if (evalskip == 0 ||
+                                           prev_evalskip != 0) {
+                                               evalskip  = prev_evalskip;
+                                               skipcount = prev_skipcount;
+                                               exitstatus = savestatus;
+                                       }
+
+                                       if (i == SIGCHLD)
+                                               ignore_sigchld--;
+                               }
+                               break;
+                       }
+               }
+               if (i >= NSIG)
+                       break;
+       }
+       in_dotrap--;
+}
+
+
+/*
+ * Controls whether the shell is interactive or not.
+ */
+void
+setinteractive(int on)
+{
+       static int is_interactive = -1;
+
+       if (on == is_interactive)
+               return;
+       setsignal(SIGINT);
+       setsignal(SIGQUIT);
+       setsignal(SIGTERM);
+       is_interactive = on;
+}
+
+
+/*
+ * Called to exit the shell.
+ */
+void
+exitshell(int status)
+{
+       TRACE(("exitshell(%d) pid=%d\n", status, getpid()));
+       exiting = 1;
+       exiting_exitstatus = status;
+       exitshell_savedstatus();
+}
+
+void
+exitshell_savedstatus(void)
+{
+       struct jmploc loc1, loc2;
+       char *p;
+       int sig = 0;
+       sigset_t sigs;
+
+       if (!exiting) {
+               if (in_dotrap && last_trapsig) {
+                       sig = last_trapsig;
+                       exiting_exitstatus = sig + 128;
+               } else
+                       exiting_exitstatus = oexitstatus;
+       }
+       exitstatus = oexitstatus = exiting_exitstatus;
+       if (!setjmp(loc1.loc)) {
+               handler = &loc1;
+               if ((p = trap[0]) != NULL && *p != '\0') {
+                       /*
+                        * Reset evalskip, or the trap on EXIT could be
+                        * interrupted if the last command was a "return".
+                        */
+                       evalskip = 0;
+                       trap[0] = NULL;
+                       evalstring(p, 0);
+               }
+       }
+       if (!setjmp(loc2.loc)) {
+               handler = &loc2;                /* probably unnecessary */
+               flushall();
+#if JOBS
+               setjobctl(0);
+#endif
+       }
+       if (sig != 0 && sig != SIGSTOP && sig != SIGTSTP && sig != SIGTTIN &&
+           sig != SIGTTOU) {
+               signal(sig, SIG_DFL);
+               sigemptyset(&sigs);
+               sigaddset(&sigs, sig);
+               sigprocmask(SIG_UNBLOCK, &sigs, NULL);
+               kill(getpid(), sig);
+               /* If the default action is to ignore, fall back to _exit(). */
+       }
+       _exit(exiting_exitstatus);
+}
diff --git a/sh/trap.h b/sh/trap.h
new file mode 100644 (file)
index 0000000..a272839
--- /dev/null
+++ b/sh/trap.h
@@ -0,0 +1,48 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)trap.h      8.3 (Berkeley) 6/5/95
+ * $FreeBSD$
+ */
+
+extern volatile sig_atomic_t pendingsig;
+extern volatile sig_atomic_t pendingsig_waitcmd;
+
+void clear_traps(void);
+int have_traps(void);
+void setsignal(int);
+void ignoresig(int);
+int issigchldtrapped(void);
+void onsig(int);
+void dotrap(void);
+void setinteractive(int);
+void exitshell(int) __dead2;
+void exitshell_savedstatus(void) __dead2;
diff --git a/sh/var.c b/sh/var.c
new file mode 100644 (file)
index 0000000..f60dea1
--- /dev/null
+++ b/sh/var.c
@@ -0,0 +1,956 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef lint
+#if 0
+static char sccsid[] = "@(#)var.c      8.3 (Berkeley) 5/4/95";
+#endif
+#endif /* not lint */
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <paths.h>
+
+/*
+ * Shell variables.
+ */
+
+#include <locale.h>
+#include <langinfo.h>
+
+#include "shell.h"
+#include "output.h"
+#include "expand.h"
+#include "nodes.h"     /* for other headers */
+#include "eval.h"      /* defines cmdenviron */
+#include "exec.h"
+#include "syntax.h"
+#include "options.h"
+#include "mail.h"
+#include "var.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "parser.h"
+#include "builtins.h"
+#ifndef NO_HISTORY
+#include "myhistedit.h"
+#endif
+
+
+#define VTABSIZE 39
+
+
+struct varinit {
+       struct var *var;
+       int flags;
+       const char *text;
+       void (*func)(const char *);
+};
+
+
+#ifndef NO_HISTORY
+struct var vhistsize;
+struct var vterm;
+#endif
+struct var vifs;
+struct var vmail;
+struct var vmpath;
+struct var vpath;
+struct var vps1;
+struct var vps2;
+struct var vps4;
+static struct var voptind;
+struct var vdisvfork;
+
+struct localvar *localvars;
+int forcelocal;
+
+static const struct varinit varinit[] = {
+#ifndef NO_HISTORY
+       { &vhistsize,   VUNSET,                         "HISTSIZE=",
+         sethistsize },
+#endif
+       { &vifs,        0,                              "IFS= \t\n",
+         NULL },
+       { &vmail,       VUNSET,                         "MAIL=",
+         NULL },
+       { &vmpath,      VUNSET,                         "MAILPATH=",
+         NULL },
+       { &vpath,       0,                              "PATH=" _PATH_DEFPATH,
+         changepath },
+       /*
+        * vps1 depends on uid
+        */
+       { &vps2,        0,                              "PS2=> ",
+         NULL },
+       { &vps4,        0,                              "PS4=+ ",
+         NULL },
+#ifndef NO_HISTORY
+       { &vterm,       VUNSET,                         "TERM=",
+         setterm },
+#endif
+       { &voptind,     0,                              "OPTIND=1",
+         getoptsreset },
+       { &vdisvfork,   VUNSET,                         "SH_DISABLE_VFORK=",
+         NULL },
+       { NULL, 0,                              NULL,
+         NULL }
+};
+
+static struct var *vartab[VTABSIZE];
+
+static const char *const locale_names[7] = {
+       "LC_COLLATE", "LC_CTYPE", "LC_MONETARY",
+       "LC_NUMERIC", "LC_TIME", "LC_MESSAGES", NULL
+};
+static const int locale_categories[7] = {
+       LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME, LC_MESSAGES, 0
+};
+
+static int varequal(const char *, const char *);
+static struct var *find_var(const char *, struct var ***, int *);
+static int localevar(const char *);
+static void setvareq_const(const char *s, int flags);
+
+extern char **environ;
+
+/*
+ * This routine initializes the builtin variables and imports the environment.
+ * It is called when the shell is initialized.
+ */
+
+void
+initvar(void)
+{
+       char ppid[20];
+       const struct varinit *ip;
+       struct var *vp;
+       struct var **vpp;
+       char **envp;
+
+       for (ip = varinit ; (vp = ip->var) != NULL ; ip++) {
+               if (find_var(ip->text, &vpp, &vp->name_len) != NULL)
+                       continue;
+               vp->next = *vpp;
+               *vpp = vp;
+               vp->text = __DECONST(char *, ip->text);
+               vp->flags = ip->flags | VSTRFIXED | VTEXTFIXED;
+               vp->func = ip->func;
+       }
+       /*
+        * PS1 depends on uid
+        */
+       if (find_var("PS1", &vpp, &vps1.name_len) == NULL) {
+               vps1.next = *vpp;
+               *vpp = &vps1;
+               vps1.text = __DECONST(char *, geteuid() ? "PS1=$ " : "PS1=# ");
+               vps1.flags = VSTRFIXED|VTEXTFIXED;
+       }
+       fmtstr(ppid, sizeof(ppid), "%d", (int)getppid());
+       setvarsafe("PPID", ppid, 0);
+       for (envp = environ ; *envp ; envp++) {
+               if (strchr(*envp, '=')) {
+                       setvareq(*envp, VEXPORT|VTEXTFIXED);
+               }
+       }
+       setvareq_const("OPTIND=1", 0);
+}
+
+/*
+ * Safe version of setvar, returns 1 on success 0 on failure.
+ */
+
+int
+setvarsafe(const char *name, const char *val, int flags)
+{
+       struct jmploc jmploc;
+       struct jmploc *const savehandler = handler;
+       int err = 0;
+       int inton;
+
+       inton = is_int_on();
+       if (setjmp(jmploc.loc))
+               err = 1;
+       else {
+               handler = &jmploc;
+               setvar(name, val, flags);
+       }
+       handler = savehandler;
+       SETINTON(inton);
+       return err;
+}
+
+/*
+ * Set the value of a variable.  The flags argument is stored with the
+ * flags of the variable.  If val is NULL, the variable is unset.
+ */
+
+void
+setvar(const char *name, const char *val, int flags)
+{
+       const char *p;
+       size_t len;
+       size_t namelen;
+       size_t vallen;
+       char *nameeq;
+       int isbad;
+
+       isbad = 0;
+       p = name;
+       if (! is_name(*p))
+               isbad = 1;
+       p++;
+       for (;;) {
+               if (! is_in_name(*p)) {
+                       if (*p == '\0' || *p == '=')
+                               break;
+                       isbad = 1;
+               }
+               p++;
+       }
+       namelen = p - name;
+       if (isbad)
+               error("%.*s: bad variable name", (int)namelen, name);
+       len = namelen + 2;              /* 2 is space for '=' and '\0' */
+       if (val == NULL) {
+               flags |= VUNSET;
+               vallen = 0;
+       } else {
+               vallen = strlen(val);
+               len += vallen;
+       }
+       INTOFF;
+       nameeq = ckmalloc(len);
+       memcpy(nameeq, name, namelen);
+       nameeq[namelen] = '=';
+       if (val)
+               memcpy(nameeq + namelen + 1, val, vallen + 1);
+       else
+               nameeq[namelen + 1] = '\0';
+       setvareq(nameeq, flags);
+       INTON;
+}
+
+static int
+localevar(const char *s)
+{
+       const char *const *ss;
+
+       if (*s != 'L')
+               return 0;
+       if (varequal(s + 1, "ANG"))
+               return 1;
+       if (strncmp(s + 1, "C_", 2) != 0)
+               return 0;
+       if (varequal(s + 3, "ALL"))
+               return 1;
+       for (ss = locale_names; *ss ; ss++)
+               if (varequal(s + 3, *ss + 3))
+                       return 1;
+       return 0;
+}
+
+
+/*
+ * Sets/unsets an environment variable from a pointer that may actually be a
+ * pointer into environ where the string should not be manipulated.
+ */
+static void
+change_env(const char *s, int set)
+{
+       char *eqp;
+       char *ss;
+
+       INTOFF;
+       ss = savestr(s);
+       if ((eqp = strchr(ss, '=')) != NULL)
+               *eqp = '\0';
+       if (set && eqp != NULL)
+               (void) setenv(ss, eqp + 1, 1);
+       else
+               (void) unsetenv(ss);
+       ckfree(ss);
+       INTON;
+
+       return;
+}
+
+
+/*
+ * Same as setvar except that the variable and value are passed in
+ * the first argument as name=value.  Since the first argument will
+ * be actually stored in the table, it should not be a string that
+ * will go away.
+ */
+
+void
+setvareq(char *s, int flags)
+{
+       struct var *vp, **vpp;
+       int nlen;
+
+       if (aflag)
+               flags |= VEXPORT;
+       if (forcelocal && !(flags & (VNOSET | VNOLOCAL)))
+               mklocal(s);
+       vp = find_var(s, &vpp, &nlen);
+       if (vp != NULL) {
+               if (vp->flags & VREADONLY) {
+                       if ((flags & (VTEXTFIXED|VSTACK)) == 0)
+                               ckfree(s);
+                       error("%.*s: is read only", vp->name_len, s);
+               }
+               if (flags & VNOSET) {
+                       if ((flags & (VTEXTFIXED|VSTACK)) == 0)
+                               ckfree(s);
+                       return;
+               }
+               INTOFF;
+
+               if (vp->func && (flags & VNOFUNC) == 0)
+                       (*vp->func)(s + vp->name_len + 1);
+
+               if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
+                       ckfree(vp->text);
+
+               vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET);
+               vp->flags |= flags;
+               vp->text = s;
+
+               /*
+                * We could roll this to a function, to handle it as
+                * a regular variable function callback, but why bother?
+                *
+                * Note: this assumes iflag is not set to 1 initially.
+                * As part of initvar(), this is called before arguments
+                * are looked at.
+                */
+               if ((vp == &vmpath || (vp == &vmail && ! mpathset())) &&
+                   iflag == 1)
+                       chkmail(1);
+               if ((vp->flags & VEXPORT) && localevar(s)) {
+                       change_env(s, 1);
+                       (void) setlocale(LC_ALL, "");
+                       updatecharset();
+               }
+               INTON;
+               return;
+       }
+       /* not found */
+       if (flags & VNOSET) {
+               if ((flags & (VTEXTFIXED|VSTACK)) == 0)
+                       ckfree(s);
+               return;
+       }
+       INTOFF;
+       vp = ckmalloc(sizeof (*vp));
+       vp->flags = flags;
+       vp->text = s;
+       vp->name_len = nlen;
+       vp->next = *vpp;
+       vp->func = NULL;
+       *vpp = vp;
+       if ((vp->flags & VEXPORT) && localevar(s)) {
+               change_env(s, 1);
+               (void) setlocale(LC_ALL, "");
+               updatecharset();
+       }
+       INTON;
+}
+
+
+static void
+setvareq_const(const char *s, int flags)
+{
+       setvareq(__DECONST(char *, s), flags | VTEXTFIXED);
+}
+
+
+/*
+ * Process a linked list of variable assignments.
+ */
+
+void
+listsetvar(struct strlist *list, int flags)
+{
+       struct strlist *lp;
+
+       INTOFF;
+       for (lp = list ; lp ; lp = lp->next) {
+               setvareq(savestr(lp->text), flags);
+       }
+       INTON;
+}
+
+
+
+/*
+ * Find the value of a variable.  Returns NULL if not set.
+ */
+
+char *
+lookupvar(const char *name)
+{
+       struct var *v;
+
+       v = find_var(name, NULL, NULL);
+       if (v == NULL || v->flags & VUNSET)
+               return NULL;
+       return v->text + v->name_len + 1;
+}
+
+
+
+/*
+ * Search the environment of a builtin command.  If the second argument
+ * is nonzero, return the value of a variable even if it hasn't been
+ * exported.
+ */
+
+char *
+bltinlookup(const char *name, int doall)
+{
+       struct strlist *sp;
+       struct var *v;
+       char *result;
+
+       result = NULL;
+       for (sp = cmdenviron ; sp ; sp = sp->next) {
+               if (varequal(sp->text, name))
+                       result = strchr(sp->text, '=') + 1;
+       }
+       if (result != NULL)
+               return result;
+
+       v = find_var(name, NULL, NULL);
+       if (v == NULL || v->flags & VUNSET ||
+           (!doall && (v->flags & VEXPORT) == 0))
+               return NULL;
+       return v->text + v->name_len + 1;
+}
+
+
+/*
+ * Set up locale for a builtin (LANG/LC_* assignments).
+ */
+void
+bltinsetlocale(void)
+{
+       struct strlist *lp;
+       int act = 0;
+       char *loc, *locdef;
+       int i;
+
+       for (lp = cmdenviron ; lp ; lp = lp->next) {
+               if (localevar(lp->text)) {
+                       act = 1;
+                       break;
+               }
+       }
+       if (!act)
+               return;
+       loc = bltinlookup("LC_ALL", 0);
+       INTOFF;
+       if (loc != NULL) {
+               setlocale(LC_ALL, loc);
+               INTON;
+               updatecharset();
+               return;
+       }
+       locdef = bltinlookup("LANG", 0);
+       for (i = 0; locale_names[i] != NULL; i++) {
+               loc = bltinlookup(locale_names[i], 0);
+               if (loc == NULL)
+                       loc = locdef;
+               if (loc != NULL)
+                       setlocale(locale_categories[i], loc);
+       }
+       INTON;
+       updatecharset();
+}
+
+/*
+ * Undo the effect of bltinlocaleset().
+ */
+void
+bltinunsetlocale(void)
+{
+       struct strlist *lp;
+
+       INTOFF;
+       for (lp = cmdenviron ; lp ; lp = lp->next) {
+               if (localevar(lp->text)) {
+                       setlocale(LC_ALL, "");
+                       updatecharset();
+                       return;
+               }
+       }
+       INTON;
+}
+
+/*
+ * Update the localeisutf8 flag.
+ */
+void
+updatecharset(void)
+{
+       char *charset;
+
+       charset = nl_langinfo(CODESET);
+       localeisutf8 = !strcmp(charset, "UTF-8");
+}
+
+void
+initcharset(void)
+{
+       updatecharset();
+       initial_localeisutf8 = localeisutf8;
+}
+
+/*
+ * Generate a list of exported variables.  This routine is used to construct
+ * the third argument to execve when executing a program.
+ */
+
+char **
+environment(void)
+{
+       int nenv;
+       struct var **vpp;
+       struct var *vp;
+       char **env, **ep;
+
+       nenv = 0;
+       for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+               for (vp = *vpp ; vp ; vp = vp->next)
+                       if (vp->flags & VEXPORT)
+                               nenv++;
+       }
+       ep = env = stalloc((nenv + 1) * sizeof *env);
+       for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+               for (vp = *vpp ; vp ; vp = vp->next)
+                       if (vp->flags & VEXPORT)
+                               *ep++ = vp->text;
+       }
+       *ep = NULL;
+       return env;
+}
+
+
+static int
+var_compare(const void *a, const void *b)
+{
+       const char *const *sa, *const *sb;
+
+       sa = a;
+       sb = b;
+       /*
+        * This compares two var=value strings which creates a different
+        * order from what you would probably expect.  POSIX is somewhat
+        * ambiguous on what should be sorted exactly.
+        */
+       return strcoll(*sa, *sb);
+}
+
+
+/*
+ * Command to list all variables which are set.  This is invoked from the
+ * set command when it is called without any options or operands.
+ */
+
+int
+showvarscmd(int argc __unused, char **argv __unused)
+{
+       struct var **vpp;
+       struct var *vp;
+       const char *s;
+       const char **vars;
+       int i, n;
+
+       /*
+        * POSIX requires us to sort the variables.
+        */
+       n = 0;
+       for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) {
+               for (vp = *vpp; vp; vp = vp->next) {
+                       if (!(vp->flags & VUNSET))
+                               n++;
+               }
+       }
+
+       INTOFF;
+       vars = ckmalloc(n * sizeof(*vars));
+       i = 0;
+       for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) {
+               for (vp = *vpp; vp; vp = vp->next) {
+                       if (!(vp->flags & VUNSET))
+                               vars[i++] = vp->text;
+               }
+       }
+
+       qsort(vars, n, sizeof(*vars), var_compare);
+       for (i = 0; i < n; i++) {
+               /*
+                * Skip improper variable names so the output remains usable as
+                * shell input.
+                */
+               if (!isassignment(vars[i]))
+                       continue;
+               s = strchr(vars[i], '=');
+               s++;
+               outbin(vars[i], s - vars[i], out1);
+               out1qstr(s);
+               out1c('\n');
+       }
+       ckfree(vars);
+       INTON;
+
+       return 0;
+}
+
+
+
+/*
+ * The export and readonly commands.
+ */
+
+int
+exportcmd(int argc __unused, char **argv)
+{
+       struct var **vpp;
+       struct var *vp;
+       char **ap;
+       char *name;
+       char *p;
+       char *cmdname;
+       int ch, values;
+       int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT;
+
+       cmdname = argv[0];
+       values = 0;
+       while ((ch = nextopt("p")) != '\0') {
+               switch (ch) {
+               case 'p':
+                       values = 1;
+                       break;
+               }
+       }
+
+       if (values && *argptr != NULL)
+               error("-p requires no arguments");
+       if (*argptr != NULL) {
+               for (ap = argptr; (name = *ap) != NULL; ap++) {
+                       if ((p = strchr(name, '=')) != NULL) {
+                               p++;
+                       } else {
+                               vp = find_var(name, NULL, NULL);
+                               if (vp != NULL) {
+                                       vp->flags |= flag;
+                                       if ((vp->flags & VEXPORT) && localevar(vp->text)) {
+                                               change_env(vp->text, 1);
+                                               (void) setlocale(LC_ALL, "");
+                                               updatecharset();
+                                       }
+                                       continue;
+                               }
+                       }
+                       setvar(name, p, flag);
+               }
+       } else {
+               for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) {
+                       for (vp = *vpp ; vp ; vp = vp->next) {
+                               if (vp->flags & flag) {
+                                       if (values) {
+                                               /*
+                                                * Skip improper variable names
+                                                * so the output remains usable
+                                                * as shell input.
+                                                */
+                                               if (!isassignment(vp->text))
+                                                       continue;
+                                               out1str(cmdname);
+                                               out1c(' ');
+                                       }
+                                       if (values && !(vp->flags & VUNSET)) {
+                                               outbin(vp->text,
+                                                   vp->name_len + 1, out1);
+                                               out1qstr(vp->text +
+                                                   vp->name_len + 1);
+                                       } else
+                                               outbin(vp->text, vp->name_len,
+                                                   out1);
+                                       out1c('\n');
+                               }
+                       }
+               }
+       }
+       return 0;
+}
+
+
+/*
+ * The "local" command.
+ */
+
+int
+localcmd(int argc __unused, char **argv __unused)
+{
+       char *name;
+
+       nextopt("");
+       if (! in_function())
+               error("Not in a function");
+       while ((name = *argptr++) != NULL) {
+               mklocal(name);
+       }
+       return 0;
+}
+
+
+/*
+ * Make a variable a local variable.  When a variable is made local, it's
+ * value and flags are saved in a localvar structure.  The saved values
+ * will be restored when the shell function returns.  We handle the name
+ * "-" as a special case.
+ */
+
+void
+mklocal(char *name)
+{
+       struct localvar *lvp;
+       struct var **vpp;
+       struct var *vp;
+
+       INTOFF;
+       lvp = ckmalloc(sizeof (struct localvar));
+       if (name[0] == '-' && name[1] == '\0') {
+               lvp->text = ckmalloc(sizeof optlist);
+               memcpy(lvp->text, optlist, sizeof optlist);
+               vp = NULL;
+       } else {
+               vp = find_var(name, &vpp, NULL);
+               if (vp == NULL) {
+                       if (strchr(name, '='))
+                               setvareq(savestr(name), VSTRFIXED | VNOLOCAL);
+                       else
+                               setvar(name, NULL, VSTRFIXED | VNOLOCAL);
+                       vp = *vpp;      /* the new variable */
+                       lvp->text = NULL;
+                       lvp->flags = VUNSET;
+               } else {
+                       lvp->text = vp->text;
+                       lvp->flags = vp->flags;
+                       vp->flags |= VSTRFIXED|VTEXTFIXED;
+                       if (name[vp->name_len] == '=')
+                               setvareq(savestr(name), VNOLOCAL);
+               }
+       }
+       lvp->vp = vp;
+       lvp->next = localvars;
+       localvars = lvp;
+       INTON;
+}
+
+
+/*
+ * Called after a function returns.
+ */
+
+void
+poplocalvars(void)
+{
+       struct localvar *lvp;
+       struct var *vp;
+
+       INTOFF;
+       while ((lvp = localvars) != NULL) {
+               localvars = lvp->next;
+               vp = lvp->vp;
+               if (vp == NULL) {       /* $- saved */
+                       memcpy(optlist, lvp->text, sizeof optlist);
+                       ckfree(lvp->text);
+                       optschanged();
+               } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
+                       (void)unsetvar(vp->text);
+               } else {
+                       if ((vp->flags & VTEXTFIXED) == 0)
+                               ckfree(vp->text);
+                       vp->flags = lvp->flags;
+                       vp->text = lvp->text;
+               }
+               ckfree(lvp);
+       }
+       INTON;
+}
+
+
+int
+setvarcmd(int argc, char **argv)
+{
+       if (argc <= 2)
+               return unsetcmd(argc, argv);
+       else if (argc == 3)
+               setvar(argv[1], argv[2], 0);
+       else
+               error("too many arguments");
+       return 0;
+}
+
+
+/*
+ * The unset builtin command.
+ */
+
+int
+unsetcmd(int argc __unused, char **argv __unused)
+{
+       char **ap;
+       int i;
+       int flg_func = 0;
+       int flg_var = 0;
+       int ret = 0;
+
+       while ((i = nextopt("vf")) != '\0') {
+               if (i == 'f')
+                       flg_func = 1;
+               else
+                       flg_var = 1;
+       }
+       if (flg_func == 0 && flg_var == 0)
+               flg_var = 1;
+
+       INTOFF;
+       for (ap = argptr; *ap ; ap++) {
+               if (flg_func)
+                       ret |= unsetfunc(*ap);
+               if (flg_var)
+                       ret |= unsetvar(*ap);
+       }
+       INTON;
+       return ret;
+}
+
+
+/*
+ * Unset the specified variable.
+ * Called with interrupts off.
+ */
+
+int
+unsetvar(const char *s)
+{
+       struct var **vpp;
+       struct var *vp;
+
+       vp = find_var(s, &vpp, NULL);
+       if (vp == NULL)
+               return (0);
+       if (vp->flags & VREADONLY)
+               return (1);
+       if (vp->text[vp->name_len + 1] != '\0')
+               setvar(s, "", 0);
+       if ((vp->flags & VEXPORT) && localevar(vp->text)) {
+               change_env(s, 0);
+               setlocale(LC_ALL, "");
+               updatecharset();
+       }
+       vp->flags &= ~VEXPORT;
+       vp->flags |= VUNSET;
+       if ((vp->flags & VSTRFIXED) == 0) {
+               if ((vp->flags & VTEXTFIXED) == 0)
+                       ckfree(vp->text);
+               *vpp = vp->next;
+               ckfree(vp);
+       }
+       return (0);
+}
+
+
+
+/*
+ * Returns true if the two strings specify the same variable.  The first
+ * variable name is terminated by '='; the second may be terminated by
+ * either '=' or '\0'.
+ */
+
+static int
+varequal(const char *p, const char *q)
+{
+       while (*p == *q++) {
+               if (*p++ == '=')
+                       return 1;
+       }
+       if (*p == '=' && *(q - 1) == '\0')
+               return 1;
+       return 0;
+}
+
+/*
+ * Search for a variable.
+ * 'name' may be terminated by '=' or a NUL.
+ * vppp is set to the pointer to vp, or the list head if vp isn't found
+ * lenp is set to the number of characters in 'name'
+ */
+
+static struct var *
+find_var(const char *name, struct var ***vppp, int *lenp)
+{
+       unsigned int hashval;
+       int len;
+       struct var *vp, **vpp;
+       const char *p = name;
+
+       hashval = 0;
+       while (*p && *p != '=')
+               hashval = 2 * hashval + (unsigned char)*p++;
+       len = p - name;
+
+       if (lenp)
+               *lenp = len;
+       vpp = &vartab[hashval % VTABSIZE];
+       if (vppp)
+               *vppp = vpp;
+
+       for (vp = *vpp ; vp ; vpp = &vp->next, vp = *vpp) {
+               if (vp->name_len != len)
+                       continue;
+               if (memcmp(vp->text, name, len) != 0)
+                       continue;
+               if (vppp)
+                       *vppp = vpp;
+               return vp;
+       }
+       return NULL;
+}
diff --git a/sh/var.h b/sh/var.h
new file mode 100644 (file)
index 0000000..6e831ae
--- /dev/null
+++ b/sh/var.h
@@ -0,0 +1,130 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)var.h       8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+/*
+ * Shell variables.
+ */
+
+/* flags */
+#define VEXPORT                0x01    /* variable is exported */
+#define VREADONLY      0x02    /* variable cannot be modified */
+#define VSTRFIXED      0x04    /* variable struct is statically allocated */
+#define VTEXTFIXED     0x08    /* text is statically allocated */
+#define VSTACK         0x10    /* text is allocated on the stack */
+#define VUNSET         0x20    /* the variable is not set */
+#define VNOFUNC                0x40    /* don't call the callback function */
+#define VNOSET         0x80    /* do not set variable - just readonly test */
+#define VNOLOCAL       0x100   /* ignore forcelocal */
+
+
+struct var {
+       struct var *next;               /* next entry in hash list */
+       int flags;                      /* flags are defined above */
+       int name_len;                   /* length of name */
+       char *text;                     /* name=value */
+       void (*func)(const char *);
+                                       /* function to be called when  */
+                                       /* the variable gets set/unset */
+};
+
+
+struct localvar {
+       struct localvar *next;          /* next local variable in list */
+       struct var *vp;                 /* the variable that was made local */
+       int flags;                      /* saved flags */
+       char *text;                     /* saved text */
+};
+
+
+extern struct localvar *localvars;
+extern int forcelocal;
+
+extern struct var vifs;
+extern struct var vmail;
+extern struct var vmpath;
+extern struct var vpath;
+extern struct var vps1;
+extern struct var vps2;
+extern struct var vps4;
+extern struct var vdisvfork;
+#ifndef NO_HISTORY
+extern struct var vhistsize;
+extern struct var vterm;
+#endif
+
+extern int localeisutf8;
+/* The parser uses the locale that was in effect at startup. */
+extern int initial_localeisutf8;
+
+/*
+ * The following macros access the values of the above variables.
+ * They have to skip over the name.  They return the null string
+ * for unset variables.
+ */
+
+#define ifsval()       (vifs.text + 4)
+#define ifsset()       ((vifs.flags & VUNSET) == 0)
+#define mailval()      (vmail.text + 5)
+#define mpathval()     (vmpath.text + 9)
+#define pathval()      (vpath.text + 5)
+#define ps1val()       (vps1.text + 4)
+#define ps2val()       (vps2.text + 4)
+#define ps4val()       (vps4.text + 4)
+#define optindval()    (voptind.text + 7)
+#ifndef NO_HISTORY
+#define histsizeval()  (vhistsize.text + 9)
+#define termval()      (vterm.text + 5)
+#endif
+
+#define mpathset()     ((vmpath.flags & VUNSET) == 0)
+#define disvforkset()  ((vdisvfork.flags & VUNSET) == 0)
+
+void initvar(void);
+void setvar(const char *, const char *, int);
+void setvareq(char *, int);
+struct strlist;
+void listsetvar(struct strlist *, int);
+char *lookupvar(const char *);
+char *bltinlookup(const char *, int);
+void bltinsetlocale(void);
+void bltinunsetlocale(void);
+void updatecharset(void);
+void initcharset(void);
+char **environment(void);
+int showvarscmd(int, char **);
+void mklocal(char *);
+void poplocalvars(void);
+int unsetvar(const char *);
+int setvarsafe(const char *, const char *, int);
index 58fea8597683cef7a518a7aa9a02eca4b6e10c3e..26fdc28bcc66e2d712037cafe657fbe2be72f439 100644 (file)
@@ -47,6 +47,7 @@
                                FCBA167D14A146D000AA698B /* PBXTargetDependency */,
                                FCBA167F14A146D000AA698B /* PBXTargetDependency */,
                                FCBA168114A146D000AA698B /* PBXTargetDependency */,
+                               FD6060BB1B7C03BC004BCA6A /* PBXTargetDependency */,
                                FCBA168314A146D000AA698B /* PBXTargetDependency */,
                                FCBA168514A146D000AA698B /* PBXTargetDependency */,
                                FCBA168714A146D000AA698B /* PBXTargetDependency */,
                                FCE30F8114B619E600CC0294 /* PBXTargetDependency */,
                                FCE30F8314B619E600CC0294 /* PBXTargetDependency */,
                                FCE30F8514B619E600CC0294 /* PBXTargetDependency */,
+                               FD6060B91B7C03B3004BCA6A /* PBXTargetDependency */,
                                FCE30F8714B619E600CC0294 /* PBXTargetDependency */,
                                FCE30F8914B619E600CC0294 /* PBXTargetDependency */,
                                FCE30F8B14B619E600CC0294 /* PBXTargetDependency */,
                FCE30EE014B536F200CC0294 /* locate.code.c in Sources */ = {isa = PBXBuildFile; fileRef = FCBA13A114A141A300AA698B /* locate.code.c */; };
                FCE30EE114B536F800CC0294 /* locate.code.8 in CopyFiles */ = {isa = PBXBuildFile; fileRef = FCBA13B114A141A300AA698B /* locate.code.8 */; };
                FCED3AF614B4FC1800C313C3 /* libpam.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FCED3AF514B4FC1800C313C3 /* libpam.dylib */; };
+               FD6060B51B7C0388004BCA6A /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060B41B7C0388004BCA6A /* main.c */; };
+               FD6060D21B7C0471004BCA6A /* alias.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060BC1B7C0471004BCA6A /* alias.c */; };
+               FD6060D31B7C0471004BCA6A /* arith_yacc.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060BD1B7C0471004BCA6A /* arith_yacc.c */; };
+               FD6060D41B7C0471004BCA6A /* arith_yylex.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060BE1B7C0471004BCA6A /* arith_yylex.c */; };
+               FD6060D51B7C0471004BCA6A /* cd.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060BF1B7C0471004BCA6A /* cd.c */; };
+               FD6060D61B7C0471004BCA6A /* error.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060C01B7C0471004BCA6A /* error.c */; };
+               FD6060D71B7C0471004BCA6A /* eval.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060C11B7C0471004BCA6A /* eval.c */; };
+               FD6060D81B7C0471004BCA6A /* exec.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060C21B7C0471004BCA6A /* exec.c */; };
+               FD6060D91B7C0471004BCA6A /* expand.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060C31B7C0471004BCA6A /* expand.c */; };
+               FD6060DA1B7C0471004BCA6A /* histedit.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060C41B7C0471004BCA6A /* histedit.c */; };
+               FD6060DB1B7C0471004BCA6A /* input.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060C51B7C0471004BCA6A /* input.c */; };
+               FD6060DC1B7C0471004BCA6A /* jobs.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060C61B7C0471004BCA6A /* jobs.c */; };
+               FD6060DD1B7C0471004BCA6A /* mail.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060C71B7C0471004BCA6A /* mail.c */; };
+               FD6060DE1B7C0471004BCA6A /* memalloc.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060C81B7C0471004BCA6A /* memalloc.c */; };
+               FD6060DF1B7C0471004BCA6A /* miscbltin.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060C91B7C0471004BCA6A /* miscbltin.c */; };
+               FD6060E01B7C0471004BCA6A /* mystring.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060CA1B7C0471004BCA6A /* mystring.c */; };
+               FD6060E11B7C0471004BCA6A /* options.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060CB1B7C0471004BCA6A /* options.c */; };
+               FD6060E21B7C0471004BCA6A /* output.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060CC1B7C0471004BCA6A /* output.c */; };
+               FD6060E31B7C0471004BCA6A /* parser.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060CD1B7C0471004BCA6A /* parser.c */; };
+               FD6060E41B7C0471004BCA6A /* redir.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060CE1B7C0471004BCA6A /* redir.c */; };
+               FD6060E51B7C0471004BCA6A /* show.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060CF1B7C0471004BCA6A /* show.c */; };
+               FD6060E61B7C0471004BCA6A /* trap.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060D01B7C0471004BCA6A /* trap.c */; };
+               FD6060E71B7C0471004BCA6A /* var.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060D11B7C0471004BCA6A /* var.c */; };
+               FD6060EB1B7C04E4004BCA6A /* echo.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060EA1B7C04E4004BCA6A /* echo.c */; };
+               FD6060EC1B7C0506004BCA6A /* kill.c in Sources */ = {isa = PBXBuildFile; fileRef = FCBA139114A141A300AA698B /* kill.c */; };
+               FD6060ED1B7C0518004BCA6A /* printf.c in Sources */ = {isa = PBXBuildFile; fileRef = FCBA13D514A141A300AA698B /* printf.c */; };
+               FD6060EE1B7C0521004BCA6A /* test.c in Sources */ = {isa = PBXBuildFile; fileRef = FCBA13FB14A141A300AA698B /* test.c */; };
+               FD6060FD1B7C0CAF004BCA6A /* builtins.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060FA1B7C0CAF004BCA6A /* builtins.c */; };
+               FD6060FE1B7C0CAF004BCA6A /* nodes.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060FB1B7C0CAF004BCA6A /* nodes.c */; };
+               FD6060FF1B7C0CAF004BCA6A /* syntax.c in Sources */ = {isa = PBXBuildFile; fileRef = FD6060FC1B7C0CAF004BCA6A /* syntax.c */; };
                FD88EB03198C5257006B7EFD /* envopts.c in Sources */ = {isa = PBXBuildFile; fileRef = FD88EB01198C5257006B7EFD /* envopts.c */; };
 /* End PBXBuildFile section */
 
                        remoteGlobalIDString = FCBA163614A145CA00AA698B;
                        remoteInfo = yes;
                };
+               FD6060B81B7C03B3004BCA6A /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = FC80BF5714A05A2F00C6F7F5 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = FD6060B11B7C0388004BCA6A;
+                       remoteInfo = sh;
+               };
+               FD6060BA1B7C03BC004BCA6A /* PBXContainerItemProxy */ = {
+                       isa = PBXContainerItemProxy;
+                       containerPortal = FC80BF5714A05A2F00C6F7F5 /* Project object */;
+                       proxyType = 1;
+                       remoteGlobalIDString = FD6060B11B7C0388004BCA6A;
+                       remoteInfo = sh;
+               };
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXCopyFilesBuildPhase section */
                FCBA13F814A141A300AA698B /* [.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = "[.1"; sourceTree = "<group>"; };
                FCBA13FA14A141A300AA698B /* test.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = test.1; sourceTree = "<group>"; };
                FCBA13FB14A141A300AA698B /* test.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = test.c; sourceTree = "<group>"; };
-               FCBA13FC14A141A300AA698B /* TEST.csh */ = {isa = PBXFileReference; lastKnownFileType = text.script.csh; path = TEST.csh; sourceTree = "<group>"; };
                FCBA13FF14A141A300AA698B /* time.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = time.1; sourceTree = "<group>"; };
                FCBA140014A141A300AA698B /* time.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = time.c; sourceTree = "<group>"; };
                FCBA140314A141A300AA698B /* true.1 */ = {isa = PBXFileReference; lastKnownFileType = text.man; path = true.1; sourceTree = "<group>"; };
                FCE30ED214B5368A00CC0294 /* locate.bigram */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = locate.bigram; sourceTree = BUILT_PRODUCTS_DIR; };
                FCE30EDE14B536C900CC0294 /* locate.code */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = locate.code; sourceTree = BUILT_PRODUCTS_DIR; };
                FCED3AF514B4FC1800C313C3 /* libpam.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libpam.dylib; path = /usr/lib/libpam.dylib; sourceTree = "<absolute>"; };
+               FD6060B21B7C0388004BCA6A /* ash */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ash; sourceTree = BUILT_PRODUCTS_DIR; };
+               FD6060B41B7C0388004BCA6A /* main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = main.c; sourceTree = "<group>"; };
+               FD6060BC1B7C0471004BCA6A /* alias.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = alias.c; sourceTree = "<group>"; };
+               FD6060BD1B7C0471004BCA6A /* arith_yacc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = arith_yacc.c; sourceTree = "<group>"; };
+               FD6060BE1B7C0471004BCA6A /* arith_yylex.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = arith_yylex.c; sourceTree = "<group>"; };
+               FD6060BF1B7C0471004BCA6A /* cd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cd.c; sourceTree = "<group>"; };
+               FD6060C01B7C0471004BCA6A /* error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = error.c; sourceTree = "<group>"; };
+               FD6060C11B7C0471004BCA6A /* eval.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = eval.c; sourceTree = "<group>"; };
+               FD6060C21B7C0471004BCA6A /* exec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = exec.c; sourceTree = "<group>"; };
+               FD6060C31B7C0471004BCA6A /* expand.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = expand.c; sourceTree = "<group>"; };
+               FD6060C41B7C0471004BCA6A /* histedit.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = histedit.c; sourceTree = "<group>"; };
+               FD6060C51B7C0471004BCA6A /* input.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = input.c; sourceTree = "<group>"; };
+               FD6060C61B7C0471004BCA6A /* jobs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = jobs.c; sourceTree = "<group>"; };
+               FD6060C71B7C0471004BCA6A /* mail.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mail.c; sourceTree = "<group>"; };
+               FD6060C81B7C0471004BCA6A /* memalloc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = memalloc.c; sourceTree = "<group>"; };
+               FD6060C91B7C0471004BCA6A /* miscbltin.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = miscbltin.c; sourceTree = "<group>"; };
+               FD6060CA1B7C0471004BCA6A /* mystring.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mystring.c; sourceTree = "<group>"; };
+               FD6060CB1B7C0471004BCA6A /* options.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = options.c; sourceTree = "<group>"; };
+               FD6060CC1B7C0471004BCA6A /* output.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = output.c; sourceTree = "<group>"; };
+               FD6060CD1B7C0471004BCA6A /* parser.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = parser.c; sourceTree = "<group>"; };
+               FD6060CE1B7C0471004BCA6A /* redir.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = redir.c; sourceTree = "<group>"; };
+               FD6060CF1B7C0471004BCA6A /* show.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = show.c; sourceTree = "<group>"; };
+               FD6060D01B7C0471004BCA6A /* trap.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = trap.c; sourceTree = "<group>"; };
+               FD6060D11B7C0471004BCA6A /* var.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = var.c; sourceTree = "<group>"; };
+               FD6060EA1B7C04E4004BCA6A /* echo.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = echo.c; sourceTree = "<group>"; };
+               FD6060FA1B7C0CAF004BCA6A /* builtins.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = builtins.c; sourceTree = "<group>"; };
+               FD6060FB1B7C0CAF004BCA6A /* nodes.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = nodes.c; sourceTree = "<group>"; };
+               FD6060FC1B7C0CAF004BCA6A /* syntax.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = syntax.c; sourceTree = "<group>"; };
+               FD6061011B7D2B6D004BCA6A /* builtins.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = builtins.h; sourceTree = "<group>"; };
+               FD6061021B7D2B6D004BCA6A /* nodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = nodes.h; sourceTree = "<group>"; };
+               FD6061031B7D2B6D004BCA6A /* syntax.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = syntax.h; sourceTree = "<group>"; };
+               FD6061041B7D2B6D004BCA6A /* token.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = token.h; sourceTree = "<group>"; };
+               FD6061051B7D2BF8004BCA6A /* bltin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bltin.h; sourceTree = "<group>"; };
+               FD6061061B7D2C8C004BCA6A /* alias.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = alias.h; sourceTree = "<group>"; };
+               FD6061071B7D2C8C004BCA6A /* arith_yacc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = arith_yacc.h; sourceTree = "<group>"; };
+               FD6061081B7D2C8C004BCA6A /* arith.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = arith.h; sourceTree = "<group>"; };
+               FD6061091B7D2C8C004BCA6A /* builtins.def */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = builtins.def; sourceTree = "<group>"; };
+               FD60610A1B7D2C8C004BCA6A /* cd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cd.h; sourceTree = "<group>"; };
+               FD60610B1B7D2C8C004BCA6A /* error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = error.h; sourceTree = "<group>"; };
+               FD60610C1B7D2C8C004BCA6A /* eval.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = eval.h; sourceTree = "<group>"; };
+               FD60610D1B7D2C8C004BCA6A /* exec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = exec.h; sourceTree = "<group>"; };
+               FD60610E1B7D2C8C004BCA6A /* expand.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = expand.h; sourceTree = "<group>"; };
+               FD60610F1B7D2C8C004BCA6A /* input.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = input.h; sourceTree = "<group>"; };
+               FD6061101B7D2C8C004BCA6A /* jobs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jobs.h; sourceTree = "<group>"; };
+               FD6061111B7D2C8C004BCA6A /* mail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mail.h; sourceTree = "<group>"; };
+               FD6061121B7D2C8C004BCA6A /* main.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = main.h; sourceTree = "<group>"; };
+               FD6061131B7D2C8C004BCA6A /* memalloc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = memalloc.h; sourceTree = "<group>"; };
+               FD6061141B7D2C8C004BCA6A /* mkbuiltins */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = mkbuiltins; sourceTree = "<group>"; };
+               FD6061151B7D2C8C004BCA6A /* mknodes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mknodes.c; sourceTree = "<group>"; };
+               FD6061161B7D2C8C004BCA6A /* mksyntax.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mksyntax.c; sourceTree = "<group>"; };
+               FD6061171B7D2C8C004BCA6A /* mktokens */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = mktokens; sourceTree = "<group>"; };
+               FD6061181B7D2C8C004BCA6A /* myhistedit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = myhistedit.h; sourceTree = "<group>"; };
+               FD6061191B7D2C8C004BCA6A /* mystring.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mystring.h; sourceTree = "<group>"; };
+               FD60611A1B7D2C8C004BCA6A /* nodes.c.pat */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = nodes.c.pat; sourceTree = "<group>"; };
+               FD60611B1B7D2C8C004BCA6A /* nodetypes */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = nodetypes; sourceTree = "<group>"; };
+               FD60611C1B7D2C8C004BCA6A /* options.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = options.h; sourceTree = "<group>"; };
+               FD60611D1B7D2C8C004BCA6A /* output.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = output.h; sourceTree = "<group>"; };
+               FD60611E1B7D2C8C004BCA6A /* parser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = parser.h; sourceTree = "<group>"; };
+               FD60611F1B7D2C8C004BCA6A /* redir.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = redir.h; sourceTree = "<group>"; };
+               FD6061201B7D2C8C004BCA6A /* sh.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; path = sh.1; sourceTree = "<group>"; };
+               FD6061211B7D2C8C004BCA6A /* shell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = shell.h; sourceTree = "<group>"; };
+               FD6061221B7D2C8C004BCA6A /* show.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = show.h; sourceTree = "<group>"; };
+               FD6061231B7D2C8C004BCA6A /* trap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = trap.h; sourceTree = "<group>"; };
+               FD6061241B7D2C8C004BCA6A /* var.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = var.h; sourceTree = "<group>"; };
+               FD6061281B7D2D1D004BCA6A /* cmv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = cmv; sourceTree = "<group>"; };
+               FD6061291B7D2D1D004BCA6A /* dirs */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = dirs; sourceTree = "<group>"; };
+               FD60612A1B7D2D1D004BCA6A /* login */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = login; sourceTree = "<group>"; };
+               FD60612B1B7D2D1D004BCA6A /* newgrp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = newgrp; sourceTree = "<group>"; };
+               FD60612C1B7D2D1D004BCA6A /* popd */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = popd; sourceTree = "<group>"; };
+               FD60612D1B7D2D1D004BCA6A /* pushd */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = pushd; sourceTree = "<group>"; };
+               FD60612E1B7D2D1D004BCA6A /* suspend */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = suspend; sourceTree = "<group>"; };
+               FD6061301B7D2DDE004BCA6A /* base.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = base.xcconfig; sourceTree = "<group>"; };
+               FD6061311B7D2DDE004BCA6A /* sh.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = sh.xcconfig; sourceTree = "<group>"; };
                FD88EB01198C5257006B7EFD /* envopts.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = envopts.c; sourceTree = "<group>"; };
                FD88EB02198C5257006B7EFD /* envopts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = envopts.h; sourceTree = "<group>"; };
 /* End PBXFileReference section */
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
+               FD6060AF1B7C0388004BCA6A /* Frameworks */ = {
+                       isa = PBXFrameworksBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
                FC80BF5514A05A2F00C6F7F5 = {
                        isa = PBXGroup;
                        children = (
+                               FD60612F1B7D2DDE004BCA6A /* xcconfigs */,
                                FCBA134014A141A300AA698B /* alias */,
                                FCBA134514A141A300AA698B /* apply */,
                                FCBA134914A141A300AA698B /* basename */,
                                FCBA13DA14A141A300AA698B /* renice */,
                                FCBA13DE14A141A300AA698B /* script */,
                                FCBA13E214A141A300AA698B /* seq */,
+                               FD6060B31B7C0388004BCA6A /* sh */,
                                FCBA13E614A141A300AA698B /* shlock */,
                                FCBA13EA14A141A300AA698B /* sleep */,
                                FCBA13EE14A141A300AA698B /* su */,
                                FCBA15B414A1459700AA698B /* renice */,
                                FCBA15BC14A1459900AA698B /* script */,
                                FCBA15C414A1459E00AA698B /* seq */,
+                               FD6060B21B7C0388004BCA6A /* ash */,
                                FCBA15CC14A145A200AA698B /* shlock */,
                                FCBA15D414A145A500AA698B /* sleep */,
                                FCBA15DC14A145A700AA698B /* su */,
                                FCBA13F814A141A300AA698B /* [.1 */,
                                FCBA13FA14A141A300AA698B /* test.1 */,
                                FCBA13FB14A141A300AA698B /* test.c */,
-                               FCBA13FC14A141A300AA698B /* TEST.csh */,
                        );
                        path = test;
                        sourceTree = "<group>";
                        path = yes;
                        sourceTree = "<group>";
                };
+               FD6060B31B7C0388004BCA6A /* sh */ = {
+                       isa = PBXGroup;
+                       children = (
+                               FD6061001B7C0D01004BCA6A /* BUILT */,
+                               FD6060BC1B7C0471004BCA6A /* alias.c */,
+                               FD6061061B7D2C8C004BCA6A /* alias.h */,
+                               FD6061081B7D2C8C004BCA6A /* arith.h */,
+                               FD6060BD1B7C0471004BCA6A /* arith_yacc.c */,
+                               FD6061071B7D2C8C004BCA6A /* arith_yacc.h */,
+                               FD6060BE1B7C0471004BCA6A /* arith_yylex.c */,
+                               FD6060E81B7C04E4004BCA6A /* bltin */,
+                               FD6061091B7D2C8C004BCA6A /* builtins.def */,
+                               FD6060BF1B7C0471004BCA6A /* cd.c */,
+                               FD60610A1B7D2C8C004BCA6A /* cd.h */,
+                               FD6060C01B7C0471004BCA6A /* error.c */,
+                               FD60610B1B7D2C8C004BCA6A /* error.h */,
+                               FD6060C11B7C0471004BCA6A /* eval.c */,
+                               FD60610C1B7D2C8C004BCA6A /* eval.h */,
+                               FD6060C21B7C0471004BCA6A /* exec.c */,
+                               FD60610D1B7D2C8C004BCA6A /* exec.h */,
+                               FD6060C31B7C0471004BCA6A /* expand.c */,
+                               FD60610E1B7D2C8C004BCA6A /* expand.h */,
+                               FD6061271B7D2D1D004BCA6A /* funcs */,
+                               FD6060C41B7C0471004BCA6A /* histedit.c */,
+                               FD6060C51B7C0471004BCA6A /* input.c */,
+                               FD60610F1B7D2C8C004BCA6A /* input.h */,
+                               FD6060C61B7C0471004BCA6A /* jobs.c */,
+                               FD6061101B7D2C8C004BCA6A /* jobs.h */,
+                               FD6060C71B7C0471004BCA6A /* mail.c */,
+                               FD6061111B7D2C8C004BCA6A /* mail.h */,
+                               FD6060B41B7C0388004BCA6A /* main.c */,
+                               FD6061121B7D2C8C004BCA6A /* main.h */,
+                               FD6060C81B7C0471004BCA6A /* memalloc.c */,
+                               FD6061131B7D2C8C004BCA6A /* memalloc.h */,
+                               FD6060C91B7C0471004BCA6A /* miscbltin.c */,
+                               FD6061141B7D2C8C004BCA6A /* mkbuiltins */,
+                               FD6061151B7D2C8C004BCA6A /* mknodes.c */,
+                               FD6061161B7D2C8C004BCA6A /* mksyntax.c */,
+                               FD6061171B7D2C8C004BCA6A /* mktokens */,
+                               FD6061181B7D2C8C004BCA6A /* myhistedit.h */,
+                               FD6060CA1B7C0471004BCA6A /* mystring.c */,
+                               FD6061191B7D2C8C004BCA6A /* mystring.h */,
+                               FD60611A1B7D2C8C004BCA6A /* nodes.c.pat */,
+                               FD60611B1B7D2C8C004BCA6A /* nodetypes */,
+                               FD6060CB1B7C0471004BCA6A /* options.c */,
+                               FD60611C1B7D2C8C004BCA6A /* options.h */,
+                               FD6060CC1B7C0471004BCA6A /* output.c */,
+                               FD60611D1B7D2C8C004BCA6A /* output.h */,
+                               FD6060CD1B7C0471004BCA6A /* parser.c */,
+                               FD60611E1B7D2C8C004BCA6A /* parser.h */,
+                               FD6060CE1B7C0471004BCA6A /* redir.c */,
+                               FD60611F1B7D2C8C004BCA6A /* redir.h */,
+                               FD6061201B7D2C8C004BCA6A /* sh.1 */,
+                               FD6061211B7D2C8C004BCA6A /* shell.h */,
+                               FD6060CF1B7C0471004BCA6A /* show.c */,
+                               FD6061221B7D2C8C004BCA6A /* show.h */,
+                               FD6060D01B7C0471004BCA6A /* trap.c */,
+                               FD6061231B7D2C8C004BCA6A /* trap.h */,
+                               FD6060D11B7C0471004BCA6A /* var.c */,
+                               FD6061241B7D2C8C004BCA6A /* var.h */,
+                       );
+                       path = sh;
+                       sourceTree = "<group>";
+               };
+               FD6060E81B7C04E4004BCA6A /* bltin */ = {
+                       isa = PBXGroup;
+                       children = (
+                               FD6061051B7D2BF8004BCA6A /* bltin.h */,
+                               FD6060EA1B7C04E4004BCA6A /* echo.c */,
+                       );
+                       path = bltin;
+                       sourceTree = "<group>";
+               };
+               FD6061001B7C0D01004BCA6A /* BUILT */ = {
+                       isa = PBXGroup;
+                       children = (
+                               FD6060FA1B7C0CAF004BCA6A /* builtins.c */,
+                               FD6061011B7D2B6D004BCA6A /* builtins.h */,
+                               FD6060FB1B7C0CAF004BCA6A /* nodes.c */,
+                               FD6061021B7D2B6D004BCA6A /* nodes.h */,
+                               FD6060FC1B7C0CAF004BCA6A /* syntax.c */,
+                               FD6061031B7D2B6D004BCA6A /* syntax.h */,
+                               FD6061041B7D2B6D004BCA6A /* token.h */,
+                       );
+                       name = BUILT;
+                       sourceTree = BUILT_PRODUCTS_DIR;
+               };
+               FD6061271B7D2D1D004BCA6A /* funcs */ = {
+                       isa = PBXGroup;
+                       children = (
+                               FD6061281B7D2D1D004BCA6A /* cmv */,
+                               FD6061291B7D2D1D004BCA6A /* dirs */,
+                               FD60612A1B7D2D1D004BCA6A /* login */,
+                               FD60612B1B7D2D1D004BCA6A /* newgrp */,
+                               FD60612C1B7D2D1D004BCA6A /* popd */,
+                               FD60612D1B7D2D1D004BCA6A /* pushd */,
+                               FD60612E1B7D2D1D004BCA6A /* suspend */,
+                       );
+                       path = funcs;
+                       sourceTree = "<group>";
+               };
+               FD60612F1B7D2DDE004BCA6A /* xcconfigs */ = {
+                       isa = PBXGroup;
+                       children = (
+                               FD6061301B7D2DDE004BCA6A /* base.xcconfig */,
+                               FD6061311B7D2DDE004BCA6A /* sh.xcconfig */,
+                       );
+                       path = xcconfigs;
+                       sourceTree = "<group>";
+               };
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
                        productReference = FCE30EDE14B536C900CC0294 /* locate.code */;
                        productType = "com.apple.product-type.tool";
                };
+               FD6060B11B7C0388004BCA6A /* sh */ = {
+                       isa = PBXNativeTarget;
+                       buildConfigurationList = FD6060B71B7C0388004BCA6A /* Build configuration list for PBXNativeTarget "sh" */;
+                       buildPhases = (
+                               FD6060EF1B7C0590004BCA6A /* mkbuiltins */,
+                               FD6060F11B7C0742004BCA6A /* mknodes */,
+                               FD6060F21B7C0744004BCA6A /* mksyntax */,
+                               FD6060F31B7C0964004BCA6A /* mktokens */,
+                               FD6060AE1B7C0388004BCA6A /* Sources */,
+                               FD6060AF1B7C0388004BCA6A /* Frameworks */,
+                               FD6061321B7D32AD004BCA6A /* sh.1 */,
+                       );
+                       buildRules = (
+                       );
+                       dependencies = (
+                       );
+                       name = sh;
+                       productName = sh;
+                       productReference = FD6060B21B7C0388004BCA6A /* ash */;
+                       productType = "com.apple.product-type.tool";
+               };
 /* End PBXNativeTarget section */
 
 /* Begin PBXProject section */
                        attributes = {
                                LastUpgradeCheck = 0430;
                                ORGANIZATIONNAME = "Apple Inc.";
+                               TargetAttributes = {
+                                       FD6060B11B7C0388004BCA6A = {
+                                               CreatedOnToolsVersion = 7.0;
+                                       };
+                               };
                        };
                        buildConfigurationList = FC80BF5A14A05A2F00C6F7F5 /* Build configuration list for PBXProject "shell_cmds" */;
                        compatibilityVersion = "Xcode 3.2";
                                FCBA15AE14A1459700AA698B /* renice */,
                                FCBA15B614A1459900AA698B /* script */,
                                FCBA15BE14A1459E00AA698B /* seq */,
+                               FD6060B11B7C0388004BCA6A /* sh */,
                                FCBA15C614A145A200AA698B /* shlock */,
                                FCBA15CE14A145A500AA698B /* sleep */,
                                FCBA15D614A145A700AA698B /* su */,
                        shellScript = ". \"$PROJECT_DIR\"/xcodescripts/install-files.sh";
                        showEnvVarsInLog = 0;
                };
+               FD6060EF1B7C0590004BCA6A /* mkbuiltins */ = {
+                       isa = PBXShellScriptBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       inputPaths = (
+                               "$(SRCROOT)/sh/mkbuiltins",
+                               "$(SRCROOT)/sh/builtins.def",
+                       );
+                       name = mkbuiltins;
+                       outputPaths = (
+                               "$(BUILT_PRODUCTS_DIR)/builtins.c",
+                               "$(BUILT_PRODUCTS_DIR)/builtins.h",
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+                       shellPath = /bin/sh;
+                       shellScript = "cd ${BUILT_PRODUCTS_DIR} && sh ${SRCROOT}/sh/mkbuiltins ${SRCROOT}/sh";
+               };
+               FD6060F11B7C0742004BCA6A /* mknodes */ = {
+                       isa = PBXShellScriptBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       inputPaths = (
+                               "$(SRCROOT)/sh/mknodes.c",
+                               "$(SRCROOT)/sh/nodetypes",
+                               "$(SRCROOT)/sh/nodes.c.get",
+                       );
+                       name = mknodes;
+                       outputPaths = (
+                               "$(BUILT_PRODUCTS_DIR)/nodes.c",
+                               "$(BUILT_PRODUCTS_DIR)/nodes.h",
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+                       shellPath = /bin/sh;
+                       shellScript = "env -i xcrun -sdk macosx cc ${SRCROOT}/sh/mknodes.c -o ${DERIVED_FILE_DIR}/mknodes\ncd ${BUILT_PRODUCTS_DIR} && ${DERIVED_FILE_DIR}/mknodes ${SRCROOT}/sh/nodetypes ${SRCROOT}/sh/nodes.c.pat";
+               };
+               FD6060F21B7C0744004BCA6A /* mksyntax */ = {
+                       isa = PBXShellScriptBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       inputPaths = (
+                               "$(SRCROOT)/sh/mksyntax.c",
+                       );
+                       name = mksyntax;
+                       outputPaths = (
+                               "$(BUILT_PRODUCTS_DIR)/syntax.c",
+                               "$(BUILT_PRODUCTS_DIR)/syntax.h",
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+                       shellPath = /bin/sh;
+                       shellScript = "env -i xcrun -sdk macosx cc ${SRCROOT}/sh/mksyntax.c -o ${DERIVED_FILE_DIR}/mksyntax\ncd ${BUILT_PRODUCTS_DIR} && ${DERIVED_FILE_DIR}/mksyntax";
+               };
+               FD6060F31B7C0964004BCA6A /* mktokens */ = {
+                       isa = PBXShellScriptBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       inputPaths = (
+                               "$(SRCROOT)/sh/mktokens",
+                       );
+                       name = mktokens;
+                       outputPaths = (
+                               "$(BUILT_PRODUCTS_DIR)/token.h",
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+                       shellPath = /bin/sh;
+                       shellScript = "cd ${BUILT_PRODUCTS_DIR} && sh ${SRCROOT}/sh/mktokens";
+               };
+               FD6061321B7D32AD004BCA6A /* sh.1 */ = {
+                       isa = PBXShellScriptBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                       );
+                       inputPaths = (
+                               "$(SRCROOT)/sh/sh.1",
+                       );
+                       name = sh.1;
+                       outputPaths = (
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+                       shellPath = /bin/sh;
+                       shellScript = "install -d -m 0755 ${DSTROOT}${SH_MAN_PREFIX}/share/man/man1\ninstall -m 0644 ${SRCROOT}/sh/sh.1 ${DSTROOT}${SH_MAN_PREFIX}/share/man/man1/${PRODUCT_NAME}.1";
+               };
 /* End PBXShellScriptBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
+               FD6060AE1B7C0388004BCA6A /* Sources */ = {
+                       isa = PBXSourcesBuildPhase;
+                       buildActionMask = 2147483647;
+                       files = (
+                               FD6060D21B7C0471004BCA6A /* alias.c in Sources */,
+                               FD6060D31B7C0471004BCA6A /* arith_yacc.c in Sources */,
+                               FD6060D41B7C0471004BCA6A /* arith_yylex.c in Sources */,
+                               FD6060D51B7C0471004BCA6A /* cd.c in Sources */,
+                               FD6060EB1B7C04E4004BCA6A /* echo.c in Sources */,
+                               FD6060D61B7C0471004BCA6A /* error.c in Sources */,
+                               FD6060D71B7C0471004BCA6A /* eval.c in Sources */,
+                               FD6060D81B7C0471004BCA6A /* exec.c in Sources */,
+                               FD6060D91B7C0471004BCA6A /* expand.c in Sources */,
+                               FD6060DA1B7C0471004BCA6A /* histedit.c in Sources */,
+                               FD6060DB1B7C0471004BCA6A /* input.c in Sources */,
+                               FD6060DC1B7C0471004BCA6A /* jobs.c in Sources */,
+                               FD6060EC1B7C0506004BCA6A /* kill.c in Sources */,
+                               FD6060DD1B7C0471004BCA6A /* mail.c in Sources */,
+                               FD6060B51B7C0388004BCA6A /* main.c in Sources */,
+                               FD6060DE1B7C0471004BCA6A /* memalloc.c in Sources */,
+                               FD6060DF1B7C0471004BCA6A /* miscbltin.c in Sources */,
+                               FD6060E01B7C0471004BCA6A /* mystring.c in Sources */,
+                               FD6060E11B7C0471004BCA6A /* options.c in Sources */,
+                               FD6060E21B7C0471004BCA6A /* output.c in Sources */,
+                               FD6060E31B7C0471004BCA6A /* parser.c in Sources */,
+                               FD6060ED1B7C0518004BCA6A /* printf.c in Sources */,
+                               FD6060E41B7C0471004BCA6A /* redir.c in Sources */,
+                               FD6060E51B7C0471004BCA6A /* show.c in Sources */,
+                               FD6060EE1B7C0521004BCA6A /* test.c in Sources */,
+                               FD6060E61B7C0471004BCA6A /* trap.c in Sources */,
+                               FD6060E71B7C0471004BCA6A /* var.c in Sources */,
+                               FD6060FD1B7C0CAF004BCA6A /* builtins.c in Sources */,
+                               FD6060FE1B7C0CAF004BCA6A /* nodes.c in Sources */,
+                               FD6060FF1B7C0CAF004BCA6A /* syntax.c in Sources */,
+                       );
+                       runOnlyForDeploymentPostprocessing = 0;
+               };
 /* End PBXSourcesBuildPhase section */
 
 /* Begin PBXTargetDependency section */
                        target = FCBA163614A145CA00AA698B /* yes */;
                        targetProxy = FCE30FA014B619E600CC0294 /* PBXContainerItemProxy */;
                };
+               FD6060B91B7C03B3004BCA6A /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = FD6060B11B7C0388004BCA6A /* sh */;
+                       targetProxy = FD6060B81B7C03B3004BCA6A /* PBXContainerItemProxy */;
+               };
+               FD6060BB1B7C03BC004BCA6A /* PBXTargetDependency */ = {
+                       isa = PBXTargetDependency;
+                       target = FD6060B11B7C0388004BCA6A /* sh */;
+                       targetProxy = FD6060BA1B7C03BC004BCA6A /* PBXContainerItemProxy */;
+               };
 /* End PBXTargetDependency section */
 
 /* Begin XCBuildConfiguration section */
                        buildSettings = {
                                ALWAYS_SEARCH_USER_PATHS = NO;
                                APPLY_RULES_IN_COPY_FILES = YES;
+                               CODE_SIGN_IDENTITY = "-";
                                COPY_PHASE_STRIP = YES;
                                CURRENT_PROJECT_VERSION = "$(RC_ProjectSourceVersion)";
                                DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
                        };
                        name = Release;
                };
+               FD6060B61B7C0388004BCA6A /* Release */ = {
+                       isa = XCBuildConfiguration;
+                       baseConfigurationReference = FD6061311B7D2DDE004BCA6A /* sh.xcconfig */;
+                       buildSettings = {
+                       };
+                       name = Release;
+               };
 /* End XCBuildConfiguration section */
 
 /* Begin XCConfigurationList section */
                        defaultConfigurationIsVisible = 0;
                        defaultConfigurationName = Release;
                };
+               FD6060B71B7C0388004BCA6A /* Build configuration list for PBXNativeTarget "sh" */ = {
+                       isa = XCConfigurationList;
+                       buildConfigurations = (
+                               FD6060B61B7C0388004BCA6A /* Release */,
+                       );
+                       defaultConfigurationIsVisible = 0;
+                       defaultConfigurationName = Release;
+               };
 /* End XCConfigurationList section */
        };
        rootObject = FC80BF5714A05A2F00C6F7F5 /* Project object */;
diff --git a/shell_cmds.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/shell_cmds.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644 (file)
index 0000000..919434a
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:">
+   </FileRef>
+</Workspace>
diff --git a/test/TEST.csh b/test/TEST.csh
deleted file mode 100644 (file)
index 672e1a0..0000000
+++ /dev/null
@@ -1,138 +0,0 @@
-#      $NetBSD: TEST.csh,v 1.2 1995/03/21 07:03:59 cgd Exp $
-#      @(#)TEST.csh    5.2 (Berkeley) 4/30/93
-
-#alias t '/usr/src/bin/test/obj/test \!*; echo $status'
-alias t '/bin/test \!*; echo $status'
-
-echo 't -b /dev/ttyp2'
-t -b /dev/ttyp2
-echo 't -b /dev/jb1a'
-t -b /dev/jb1a
-
-echo 't -c test.c'
-t -c test.c
-echo 't -c /dev/tty'
-t -c /dev/tty
-
-echo 't -d test.c'
-t -d test.c
-echo 't -d /etc'
-t -d /etc
-
-echo 't -e noexist'
-t -e noexist
-echo 't -e test.c'
-t -e test.c
-
-echo 't -f noexist'
-t -f noexist
-echo 't -f /dev/tty'
-t -f /dev/tty
-echo 't -f test.c'
-t -f test.c
-
-echo 't -g test.c'
-t -g test.c
-echo 't -g /bin/ps'
-t -g /bin/ps
-
-echo 't -n ""'
-t -n ""
-echo 't -n "hello"'
-t -n "hello"
-
-echo 't -p test.c'
-t -p test.c
-
-echo 't -r noexist'
-t -r noexist
-echo 't -r /etc/master.passwd'
-t -r /etc/master.passwd
-echo 't -r test.c'
-t -r test.c
-
-echo 't -s noexist'
-t -s noexist
-echo 't -s /dev/null'
-t -s /dev/null
-echo 't -s test.c'
-t -s test.c
-
-echo 't -t 20'
-t -t 20
-echo 't -t 0'
-t -t 0
-
-echo 't -u test.c'
-t -u test.c
-echo 't -u /bin/rcp'
-t -u /bin/rcp
-
-echo 't -w noexist'
-t -w noexist
-echo 't -w /etc/master.passwd'
-t -w /etc/master.passwd
-echo 't -w /dev/null'
-t -w /dev/null
-
-echo 't -x noexist'
-t -x noexist
-echo 't -x /bin/ps'
-t -x /bin/ps
-echo 't -x /etc/motd'
-t -x /etc/motd
-
-echo 't -z ""'
-t -z ""
-echo 't -z "foo"'
-t -z "foo"
-
-echo 't "foo"'
-t "foo"
-echo 't ""'
-t ""
-
-echo 't "hello" = "hello"'
-t "hello" = "hello"
-echo 't "hello" = "goodbye"'
-t "hello" = "goodbye"
-
-echo 't "hello" != "hello"'
-t "hello" != "hello"
-echo 't "hello" != "goodbye"'
-t "hello" != "goodbye"
-
-echo 't 200 -eq 200'
-t 200 -eq 200
-echo 't 34 -eq 222'
-t 34 -eq 222
-
-echo 't 200 -ne 200'
-t 200 -ne 200
-echo 't 34 -ne 222'
-t 34 -ne 222
-
-echo 't 200 -gt 200'
-t 200 -gt 200
-echo 't 340 -gt 222'
-t 340 -gt 222
-
-echo 't 200 -ge 200'
-t 200 -ge 200
-echo 't 34 -ge 222'
-t 34 -ge 222
-
-echo 't 200 -lt 200'
-t 200 -lt 200
-echo 't 34 -lt 222'
-t 34 -lt 222
-
-echo 't 200 -le 200'
-t 200 -le 200
-echo 't 340 -le 222'
-t 340 -le 222
-
-echo 't 700 -le 1000 -a -n "1" -a "20" = "20"'
-t 700 -le 1000 -a -n "1" -a "20" = "20"
-echo 't ! \( 700 -le 1000 -a -n "1" -a "20" = "20" \)'
-t ! \( 700 -le 1000 -a -n "1" -a "20" = "20" \)
index 9ed63860682b66781534fc996b68ba486bf76be6..84e056804da8ec278fcb0262c66635a050a15645 100644 (file)
--- a/test/[.1
+++ b/test/[.1
@@ -1 +1 @@
-.so man1/test.1
\ No newline at end of file
+.so man1/test.1
index 44c5ff99d02d30c7251fb8c2a56d30eb699e20c7..136ee5724fdf2cac70fc4b0a123358075c0810bf 100644 (file)
@@ -1,5 +1,4 @@
-.\"    $NetBSD: test.1,v 1.10 1997/11/12 00:48:21 mrg Exp $
-.\"
+.\"-
 .\" Copyright (c) 1991, 1993
 .\"    The Regents of the University of California.  All rights reserved.
 .\"
 .\" 2. Redistributions in binary form must reproduce the above copyright
 .\"    notice, this list of conditions and the following disclaimer in the
 .\"    documentation and/or other materials provided with the distribution.
-.\" 3. All advertising materials mentioning features or use of this software
-.\"    must display the following acknowledgement:
-.\"    This product includes software developed by the University of
-.\"    California, Berkeley and its contributors.
 .\" 4. Neither the name of the University nor the names of its contributors
 .\"    may be used to endorse or promote products derived from this software
 .\"    without specific prior written permission.
@@ -35,8 +30,9 @@
 .\" SUCH DAMAGE.
 .\"
 .\"     @(#)test.1     8.1 (Berkeley) 5/31/93
+.\" $FreeBSD$
 .\"
-.Dd May 31, 1993
+.Dd June 1, 2013
 .Dt TEST 1
 .Os
 .Sh NAME
 .Nm
 .Ar expression
 .Nm \&[
-.Ar expression Cm ]
+.Ar expression Cm \&]
 .Sh DESCRIPTION
 The
 .Nm
 utility evaluates the expression and, if it evaluates
 to true, returns a zero (true) exit status; otherwise
 it returns 1 (false).
-If there is no expression, test also
+If there is no expression,
+.Nm
+also
 returns 1 (false).
 .Pp
 All operators and flags are separate arguments to the
 .Nm
 utility.
 .Pp
-The following primaries are used to construct expressions:
+The following primaries are used to construct expression:
 .Bl -tag -width Ar
 .It Fl b Ar file
 True if
@@ -95,7 +93,8 @@ True if
 .Ar file
 exists and is a symbolic link.
 This operator is retained for compatibility with previous versions of
-this program. Do not rely on its existence; use
+this program.
+Do not rely on its existence; use
 .Fl L
 instead.
 .It Fl k Ar file
@@ -110,10 +109,10 @@ is nonzero.
 True if
 .Ar file
 is a named pipe
-.Po Tn FIFO Pc .
+.Pq Tn FIFO .
 .It Fl r Ar file
 True if
-.Ar file 
+.Ar file
 exists and is readable.
 .It Fl s Ar file
 True if
@@ -154,7 +153,7 @@ True if the length of
 .Ar string
 is zero.
 .It Fl L Ar file
-True if 
+True if
 .Ar file
 exists and is a symbolic link.
 .It Fl O Ar file
@@ -190,70 +189,81 @@ True if
 .Ar string
 is not the null
 string.
-.It Ar \&s\&1 Cm \&= Ar \&s\&2
+.It Ar s1 Cm = Ar s2
 True if the strings
-.Ar \&s\&1
+.Ar s1
 and
-.Ar \&s\&2
+.Ar s2
 are identical.
-.It Ar \&s\&1 Cm \&!= Ar \&s\&2
+.It Ar s1 Cm != Ar s2
 True if the strings
-.Ar \&s\&1
+.Ar s1
 and
-.Ar \&s\&2
+.Ar s2
 are not identical.
-.It Ar \&s\&1 Cm \&< Ar \&s\&2
+.It Ar s1 Cm < Ar s2
 True if string
-.Ar \&s\&1
+.Ar s1
 comes before
-.Ar \&s\&2
-based on the ASCII value of their characters.
-.It Ar \&s\&1 Cm \&> Ar \&s\&2
+.Ar s2
+based on the binary value of their characters.
+.It Ar s1 Cm > Ar s2
 True if string
-.Ar \&s\&1
+.Ar s1
 comes after
-.Ar \&s\&2
-based on the ASCII value of their characters.
-.It Ar \&n\&1 Fl \&eq Ar \&n\&2 
+.Ar s2
+based on the binary value of their characters.
+.It Ar n1 Fl eq Ar n2
 True if the integers
-.Ar \&n\&1
+.Ar n1
 and
-.Ar \&n\&2
+.Ar n2
 are algebraically
 equal.
-.It Ar \&n\&1 Fl \&ne Ar \&n\&2
+.It Ar n1 Fl ne Ar n2
 True if the integers
-.Ar \&n\&1
+.Ar n1
 and
-.Ar \&n\&2
+.Ar n2
 are not
 algebraically equal.
-.It Ar \&n\&1 Fl \&gt Ar \&n\&2
+.It Ar n1 Fl gt Ar n2
 True if the integer
-.Ar \&n\&1
+.Ar n1
 is algebraically
 greater than the integer
-.Ar \&n\&2 .
-.It Ar \&n\&1 Fl \&ge Ar \&n\&2
+.Ar n2 .
+.It Ar n1 Fl ge Ar n2
 True if the integer
-.Ar \&n\&1
+.Ar n1
 is algebraically
 greater than or equal to the integer
-.Ar \&n\&2 .
-.It Ar \&n\&1 Fl \&lt Ar \&n\&2
+.Ar n2 .
+.It Ar n1 Fl lt Ar n2
 True if the integer
-.Ar \&n\&1
+.Ar n1
 is algebraically less
 than the integer
-.Ar \&n\&2 .
-.It Ar \&n\&1 Fl \&le Ar \&n\&2
+.Ar n2 .
+.It Ar n1 Fl le Ar n2
 True if the integer
-.Ar \&n\&1
+.Ar n1
 is algebraically less
 than or equal to the integer
-.Ar \&n\&2 .
+.Ar n2 .
 .El
 .Pp
+If
+.Ar file
+is a symbolic link,
+.Nm
+will fully dereference it and then evaluate the expression
+against the file referenced, except for the
+.Fl h
+and
+.Fl L
+primaries.
+.Pp
 These primaries can be combined with the following operators:
 .Bl -tag -width Ar
 .It Cm \&! Ar expression
@@ -272,7 +282,7 @@ True if either
 or
 .Ar expression2
 are true.
-.It Cm \&( Ns Ar expression Ns Cm \&)
+.It Cm \&( Ar expression Cm \&)
 True if expression is true.
 .El
 .Pp
@@ -281,21 +291,38 @@ The
 operator has higher precedence than the
 .Fl o
 operator.
+.Pp
+Some shells may provide a builtin
+.Nm
+command which is similar or identical to this utility.
+Consult the
+.Xr builtin 1
+manual page.
 .Sh GRAMMAR AMBIGUITY
-The 
+The
 .Nm
-grammar is inherently ambiguous.  In order to assure a degree of consistency,
-the cases described in 
-.St -p1003.2
-section 4.62.4, 
+grammar is inherently ambiguous.
+In order to assure a degree of consistency,
+the cases described in the
+.St -p1003.2 ,
+section D11.2/4.62.4, standard
 are evaluated consistently according to the rules specified in the
-standards document.  All other cases are subject to the ambiguity in the
+standards document.
+All other cases are subject to the ambiguity in the
 command semantics.
-.Sh RETURN VALUES
+.Pp
+In particular, only expressions containing
+.Fl a ,
+.Fl o ,
+.Cm \&(
+or
+.Cm \&)
+can be ambiguous.
+.Sh EXIT STATUS
 The
 .Nm
 utility exits with one of the following values:
-.Bl -tag -width Ds
+.Bl -tag -width indent
 .It 0
 expression evaluated to true.
 .It 1
@@ -304,9 +331,60 @@ missing.
 .It >1
 An error occurred.
 .El
+.Sh EXAMPLES
+Implement
+.Li test FILE1 -nt FILE2
+using only
+.Tn POSIX
+functionality:
+.Pp
+.Dl test -n \&"$(find -L -- FILE1 -prune -newer FILE2 2>/dev/null)\&"
+.Pp
+This can be modified using non-standard
+.Xr find 1
+primaries like
+.Cm -newerca
+to compare other timestamps.
+.Sh COMPATIBILITY
+For compatibility with some other implementations,
+the
+.Cm =
+primary can be substituted with
+.Cm ==
+with the same meaning.
+.Sh SEE ALSO
+.Xr builtin 1 ,
+.Xr expr 1 ,
+.Xr find 1 ,
+.Xr sh 1 ,
+.Xr stat 1 ,
+.Xr symlink 7
 .Sh STANDARDS
 The
 .Nm
 utility implements a superset of the
 .St -p1003.2
 specification.
+The primaries
+.Cm < ,
+.Cm == ,
+.Cm > ,
+.Fl ef ,
+.Fl nt ,
+.Fl ot ,
+.Fl G ,
+and
+.Fl O
+are extensions.
+.Sh BUGS
+Both sides are always evaluated in
+.Fl a
+and
+.Fl o .
+For instance, the writable status of
+.Pa file
+will be tested by the following command even though the former expression
+indicated false, which results in a gratuitous access to the file system:
+.Dl "[ -z abc -a -w file ]"
+To avoid this, write
+.Dl "[ -z abc ] && [ -w file ]"
index 20df58a2e5255b30b3253f56c300484f93b02698..14bd3a3c10786d1c81426580aa5c1653ee507e83 100644 (file)
@@ -1,6 +1,6 @@
-/*     $NetBSD: test.c,v 1.19 1998/07/28 11:41:59 mycroft Exp $        */
+/*     $NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $ */
 
-/*
+/*-
  * test(1); version 7-like  --  author Erik Baalbergen
  * modified by Eric Gisin to be used as built-in.
  * modified by Arnold Robbins to add SVR3 compatibility
@@ -9,21 +9,54 @@
  *
  * This program is in the Public Domain.
  */
+/*
+ * Important: This file is used both as a standalone program /bin/test and
+ * as a builtin for /bin/sh (#define SHELL).
+ */
 
 #include <sys/cdefs.h>
-#ifndef lint
-__RCSID("$NetBSD: test.c,v 1.19 1998/07/28 11:41:59 mycroft Exp $");
-#endif
+__FBSDID("$FreeBSD$");
 
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <unistd.h>
+
 #include <ctype.h>
+#include <err.h>
 #include <errno.h>
+#ifdef __APPLE__
+#include <fcntl.h>
+#endif /* __APPLE__ */
+#include <inttypes.h>
+#include <limits.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <err.h>
+#include <unistd.h>
+
+#ifdef __APPLE__
+#define eaccess(path, mode) faccessat(AT_FDCWD, path, mode, AT_EACCESS)
+#define st_mtim st_mtimespec
+#endif /* __APPLE__ */
+
+#ifdef SHELL
+#define main testcmd
+#include "bltin/bltin.h"
+#else
+#include <locale.h>
+
+static void error(const char *, ...) __dead2 __printf0like(1, 2);
+
+static void
+error(const char *msg, ...)
+{
+       va_list ap;
+       va_start(ap, msg);
+       verrx(2, msg, ap);
+       /*NOTREACHED*/
+       va_end(ap);
+}
+#endif
 
 /* test(1) accepts the following grammar:
        oexpr   ::= aexpr | aexpr "-o" oexpr ;
@@ -42,9 +75,18 @@ __RCSID("$NetBSD: test.c,v 1.19 1998/07/28 11:41:59 mycroft Exp $");
        operand ::= <any legal UNIX file name>
 */
 
+enum token_types {
+       UNOP = 0x100,
+       BINOP = 0x200,
+       BUNOP = 0x300,
+       BBINOP = 0x400,
+       PAREN = 0x500
+};
+
 enum token {
        EOI,
-       FILRD,
+       OPERAND,
+       FILRD = UNOP + 1,
        FILWR,
        FILEX,
        FILEXIST,
@@ -60,13 +102,13 @@ enum token {
        FILSUID,
        FILSGID,
        FILSTCK,
-       FILNT,
-       FILOT,
-       FILEQ,
-       FILUID,
-       FILGID,
        STREZ,
        STRNZ,
+       FILUID,
+       FILGID,
+       FILNT = BINOP + 1,
+       FILOT,
+       FILEQ,
        STREQ,
        STRNE,
        STRLT,
@@ -77,236 +119,230 @@ enum token {
        INTGT,
        INTLE,
        INTLT,
-       UNOT,
-       BAND,
+       UNOT = BUNOP + 1,
+       BAND = BBINOP + 1,
        BOR,
-       LPAREN,
-       RPAREN,
-       OPERAND
+       LPAREN = PAREN + 1,
+       RPAREN
 };
 
-enum token_types {
-       UNOP,
-       BINOP,
-       BUNOP,
-       BBINOP,
-       PAREN
-};
+#define TOKEN_TYPE(token) ((token) & 0xff00)
 
-struct t_op {
-       const char *op_text;
-       short op_num, op_type;
+static struct t_op {
+       char op_text[4];
+       short op_num;
 } const ops [] = {
-       {"-r",  FILRD,  UNOP},
-       {"-w",  FILWR,  UNOP},
-       {"-x",  FILEX,  UNOP},
-       {"-e",  FILEXIST,UNOP},
-       {"-f",  FILREG, UNOP},
-       {"-d",  FILDIR, UNOP},
-       {"-c",  FILCDEV,UNOP},
-       {"-b",  FILBDEV,UNOP},
-       {"-p",  FILFIFO,UNOP},
-       {"-u",  FILSUID,UNOP},
-       {"-g",  FILSGID,UNOP},
-       {"-k",  FILSTCK,UNOP},
-       {"-s",  FILGZ,  UNOP},
-       {"-t",  FILTT,  UNOP},
-       {"-z",  STREZ,  UNOP},
-       {"-n",  STRNZ,  UNOP},
-       {"-h",  FILSYM, UNOP},          /* for backwards compat */
-       {"-O",  FILUID, UNOP},
-       {"-G",  FILGID, UNOP},
-       {"-L",  FILSYM, UNOP},
-       {"-S",  FILSOCK,UNOP},
-       {"=",   STREQ,  BINOP},
-       {"!=",  STRNE,  BINOP},
-       {"<",   STRLT,  BINOP},
-       {">",   STRGT,  BINOP},
-       {"-eq", INTEQ,  BINOP},
-       {"-ne", INTNE,  BINOP},
-       {"-ge", INTGE,  BINOP},
-       {"-gt", INTGT,  BINOP},
-       {"-le", INTLE,  BINOP},
-       {"-lt", INTLT,  BINOP},
-       {"-nt", FILNT,  BINOP},
-       {"-ot", FILOT,  BINOP},
-       {"-ef", FILEQ,  BINOP},
-       {"!",   UNOT,   BUNOP},
-       {"-a",  BAND,   BBINOP},
-       {"-o",  BOR,    BBINOP},
-       {"(",   LPAREN, PAREN},
-       {")",   RPAREN, PAREN},
-       {0,     0,      0}
+       {"-r",  FILRD},
+       {"-w",  FILWR},
+       {"-x",  FILEX},
+       {"-e",  FILEXIST},
+       {"-f",  FILREG},
+       {"-d",  FILDIR},
+       {"-c",  FILCDEV},
+       {"-b",  FILBDEV},
+       {"-p",  FILFIFO},
+       {"-u",  FILSUID},
+       {"-g",  FILSGID},
+       {"-k",  FILSTCK},
+       {"-s",  FILGZ},
+       {"-t",  FILTT},
+       {"-z",  STREZ},
+       {"-n",  STRNZ},
+       {"-h",  FILSYM},                /* for backwards compat */
+       {"-O",  FILUID},
+       {"-G",  FILGID},
+       {"-L",  FILSYM},
+       {"-S",  FILSOCK},
+       {"=",   STREQ},
+       {"==",  STREQ},
+       {"!=",  STRNE},
+       {"<",   STRLT},
+       {">",   STRGT},
+       {"-eq", INTEQ},
+       {"-ne", INTNE},
+       {"-ge", INTGE},
+       {"-gt", INTGT},
+       {"-le", INTLE},
+       {"-lt", INTLT},
+       {"-nt", FILNT},
+       {"-ot", FILOT},
+       {"-ef", FILEQ},
+       {"!",   UNOT},
+       {"-a",  BAND},
+       {"-o",  BOR},
+       {"(",   LPAREN},
+       {")",   RPAREN},
+       {"",    0}
 };
 
-char **t_wp;
-struct t_op const *t_wp_op;
-
-static void syntax __P((const char *, const char *));
-static int oexpr __P((enum token));
-static int aexpr __P((enum token));
-static int nexpr __P((enum token));
-static int primary __P((enum token));
-static int binop __P((void));
-static int filstat __P((char *, enum token));
-static enum token t_lex __P((char *));
-static int getn __P((const char *));
-static int newerf __P((const char *, const char *));
-static int olderf __P((const char *, const char *));
-static int equalf __P((const char *, const char *));
-
-int main __P((int, char **));
+static int nargc;
+static char **t_wp;
+static int parenlevel;
+
+static int     aexpr(enum token);
+static int     binop(enum token);
+static int     equalf(const char *, const char *);
+static int     filstat(char *, enum token);
+static int     getn(const char *);
+static intmax_t        getq(const char *);
+static int     intcmp(const char *, const char *);
+static int     isunopoperand(void);
+static int     islparenoperand(void);
+static int     isrparenoperand(void);
+static int     newerf(const char *, const char *);
+static int     nexpr(enum token);
+static int     oexpr(enum token);
+static int     olderf(const char *, const char *);
+static int     primary(enum token);
+static void    syntax(const char *, const char *);
+static enum    token t_lex(char *);
 
 int
-main(argc, argv)
-       int argc;
-       char **argv;
+main(int argc, char **argv)
 {
        int     res;
+       char    *p;
+
+       /* radar:4689479 */
+       if (argc == 0)
+               return 1;
 
-       if (argv[0] && strcmp(argv[0], "[") == 0) {
-               if (strcmp(argv[--argc], "]"))
-                       errx(2, "missing ]");
+       if ((p = strrchr(argv[0], '/')) == NULL)
+               p = argv[0];
+       else
+               p++;
+       if (strcmp(p, "[") == 0) {
+               if (strcmp(argv[--argc], "]") != 0)
+                       error("missing ]");
                argv[argc] = NULL;
        }
 
-       /* Implement special cases from POSIX.2, section 4.62.4 */
-       switch (argc) {
-       case 1:
+       /* no expression => false */
+       if (--argc <= 0)
                return 1;
-       case 2:
-               return (*argv[1] == '\0');
-       case 3:
-               if (argv[1][0] == '!' && argv[1][1] == '\0') {
-                       return !(*argv[2] == '\0');
-               }
-               break;
-       case 4:
-               if (argv[1][0] != '!' || argv[1][1] != '\0') {
-                       if (t_lex(argv[2]), 
-                           t_wp_op && t_wp_op->op_type == BINOP) {
-                               t_wp = &argv[1];
-                               return (binop() == 0);
-                       }
-               }
-               break;
-       case 5:
-               if (argv[1][0] == '!' && argv[1][1] == '\0') {
-                       if (t_lex(argv[3]), 
-                           t_wp_op && t_wp_op->op_type == BINOP) {
-                               t_wp = &argv[2];
-                               return !(binop() == 0);
-                       }
-               }
-               break;
-       }
 
+#ifndef SHELL
+       (void)setlocale(LC_CTYPE, "");
+#endif
+       nargc = argc;
        t_wp = &argv[1];
-       res = !oexpr(t_lex(*t_wp));
-
-       if (*t_wp != NULL && *++t_wp != NULL)
-               syntax(*t_wp, "unknown operand");
+       parenlevel = 0;
+       if (nargc == 4 && strcmp(*t_wp, "!") == 0) {
+               /* Things like ! "" -o x do not fit in the normal grammar. */
+               --nargc;
+               ++t_wp;
+               res = oexpr(t_lex(*t_wp));
+       } else
+               res = !oexpr(t_lex(*t_wp));
+
+       if (--nargc > 0)
+               syntax(*t_wp, "unexpected operator");
 
        return res;
 }
 
 static void
-syntax(op, msg)
-       const char      *op;
-       const char      *msg;
+syntax(const char *op, const char *msg)
 {
+
        if (op && *op)
-               errx(2, "%s: %s", op, msg);
+               error("%s: %s", op, msg);
        else
-               errx(2, "%s", msg);
+               error("%s", msg);
 }
 
 static int
-oexpr(n)
-       enum token n;
+oexpr(enum token n)
 {
        int res;
 
        res = aexpr(n);
-       if (t_lex(*++t_wp) == BOR)
-               return oexpr(t_lex(*++t_wp)) || res;
+       if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR)
+               return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) ||
+                   res;
        t_wp--;
+       nargc++;
        return res;
 }
 
 static int
-aexpr(n)
-       enum token n;
+aexpr(enum token n)
 {
        int res;
 
        res = nexpr(n);
-       if (t_lex(*++t_wp) == BAND)
-               return aexpr(t_lex(*++t_wp)) && res;
+       if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND)
+               return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) &&
+                   res;
        t_wp--;
+       nargc++;
        return res;
 }
 
 static int
-nexpr(n)
-       enum token n;                   /* token */
+nexpr(enum token n)
 {
        if (n == UNOT)
-               return !nexpr(t_lex(*++t_wp));
+               return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL));
        return primary(n);
 }
 
 static int
-primary(n)
-       enum token n;
+primary(enum token n)
 {
+       enum token nn;
        int res;
 
        if (n == EOI)
-               syntax(NULL, "argument expected");
+               return 0;               /* missing expression */
        if (n == LPAREN) {
-               res = oexpr(t_lex(*++t_wp));
-               if (t_lex(*++t_wp) != RPAREN)
+               parenlevel++;
+               if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) ==
+                   RPAREN) {
+                       parenlevel--;
+                       return 0;       /* missing expression */
+               }
+               res = oexpr(nn);
+               if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN)
                        syntax(NULL, "closing paren expected");
+               parenlevel--;
                return res;
        }
-       if (t_wp_op && t_wp_op->op_type == UNOP) {
+       if (TOKEN_TYPE(n) == UNOP) {
                /* unary expression */
-               if (*++t_wp == NULL)
-                       syntax(t_wp_op->op_text, "argument expected");
+               if (--nargc == 0)
+                       syntax(NULL, "argument expected"); /* impossible */
                switch (n) {
                case STREZ:
-                       return strlen(*t_wp) == 0;
+                       return strlen(*++t_wp) == 0;
                case STRNZ:
-                       return strlen(*t_wp) != 0;
+                       return strlen(*++t_wp) != 0;
                case FILTT:
-                       return isatty(getn(*t_wp));
+                       return isatty(getn(*++t_wp));
                default:
-                       return filstat(*t_wp, n);
+                       return filstat(*++t_wp, n);
                }
        }
 
-       if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) {
-               return binop();
-       }         
+       nn = t_lex(nargc > 0 ? t_wp[1] : NULL);
+       if (TOKEN_TYPE(nn) == BINOP)
+               return binop(nn);
 
        return strlen(*t_wp) > 0;
 }
 
 static int
-binop()
+binop(enum token n)
 {
-       const char *opnd1, *opnd2;
-       struct t_op const *op;
+       const char *opnd1, *op, *opnd2;
 
        opnd1 = *t_wp;
-       (void) t_lex(*++t_wp);
-       op = t_wp_op;
+       op = nargc > 0 ? (--nargc, *++t_wp) : NULL;
 
-       if ((opnd2 = *++t_wp) == (char *)0)
-               syntax(op->op_text, "argument expected");
-               
-       switch (op->op_num) {
+       if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL)
+               syntax(op, "argument expected");
+
+       switch (n) {
        case STREQ:
                return strcmp(opnd1, opnd2) == 0;
        case STRNE:
@@ -316,17 +352,17 @@ binop()
        case STRGT:
                return strcmp(opnd1, opnd2) > 0;
        case INTEQ:
-               return getn(opnd1) == getn(opnd2);
+               return intcmp(opnd1, opnd2) == 0;
        case INTNE:
-               return getn(opnd1) != getn(opnd2);
+               return intcmp(opnd1, opnd2) != 0;
        case INTGE:
-               return getn(opnd1) >= getn(opnd2);
+               return intcmp(opnd1, opnd2) >= 0;
        case INTGT:
-               return getn(opnd1) > getn(opnd2);
+               return intcmp(opnd1, opnd2) > 0;
        case INTLE:
-               return getn(opnd1) <= getn(opnd2);
+               return intcmp(opnd1, opnd2) <= 0;
        case INTLT:
-               return getn(opnd1) < getn(opnd2);
+               return intcmp(opnd1, opnd2) < 0;
        case FILNT:
                return newerf (opnd1, opnd2);
        case FILOT:
@@ -340,9 +376,7 @@ binop()
 }
 
 static int
-filstat(nm, mode)
-       char *nm;
-       enum token mode;
+filstat(char *nm, enum token mode)
 {
        struct stat s;
 
@@ -351,13 +385,18 @@ filstat(nm, mode)
 
        switch (mode) {
        case FILRD:
-               return access(nm, R_OK) == 0;
+               return (eaccess(nm, R_OK) == 0);
        case FILWR:
-               return access(nm, W_OK) == 0;
+               return (eaccess(nm, W_OK) == 0);
        case FILEX:
-               return access(nm, X_OK) == 0;
+               /* XXX work around eaccess(2) false positives for superuser */
+               if (eaccess(nm, X_OK) != 0)
+                       return 0;
+               if (S_ISDIR(s.st_mode) || geteuid() != 0)
+                       return 1;
+               return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;
        case FILEXIST:
-               return access(nm, F_OK) == 0;
+               return (eaccess(nm, F_OK) == 0);
        case FILREG:
                return S_ISREG(s.st_mode);
        case FILDIR:
@@ -390,30 +429,87 @@ filstat(nm, mode)
 }
 
 static enum token
-t_lex(s)
-       char *s;
+t_lex(char *s)
 {
        struct t_op const *op = ops;
 
        if (s == 0) {
-               t_wp_op = (struct t_op *)0;
                return EOI;
        }
-       while (op->op_text) {
+       while (*op->op_text) {
                if (strcmp(s, op->op_text) == 0) {
-                       t_wp_op = op;
+                       if (((TOKEN_TYPE(op->op_num) == UNOP ||
+                           TOKEN_TYPE(op->op_num) == BUNOP)
+                                               && isunopoperand()) ||
+                           (op->op_num == LPAREN && islparenoperand()) ||
+                           (op->op_num == RPAREN && isrparenoperand()))
+                               break;
                        return op->op_num;
                }
                op++;
        }
-       t_wp_op = (struct t_op *)0;
        return OPERAND;
 }
 
+static int
+isunopoperand(void)
+{
+       struct t_op const *op = ops;
+       char *s;
+       char *t;
+
+       if (nargc == 1)
+               return 1;
+       s = *(t_wp + 1);
+       if (nargc == 2)
+               return parenlevel == 1 && strcmp(s, ")") == 0;
+       t = *(t_wp + 2);
+       while (*op->op_text) {
+               if (strcmp(s, op->op_text) == 0)
+                       return TOKEN_TYPE(op->op_num) == BINOP &&
+                           (parenlevel == 0 || t[0] != ')' || t[1] != '\0');
+               op++;
+       }
+       return 0;
+}
+
+static int
+islparenoperand(void)
+{
+       struct t_op const *op = ops;
+       char *s;
+
+       if (nargc == 1)
+               return 1;
+       s = *(t_wp + 1);
+       if (nargc == 2)
+               return parenlevel == 1 && strcmp(s, ")") == 0;
+       if (nargc != 3)
+               return 0;
+       while (*op->op_text) {
+               if (strcmp(s, op->op_text) == 0)
+                       return TOKEN_TYPE(op->op_num) == BINOP;
+               op++;
+       }
+       return 0;
+}
+
+static int
+isrparenoperand(void)
+{
+       char *s;
+
+       if (nargc == 1)
+               return 0;
+       s = *(t_wp + 1);
+       if (nargc == 2)
+               return parenlevel == 1 && strcmp(s, ")") == 0;
+       return 0;
+}
+
 /* atoi with error detection */
 static int
-getn(s)
-       const char *s;
+getn(const char *s)
 {
        char *p;
        long r;
@@ -421,43 +517,90 @@ getn(s)
        errno = 0;
        r = strtol(s, &p, 10);
 
+       if (s == p)
+               error("%s: bad number", s);
+
        if (errno != 0)
-         errx(2, "%s: out of range", s);
+               error((errno == EINVAL) ? "%s: bad number" :
+                                         "%s: out of range", s);
+
+       while (isspace((unsigned char)*p))
+               p++;
 
-       while (isspace(*p))
-         p++;
-       
        if (*p)
-         errx(2, "%s: bad number", s);
+               error("%s: bad number", s);
 
        return (int) r;
 }
 
+/* atoi with error detection and 64 bit range */
+static intmax_t
+getq(const char *s)
+{
+       char *p;
+       intmax_t r;
+
+       errno = 0;
+       r = strtoimax(s, &p, 10);
+
+       if (s == p)
+               error("%s: bad number", s);
+
+       if (errno != 0)
+               error((errno == EINVAL) ? "%s: bad number" :
+                                         "%s: out of range", s);
+
+       while (isspace((unsigned char)*p))
+               p++;
+
+       if (*p)
+               error("%s: bad number", s);
+
+       return r;
+}
+
 static int
-newerf (f1, f2)
-const char *f1, *f2;
+intcmp (const char *s1, const char *s2)
 {
-       struct stat b1, b2;
+       intmax_t q1, q2;
 
-       return (stat (f1, &b1) == 0 &&
-               stat (f2, &b2) == 0 &&
-               b1.st_mtime > b2.st_mtime);
+
+       q1 = getq(s1);
+       q2 = getq(s2);
+
+       if (q1 > q2)
+               return 1;
+
+       if (q1 < q2)
+               return -1;
+
+       return 0;
 }
 
 static int
-olderf (f1, f2)
-const char *f1, *f2;
+newerf (const char *f1, const char *f2)
 {
        struct stat b1, b2;
 
-       return (stat (f1, &b1) == 0 &&
-               stat (f2, &b2) == 0 &&
-               b1.st_mtime < b2.st_mtime);
+       if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0)
+               return 0;
+
+       if (b1.st_mtim.tv_sec > b2.st_mtim.tv_sec)
+               return 1;
+       if (b1.st_mtim.tv_sec < b2.st_mtim.tv_sec)
+               return 0;
+
+       return (b1.st_mtim.tv_nsec > b2.st_mtim.tv_nsec);
+}
+
+static int
+olderf (const char *f1, const char *f2)
+{
+       return (newerf(f2, f1));
 }
 
 static int
-equalf (f1, f2)
-const char *f1, *f2;
+equalf (const char *f1, const char *f2)
 {
        struct stat b1, b2;
 
diff --git a/test/test.plist.part b/test/test.plist.part
new file mode 100644 (file)
index 0000000..e645422
--- /dev/null
@@ -0,0 +1,19 @@
+       <dict>
+               <key>OpenSourceProject</key>
+               <string>test</string>
+               <key>OpenSourceVersion</key>
+               <string>2013-12-05</string>
+               <key>OpenSourceWebsiteURL</key>
+               <string>http://svnweb.freebsd.org/base/head/bin/test/</string>
+               <key>OpenSourceSCM</key>
+               <string>svn co http://svn.freebsd.org/base/head/bin/test/</string>
+               <key>OpenSourceImportDate</key>
+               <string>2015-07-31</string>
+               <key>OpenSourceModifications</key>
+               <array>
+                   <string>4689479</string>
+                   <string>compatibility for eaccess, st_mtim</string>
+               </array>
+               <key>OpenSourceLicense</key>
+               <string>bsd</string>
+       </dict>
diff --git a/test/tests/Makefile b/test/tests/Makefile
new file mode 100644 (file)
index 0000000..5ee337a
--- /dev/null
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+.include <bsd.own.mk>
+
+TESTSDIR=      ${TESTSBASE}/bin/test
+
+TAP_TESTS_SH=  legacy_test
+# Some tests in here are silently not run when the tests are executed as
+# root.  Explicitly tell Kyua to drop privileges.
+#
+# TODO(jmmv): Kyua needs to do this by default, not only when explicitly
+# requested.  See https://code.google.com/p/kyua/issues/detail?id=6
+TEST_METADATA.legacy_test+= required_user="unprivileged"
+
+.include <bsd.test.mk>
diff --git a/test/tests/legacy_test.sh b/test/tests/legacy_test.sh
new file mode 100644 (file)
index 0000000..9229551
--- /dev/null
@@ -0,0 +1,196 @@
+#!/bin/sh
+
+#-
+# Copyright (c) June 1996 Wolfram Schneider <wosch@FreeBSD.org>. Berlin.
+# All rights reserved. 
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+#
+# TEST.sh - check if test(1) or builtin test works
+#
+# $FreeBSD$
+
+# force a specified test program, e.g. `env test=/bin/test sh regress.sh'
+: ${test=test}         
+
+t ()
+{
+       # $1 -> exit code
+       # $2 -> $test expression
+
+       count=$((count+1))
+       # check for syntax errors
+       syntax="`eval $test $2 2>&1`"
+       ret=$?
+       if test -n "$syntax"; then
+               printf "not ok %s - (syntax error)\n" "$count $2"
+       elif [ "$ret" != "$1" ]; then
+               printf "not ok %s - (got $ret, expected $1)\n" "$count $2"
+       else
+               printf "ok %s\n" "$count $2"
+       fi
+}
+
+count=0
+echo "1..130"
+
+t 0 'b = b' 
+t 0 'b == b' 
+t 1 'b != b' 
+t 0 '\( b = b \)' 
+t 0 '\( b == b \)' 
+t 1 '! \( b = b \)' 
+t 1 '! \( b == b \)' 
+t 1 '! -f /etc/passwd'
+
+t 0 '-h = -h'
+t 0 '-o = -o'
+t 1 '-f = h'
+t 1 '-h = f'
+t 1 '-o = f'
+t 1 'f = -o'
+t 0 '\( -h = -h \)'
+t 1 '\( a = -h \)'
+t 1 '\( -f = h \)'
+t 0 '-h = -h -o a'
+t 0 '\( -h = -h \) -o 1'
+t 0 '-h = -h -o -h = -h'
+t 0 '\( -h = -h \) -o \( -h = -h \)'
+t 0 'roedelheim = roedelheim'
+t 1 'potsdam = berlin-dahlem'
+
+t 0 '-d /'
+t 0 '-d / -a a != b'
+t 1 '-z "-z"'
+t 0 '-n -n'
+
+t 0 '0'
+t 0 '\( 0 \)'
+t 0 '-E'
+t 0 '-X -a -X'
+t 0 '-XXX'
+t 0 '\( -E \)'
+t 0 'true -o X'
+t 0 'true -o -X'
+t 0 '\( \( \( a = a \) -o 1 \) -a 1 \) -a true'
+t 1 '-h /'
+t 0 '-r /'
+t 1 '-w /'
+t 0 '-x /bin/sh'
+t 0 '-c /dev/null'
+t 0 '-f /etc/passwd'
+t 0 '-s /etc/passwd'
+
+t 1 '! \( 700 -le 1000 -a -n "1" -a "20" = "20" \)'
+t 0 '100 -eq 100'
+t 0 '100 -lt 200'
+t 1 '1000 -lt 200'
+t 0 '1000 -gt 200'
+t 0 '1000 -ge 200'
+t 0 '1000 -ge 1000'
+t 1 '2 -ne 2'
+t 0 '0 -eq 0'
+t 1 '-5 -eq 5'
+t 0 '\( 0 -eq 0 \)'
+t 1 '1 -eq 0 -o a = a -a 1 -eq 0 -o a = aa'
+
+t 1 '"" -o ""'
+t 1 '"" -a ""'
+t 1 '"a" -a ""'
+t 0 '"a" -a ! ""'
+t 1 '""'
+t 0 '! ""'
+
+t 0 '!'
+t 0 '\('
+t 0 '\)'
+
+t 1 '\( = \)'
+t 0 '\( != \)'
+t 0 '\( ! \)'
+t 0 '\( \( \)'
+t 0 '\( \) \)'
+t 0 '! = !'
+t 1 '! != !'
+t 1 '-n = \)'
+t 0 '! != \)'
+t 1 '! = a'
+t 0 '! != -n'
+t 0 '! -c /etc/passwd'
+
+t 1 '! = = ='
+t 0 '! = = \)'
+t 0 '! "" -o ""'
+t 1 '! "x" -o ""'
+t 1 '! "" -o "x"'
+t 1 '! "x" -o "x"'
+t 0 '\( -f /etc/passwd \)'
+t 0 '\( ! "" \)'
+t 1 '\( ! -e \)'
+
+t 0 '0 -eq 0 -a -d /'
+t 0 '-s = "" -o "" = ""'
+t 0 '"" = "" -o -s = ""'
+t 1 '-s = "" -o -s = ""'
+t 0 '-z x -o x = "#" -o x = x'
+t 1 '-z y -o y = "#" -o y = x'
+t 0 '0 -ne 0 -o ! -f /'
+t 0 '1 -ne 0 -o ! -f /etc/passwd'
+t 1 '0 -ne 0 -o ! -f /etc/passwd'
+
+t 0 '-n ='
+t 1 '-z ='
+t 1 '! ='
+t 0 '-n -eq'
+t 1 '-z -eq'
+t 1 '! -eq'
+t 0 '-n -a'
+t 1 '-z -a'
+t 1 '! -a'
+t 0 '-n -o'
+t 1 '-z -o'
+t 1 '! -o'
+t 1 '! -n ='
+t 0 '! -z ='
+t 0 '! ! ='
+t 1 '! -n -eq'
+t 0 '! -z -eq'
+t 0 '! ! -eq'
+t 1 '! -n -a'
+t 0 '! -z -a'
+t 0 '! ! -a'
+t 1 '! -n -o'
+t 0 '! -z -o'
+t 0 '! ! -o'
+t 0 '\( -n = \)'
+t 1 '\( -z = \)'
+t 1 '\( ! = \)'
+t 0 '\( -n -eq \)'
+t 1 '\( -z -eq \)'
+t 1 '\( ! -eq \)'
+t 0 '\( -n -a \)'
+t 1 '\( -z -a \)'
+t 1 '\( ! -a \)'
+t 0 '\( -n -o \)'
+t 1 '\( -z -o \)'
+t 1 '\( ! -o \)'
diff --git a/xcconfigs/base.xcconfig b/xcconfigs/base.xcconfig
new file mode 100644 (file)
index 0000000..72c716a
--- /dev/null
@@ -0,0 +1,59 @@
+// Build Options
+DEBUG_INFORMATION_FORMAT = dwarf-with-dsym
+
+// Code Signing
+CODE_SIGN_IDENTITY = -
+
+// Deployment
+COPY_PHASE_STRIP = YES
+
+// Packaging
+APPLY_RULES_IN_COPY_FILES = YES
+
+// Search Paths
+ALWAYS_SEARCH_USER_PATHS = NO
+USE_HEADERMAP = NO
+
+// Versioning
+CURRENT_PROJECT_VERSION = $(RC_ProjectSourceVersion)
+VERSIONING_SYSTEM = apple-generic
+VERSION_INFO_PREFIX = __
+
+// Warning Policies
+GCC_TREAT_WARNINGS_AS_ERRORS = YES
+
+// Warnings - All languages
+CLANG_WARN_ASSIGN_ENUM = YES
+CLANG_WARN_BOOL_CONVERSION = YES
+CLANG_WARN_CONSTANT_CONVERSION = YES
+CLANG_WARN_DOCUMENTATION_COMMENTS = YES
+CLANG_WARN_EMPTY_BODY = YES
+CLANG_WARN_ENUM_CONVERSION = YES
+CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES
+CLANG_WARN_INT_CONVERSION = YES
+CLANG_WARN_NULLABLE_TO_NONNULL_CONVERSION = YES
+CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES
+CLANG_WARN_UNREACHABLE_CODE = YES
+GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES
+GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES
+GCC_WARN_64_TO_32_BIT_CONVERSION = YES
+GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = YES
+GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES
+GCC_WARN_ABOUT_MISSING_NEWLINE = YES
+GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES
+GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES
+GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR
+GCC_WARN_CHECK_SWITCH_STATEMENTS = YES
+GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES
+GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES
+GCC_WARN_MISSING_PARENTHESES = YES
+GCC_WARN_SHADOW = YES
+GCC_WARN_SIGN_COMPARE = YES
+GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES
+GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE
+GCC_WARN_UNKNOWN_PRAGMAS = YES
+GCC_WARN_UNUSED_FUNCTION = YES
+GCC_WARN_UNUSED_LABEL = YES
+GCC_WARN_UNUSED_PARAMETER = YES
+GCC_WARN_UNUSED_VALUE = YES
+GCC_WARN_UNUSED_VARIABLE = YES
diff --git a/xcconfigs/sh.xcconfig b/xcconfigs/sh.xcconfig
new file mode 100644 (file)
index 0000000..f068b26
--- /dev/null
@@ -0,0 +1,33 @@
+#include "base.xcconfig"
+
+// TODO: change to /bin, sh, /usr
+SH_INSTALL_PATH = /usr/local/bin
+SH_PRODUCT_NAME = ash
+SH_MAN_PREFIX = /usr/local
+
+// Architectures
+SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx
+
+// Deployment
+INSTALL_PATH = $(SH_INSTALL_PATH)
+
+// Linking
+OTHER_LDFLAGS = -ledit
+
+// Packaging
+PRODUCT_NAME = $(SH_PRODUCT_NAME)
+
+// Search Paths
+USER_HEADER_SEARCH_PATHS = $(BUILT_PRODUCTS_DIR) $(SRCROOT)/sh
+
+// Preprocessing
+GCC_PREPROCESSOR_DEFINITIONS = SHELL
+
+// Warnings - All languages
+GCC_WARN_64_TO_32_BIT_CONVERSION = NO
+GCC_WARN_UNINITIALIZED_AUTOS = YES
+GCC_WARN_UNUSED_VARIABLE = NO
+CLANG_WARN_ASSIGN_ENUM = NO
+CLANG_WARN_EMPTY_BODY = NO
+CLANG_WARN_IMPLICIT_SIGN_CONVERSION = NO
+CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = NO
index ba69d19717a90323f268a289115a43c8c5d41ccb..ffc6f4edcae89e43fb46bfe6c3a7d92081dab1ab 100644 (file)
@@ -44,7 +44,6 @@ if.1
 jobid.1
 jobs.1
 limit.1
-log.1
 logout.1
 ls-F.1
 notify.1