aboutsummaryrefslogtreecommitdiff
path: root/bin/sh
diff options
context:
space:
mode:
Diffstat (limited to 'bin/sh')
-rw-r--r--bin/sh/Makefile70
-rw-r--r--bin/sh/Makefile.depend20
-rw-r--r--bin/sh/TOUR301
-rw-r--r--bin/sh/alias.c256
-rw-r--r--bin/sh/alias.h45
-rw-r--r--bin/sh/arith.h37
-rw-r--r--bin/sh/arith_yacc.c381
-rw-r--r--bin/sh/arith_yacc.h93
-rw-r--r--bin/sh/arith_yylex.c248
-rw-r--r--bin/sh/bltin/bltin.h79
-rw-r--r--bin/sh/bltin/echo.c107
-rw-r--r--bin/sh/builtins.def96
-rw-r--r--bin/sh/cd.c430
-rw-r--r--bin/sh/cd.h32
-rw-r--r--bin/sh/error.c199
-rw-r--r--bin/sh/error.h95
-rw-r--r--bin/sh/eval.c1382
-rw-r--r--bin/sh/eval.h70
-rw-r--r--bin/sh/exec.c776
-rw-r--r--bin/sh/exec.h77
-rw-r--r--bin/sh/expand.c1550
-rw-r--r--bin/sh/expand.h62
-rw-r--r--bin/sh/funcs/cmv49
-rw-r--r--bin/sh/funcs/dirs73
-rw-r--r--bin/sh/funcs/login38
-rw-r--r--bin/sh/funcs/newgrp37
-rw-r--r--bin/sh/funcs/popd73
-rw-r--r--bin/sh/funcs/pushd73
-rw-r--r--bin/sh/funcs/suspend39
-rw-r--r--bin/sh/histedit.c502
-rw-r--r--bin/sh/input.c518
-rw-r--r--bin/sh/input.h65
-rw-r--r--bin/sh/jobs.c1515
-rw-r--r--bin/sh/jobs.h100
-rw-r--r--bin/sh/mail.c117
-rw-r--r--bin/sh/mail.h36
-rw-r--r--bin/sh/main.c349
-rw-r--r--bin/sh/main.h40
-rw-r--r--bin/sh/memalloc.c342
-rw-r--r--bin/sh/memalloc.h86
-rw-r--r--bin/sh/miscbltin.c532
-rwxr-xr-xbin/sh/mkbuiltins137
-rw-r--r--bin/sh/mknodes.c459
-rw-r--r--bin/sh/mksyntax.c329
-rw-r--r--bin/sh/mktokens93
-rw-r--r--bin/sh/myhistedit.h42
-rw-r--r--bin/sh/mystring.c98
-rw-r--r--bin/sh/mystring.h41
-rw-r--r--bin/sh/nodes.c.pat193
-rw-r--r--bin/sh/nodetypes145
-rw-r--r--bin/sh/options.c592
-rw-r--r--bin/sh/options.h113
-rw-r--r--bin/sh/output.c368
-rw-r--r--bin/sh/output.h83
-rw-r--r--bin/sh/parser.c2118
-rw-r--r--bin/sh/parser.h85
-rw-r--r--bin/sh/redir.c363
-rw-r--r--bin/sh/redir.h45
-rw-r--r--bin/sh/sh.12879
-rw-r--r--bin/sh/shell.h77
-rw-r--r--bin/sh/show.c408
-rw-r--r--bin/sh/show.h40
-rw-r--r--bin/sh/tests/Makefile14
-rw-r--r--bin/sh/tests/Makefile.depend11
-rw-r--r--bin/sh/tests/builtins/Makefile185
-rw-r--r--bin/sh/tests/builtins/Makefile.depend11
-rw-r--r--bin/sh/tests/builtins/alias.09
-rw-r--r--bin/sh/tests/builtins/alias.0.stdout4
-rw-r--r--bin/sh/tests/builtins/alias.13
-rw-r--r--bin/sh/tests/builtins/alias.1.stderr1
-rw-r--r--bin/sh/tests/builtins/alias3.012
-rw-r--r--bin/sh/tests/builtins/alias3.0.stdout4
-rw-r--r--bin/sh/tests/builtins/alias4.04
-rw-r--r--bin/sh/tests/builtins/break1.016
-rw-r--r--bin/sh/tests/builtins/break2.012
-rw-r--r--bin/sh/tests/builtins/break2.0.stdout1
-rw-r--r--bin/sh/tests/builtins/break3.015
-rw-r--r--bin/sh/tests/builtins/break4.47
-rw-r--r--bin/sh/tests/builtins/break5.412
-rw-r--r--bin/sh/tests/builtins/break6.08
-rw-r--r--bin/sh/tests/builtins/builtin1.031
-rw-r--r--bin/sh/tests/builtins/case1.013
-rw-r--r--bin/sh/tests/builtins/case10.016
-rw-r--r--bin/sh/tests/builtins/case11.06
-rw-r--r--bin/sh/tests/builtins/case12.06
-rw-r--r--bin/sh/tests/builtins/case13.012
-rw-r--r--bin/sh/tests/builtins/case14.05
-rw-r--r--bin/sh/tests/builtins/case15.05
-rw-r--r--bin/sh/tests/builtins/case16.07
-rw-r--r--bin/sh/tests/builtins/case17.03
-rw-r--r--bin/sh/tests/builtins/case18.07
-rw-r--r--bin/sh/tests/builtins/case19.07
-rw-r--r--bin/sh/tests/builtins/case2.0106
-rw-r--r--bin/sh/tests/builtins/case20.09
-rw-r--r--bin/sh/tests/builtins/case21.010
-rw-r--r--bin/sh/tests/builtins/case22.010
-rw-r--r--bin/sh/tests/builtins/case3.095
-rw-r--r--bin/sh/tests/builtins/case4.06
-rw-r--r--bin/sh/tests/builtins/case5.057
-rw-r--r--bin/sh/tests/builtins/case6.052
-rw-r--r--bin/sh/tests/builtins/case7.024
-rw-r--r--bin/sh/tests/builtins/case8.032
-rw-r--r--bin/sh/tests/builtins/case9.039
-rw-r--r--bin/sh/tests/builtins/cd1.030
-rw-r--r--bin/sh/tests/builtins/cd10.06
-rw-r--r--bin/sh/tests/builtins/cd2.016
-rw-r--r--bin/sh/tests/builtins/cd3.021
-rw-r--r--bin/sh/tests/builtins/cd4.038
-rw-r--r--bin/sh/tests/builtins/cd5.023
-rw-r--r--bin/sh/tests/builtins/cd6.010
-rw-r--r--bin/sh/tests/builtins/cd7.015
-rw-r--r--bin/sh/tests/builtins/cd8.026
-rw-r--r--bin/sh/tests/builtins/cd9.08
-rw-r--r--bin/sh/tests/builtins/cd9.0.stdout2
-rw-r--r--bin/sh/tests/builtins/command1.05
-rw-r--r--bin/sh/tests/builtins/command10.014
-rw-r--r--bin/sh/tests/builtins/command11.014
-rw-r--r--bin/sh/tests/builtins/command12.07
-rw-r--r--bin/sh/tests/builtins/command2.03
-rw-r--r--bin/sh/tests/builtins/command3.014
-rw-r--r--bin/sh/tests/builtins/command3.0.stdout7
-rw-r--r--bin/sh/tests/builtins/command4.02
-rw-r--r--bin/sh/tests/builtins/command5.015
-rw-r--r--bin/sh/tests/builtins/command5.0.stdout8
-rw-r--r--bin/sh/tests/builtins/command6.022
-rw-r--r--bin/sh/tests/builtins/command6.0.stdout7
-rw-r--r--bin/sh/tests/builtins/command7.034
-rw-r--r--bin/sh/tests/builtins/command8.045
-rw-r--r--bin/sh/tests/builtins/command9.014
-rw-r--r--bin/sh/tests/builtins/dot1.021
-rw-r--r--bin/sh/tests/builtins/dot2.021
-rw-r--r--bin/sh/tests/builtins/dot3.010
-rw-r--r--bin/sh/tests/builtins/dot4.012
-rw-r--r--bin/sh/tests/builtins/echo1.06
-rw-r--r--bin/sh/tests/builtins/echo2.07
-rw-r--r--bin/sh/tests/builtins/echo3.05
-rw-r--r--bin/sh/tests/builtins/eval1.09
-rw-r--r--bin/sh/tests/builtins/eval2.07
-rw-r--r--bin/sh/tests/builtins/eval3.09
-rw-r--r--bin/sh/tests/builtins/eval4.05
-rw-r--r--bin/sh/tests/builtins/eval5.04
-rw-r--r--bin/sh/tests/builtins/eval6.05
-rw-r--r--bin/sh/tests/builtins/eval7.09
-rw-r--r--bin/sh/tests/builtins/eval8.77
-rw-r--r--bin/sh/tests/builtins/exec1.025
-rw-r--r--bin/sh/tests/builtins/exec2.025
-rw-r--r--bin/sh/tests/builtins/exit1.06
-rw-r--r--bin/sh/tests/builtins/exit2.87
-rw-r--r--bin/sh/tests/builtins/exit3.05
-rw-r--r--bin/sh/tests/builtins/export1.03
-rw-r--r--bin/sh/tests/builtins/fc1.027
-rw-r--r--bin/sh/tests/builtins/fc2.034
-rw-r--r--bin/sh/tests/builtins/for1.04
-rw-r--r--bin/sh/tests/builtins/for2.09
-rw-r--r--bin/sh/tests/builtins/for3.08
-rw-r--r--bin/sh/tests/builtins/getopts1.025
-rw-r--r--bin/sh/tests/builtins/getopts1.0.stdout8
-rw-r--r--bin/sh/tests/builtins/getopts10.011
-rw-r--r--bin/sh/tests/builtins/getopts2.06
-rw-r--r--bin/sh/tests/builtins/getopts2.0.stdout1
-rw-r--r--bin/sh/tests/builtins/getopts3.06
-rw-r--r--bin/sh/tests/builtins/getopts4.010
-rw-r--r--bin/sh/tests/builtins/getopts5.010
-rw-r--r--bin/sh/tests/builtins/getopts6.07
-rw-r--r--bin/sh/tests/builtins/getopts7.06
-rw-r--r--bin/sh/tests/builtins/getopts8.08
-rw-r--r--bin/sh/tests/builtins/getopts8.0.stdout5
-rw-r--r--bin/sh/tests/builtins/getopts9.09
-rw-r--r--bin/sh/tests/builtins/getopts9.0.stdout3
-rw-r--r--bin/sh/tests/builtins/hash1.05
-rw-r--r--bin/sh/tests/builtins/hash1.0.stdout1
-rw-r--r--bin/sh/tests/builtins/hash2.04
-rw-r--r--bin/sh/tests/builtins/hash2.0.stdout1
-rw-r--r--bin/sh/tests/builtins/hash3.03
-rw-r--r--bin/sh/tests/builtins/hash3.0.stdout2
-rw-r--r--bin/sh/tests/builtins/hash4.06
-rw-r--r--bin/sh/tests/builtins/jobid1.07
-rw-r--r--bin/sh/tests/builtins/jobid2.09
-rw-r--r--bin/sh/tests/builtins/kill1.08
-rw-r--r--bin/sh/tests/builtins/kill2.07
-rw-r--r--bin/sh/tests/builtins/lineno.016
-rw-r--r--bin/sh/tests/builtins/lineno.0.stdout9
-rw-r--r--bin/sh/tests/builtins/lineno2.010
-rw-r--r--bin/sh/tests/builtins/lineno3.06
-rw-r--r--bin/sh/tests/builtins/lineno3.0.stdout2
-rw-r--r--bin/sh/tests/builtins/local1.013
-rw-r--r--bin/sh/tests/builtins/local2.017
-rw-r--r--bin/sh/tests/builtins/local3.026
-rw-r--r--bin/sh/tests/builtins/local4.012
-rw-r--r--bin/sh/tests/builtins/local5.015
-rw-r--r--bin/sh/tests/builtins/local6.010
-rw-r--r--bin/sh/tests/builtins/local7.010
-rw-r--r--bin/sh/tests/builtins/locale1.0134
-rw-r--r--bin/sh/tests/builtins/locale2.05
-rw-r--r--bin/sh/tests/builtins/printf1.03
-rw-r--r--bin/sh/tests/builtins/printf2.03
-rw-r--r--bin/sh/tests/builtins/printf3.05
-rw-r--r--bin/sh/tests/builtins/printf4.05
-rw-r--r--bin/sh/tests/builtins/read1.026
-rw-r--r--bin/sh/tests/builtins/read1.0.stdout20
-rw-r--r--bin/sh/tests/builtins/read2.031
-rw-r--r--bin/sh/tests/builtins/read3.011
-rw-r--r--bin/sh/tests/builtins/read3.0.stdout9
-rw-r--r--bin/sh/tests/builtins/read4.010
-rw-r--r--bin/sh/tests/builtins/read4.0.stdout8
-rw-r--r--bin/sh/tests/builtins/read5.032
-rw-r--r--bin/sh/tests/builtins/read6.05
-rw-r--r--bin/sh/tests/builtins/read7.05
-rw-r--r--bin/sh/tests/builtins/read8.017
-rw-r--r--bin/sh/tests/builtins/read9.010
-rw-r--r--bin/sh/tests/builtins/return1.07
-rw-r--r--bin/sh/tests/builtins/return2.17
-rw-r--r--bin/sh/tests/builtins/return3.13
-rw-r--r--bin/sh/tests/builtins/return4.016
-rw-r--r--bin/sh/tests/builtins/return5.017
-rw-r--r--bin/sh/tests/builtins/return6.43
-rw-r--r--bin/sh/tests/builtins/return7.46
-rw-r--r--bin/sh/tests/builtins/return8.013
-rw-r--r--bin/sh/tests/builtins/set1.032
-rw-r--r--bin/sh/tests/builtins/set2.03
-rw-r--r--bin/sh/tests/builtins/set3.04
-rw-r--r--bin/sh/tests/builtins/trap1.022
-rw-r--r--bin/sh/tests/builtins/trap10.06
-rw-r--r--bin/sh/tests/builtins/trap11.08
-rw-r--r--bin/sh/tests/builtins/trap12.010
-rw-r--r--bin/sh/tests/builtins/trap13.08
-rw-r--r--bin/sh/tests/builtins/trap14.010
-rw-r--r--bin/sh/tests/builtins/trap15.05
-rw-r--r--bin/sh/tests/builtins/trap16.020
-rw-r--r--bin/sh/tests/builtins/trap17.010
-rw-r--r--bin/sh/tests/builtins/trap2.052
-rw-r--r--bin/sh/tests/builtins/trap3.011
-rw-r--r--bin/sh/tests/builtins/trap4.017
-rw-r--r--bin/sh/tests/builtins/trap5.019
-rw-r--r--bin/sh/tests/builtins/trap6.09
-rw-r--r--bin/sh/tests/builtins/trap7.03
-rw-r--r--bin/sh/tests/builtins/trap8.07
-rw-r--r--bin/sh/tests/builtins/trap9.03
-rw-r--r--bin/sh/tests/builtins/type1.08
-rw-r--r--bin/sh/tests/builtins/type1.0.stderr4
-rw-r--r--bin/sh/tests/builtins/type2.026
-rw-r--r--bin/sh/tests/builtins/type3.03
-rw-r--r--bin/sh/tests/builtins/unalias.021
-rw-r--r--bin/sh/tests/builtins/var-assign.055
-rw-r--r--bin/sh/tests/builtins/var-assign2.055
-rw-r--r--bin/sh/tests/builtins/wait1.023
-rw-r--r--bin/sh/tests/builtins/wait10.05
-rw-r--r--bin/sh/tests/builtins/wait2.015
-rw-r--r--bin/sh/tests/builtins/wait3.021
-rw-r--r--bin/sh/tests/builtins/wait4.012
-rw-r--r--bin/sh/tests/builtins/wait5.012
-rw-r--r--bin/sh/tests/builtins/wait6.03
-rw-r--r--bin/sh/tests/builtins/wait7.04
-rw-r--r--bin/sh/tests/builtins/wait8.07
-rw-r--r--bin/sh/tests/builtins/wait9.1273
-rw-r--r--bin/sh/tests/errors/Makefile35
-rw-r--r--bin/sh/tests/errors/Makefile.depend11
-rw-r--r--bin/sh/tests/errors/assignment-error1.030
-rw-r--r--bin/sh/tests/errors/assignment-error2.08
-rw-r--r--bin/sh/tests/errors/backquote-error1.04
-rw-r--r--bin/sh/tests/errors/backquote-error2.07
-rw-r--r--bin/sh/tests/errors/bad-binary1.12612
-rw-r--r--bin/sh/tests/errors/bad-keyword1.04
-rw-r--r--bin/sh/tests/errors/bad-parm-exp1.07
-rw-r--r--bin/sh/tests/errors/bad-parm-exp2.22
-rw-r--r--bin/sh/tests/errors/bad-parm-exp2.2.stderr1
-rw-r--r--bin/sh/tests/errors/bad-parm-exp3.22
-rw-r--r--bin/sh/tests/errors/bad-parm-exp3.2.stderr1
-rw-r--r--bin/sh/tests/errors/bad-parm-exp4.22
-rw-r--r--bin/sh/tests/errors/bad-parm-exp4.2.stderr1
-rw-r--r--bin/sh/tests/errors/bad-parm-exp5.22
-rw-r--r--bin/sh/tests/errors/bad-parm-exp5.2.stderr1
-rw-r--r--bin/sh/tests/errors/bad-parm-exp6.22
-rw-r--r--bin/sh/tests/errors/bad-parm-exp6.2.stderr1
-rw-r--r--bin/sh/tests/errors/bad-parm-exp7.04
-rw-r--r--bin/sh/tests/errors/bad-parm-exp8.04
-rw-r--r--bin/sh/tests/errors/option-error.046
-rw-r--r--bin/sh/tests/errors/redirection-error.053
-rw-r--r--bin/sh/tests/errors/redirection-error2.24
-rw-r--r--bin/sh/tests/errors/redirection-error3.054
-rw-r--r--bin/sh/tests/errors/redirection-error4.07
-rw-r--r--bin/sh/tests/errors/redirection-error5.05
-rw-r--r--bin/sh/tests/errors/redirection-error6.012
-rw-r--r--bin/sh/tests/errors/redirection-error7.07
-rw-r--r--bin/sh/tests/errors/redirection-error8.05
-rw-r--r--bin/sh/tests/errors/write-error1.03
-rw-r--r--bin/sh/tests/execution/Makefile57
-rw-r--r--bin/sh/tests/execution/Makefile.depend11
-rw-r--r--bin/sh/tests/execution/bg1.03
-rw-r--r--bin/sh/tests/execution/bg10.04
-rw-r--r--bin/sh/tests/execution/bg10.0.stdout1
-rw-r--r--bin/sh/tests/execution/bg2.05
-rw-r--r--bin/sh/tests/execution/bg3.05
-rw-r--r--bin/sh/tests/execution/bg4.06
-rw-r--r--bin/sh/tests/execution/bg5.04
-rw-r--r--bin/sh/tests/execution/bg6.04
-rw-r--r--bin/sh/tests/execution/bg6.0.stdout1
-rw-r--r--bin/sh/tests/execution/bg7.05
-rw-r--r--bin/sh/tests/execution/bg8.05
-rw-r--r--bin/sh/tests/execution/bg9.05
-rw-r--r--bin/sh/tests/execution/fork1.010
-rw-r--r--bin/sh/tests/execution/fork2.09
-rw-r--r--bin/sh/tests/execution/fork3.04
-rw-r--r--bin/sh/tests/execution/func1.04
-rw-r--r--bin/sh/tests/execution/func2.012
-rw-r--r--bin/sh/tests/execution/func3.07
-rw-r--r--bin/sh/tests/execution/hash1.012
-rw-r--r--bin/sh/tests/execution/int-cmd1.03
-rw-r--r--bin/sh/tests/execution/killed1.08
-rw-r--r--bin/sh/tests/execution/killed2.010
-rw-r--r--bin/sh/tests/execution/not1.04
-rw-r--r--bin/sh/tests/execution/not2.06
-rw-r--r--bin/sh/tests/execution/path1.015
-rw-r--r--bin/sh/tests/execution/redir1.027
-rw-r--r--bin/sh/tests/execution/redir2.029
-rw-r--r--bin/sh/tests/execution/redir3.03
-rw-r--r--bin/sh/tests/execution/redir4.04
-rw-r--r--bin/sh/tests/execution/redir5.03
-rw-r--r--bin/sh/tests/execution/redir6.021
-rw-r--r--bin/sh/tests/execution/redir7.021
-rw-r--r--bin/sh/tests/execution/set-C1.012
-rw-r--r--bin/sh/tests/execution/set-n1.07
-rw-r--r--bin/sh/tests/execution/set-n2.05
-rw-r--r--bin/sh/tests/execution/set-n3.04
-rw-r--r--bin/sh/tests/execution/set-n4.03
-rw-r--r--bin/sh/tests/execution/set-x1.08
-rw-r--r--bin/sh/tests/execution/set-x2.09
-rw-r--r--bin/sh/tests/execution/set-x3.09
-rw-r--r--bin/sh/tests/execution/set-x4.07
-rw-r--r--bin/sh/tests/execution/shellproc1.011
-rw-r--r--bin/sh/tests/execution/subshell1.06
-rw-r--r--bin/sh/tests/execution/subshell1.0.stdout2
-rw-r--r--bin/sh/tests/execution/subshell2.010
-rw-r--r--bin/sh/tests/execution/subshell3.04
-rw-r--r--bin/sh/tests/execution/subshell4.03
-rw-r--r--bin/sh/tests/execution/unknown1.029
-rw-r--r--bin/sh/tests/execution/var-assign1.03
-rw-r--r--bin/sh/tests/expansion/Makefile105
-rw-r--r--bin/sh/tests/expansion/Makefile.depend11
-rw-r--r--bin/sh/tests/expansion/arith1.030
-rw-r--r--bin/sh/tests/expansion/arith10.035
-rw-r--r--bin/sh/tests/expansion/arith11.012
-rw-r--r--bin/sh/tests/expansion/arith12.04
-rw-r--r--bin/sh/tests/expansion/arith13.06
-rw-r--r--bin/sh/tests/expansion/arith14.040
-rw-r--r--bin/sh/tests/expansion/arith2.077
-rw-r--r--bin/sh/tests/expansion/arith3.014
-rw-r--r--bin/sh/tests/expansion/arith4.020
-rw-r--r--bin/sh/tests/expansion/arith5.017
-rw-r--r--bin/sh/tests/expansion/arith6.016
-rw-r--r--bin/sh/tests/expansion/arith7.012
-rw-r--r--bin/sh/tests/expansion/arith8.04
-rw-r--r--bin/sh/tests/expansion/arith9.020
-rw-r--r--bin/sh/tests/expansion/assign1.037
-rw-r--r--bin/sh/tests/expansion/cmdsubst1.048
-rw-r--r--bin/sh/tests/expansion/cmdsubst10.051
-rw-r--r--bin/sh/tests/expansion/cmdsubst11.05
-rw-r--r--bin/sh/tests/expansion/cmdsubst12.06
-rw-r--r--bin/sh/tests/expansion/cmdsubst13.012
-rw-r--r--bin/sh/tests/expansion/cmdsubst14.05
-rw-r--r--bin/sh/tests/expansion/cmdsubst15.05
-rw-r--r--bin/sh/tests/expansion/cmdsubst16.05
-rw-r--r--bin/sh/tests/expansion/cmdsubst17.05
-rw-r--r--bin/sh/tests/expansion/cmdsubst18.06
-rw-r--r--bin/sh/tests/expansion/cmdsubst19.05
-rw-r--r--bin/sh/tests/expansion/cmdsubst2.043
-rw-r--r--bin/sh/tests/expansion/cmdsubst20.06
-rw-r--r--bin/sh/tests/expansion/cmdsubst21.06
-rw-r--r--bin/sh/tests/expansion/cmdsubst22.06
-rw-r--r--bin/sh/tests/expansion/cmdsubst23.05
-rw-r--r--bin/sh/tests/expansion/cmdsubst24.024
-rw-r--r--bin/sh/tests/expansion/cmdsubst25.07
-rw-r--r--bin/sh/tests/expansion/cmdsubst26.06
-rw-r--r--bin/sh/tests/expansion/cmdsubst3.023
-rw-r--r--bin/sh/tests/expansion/cmdsubst4.04
-rw-r--r--bin/sh/tests/expansion/cmdsubst5.05
-rw-r--r--bin/sh/tests/expansion/cmdsubst6.053
-rw-r--r--bin/sh/tests/expansion/cmdsubst7.031
-rw-r--r--bin/sh/tests/expansion/cmdsubst8.017
-rw-r--r--bin/sh/tests/expansion/cmdsubst9.011
-rw-r--r--bin/sh/tests/expansion/export1.013
-rw-r--r--bin/sh/tests/expansion/export2.024
-rw-r--r--bin/sh/tests/expansion/export3.030
-rw-r--r--bin/sh/tests/expansion/heredoc1.025
-rw-r--r--bin/sh/tests/expansion/heredoc2.015
-rw-r--r--bin/sh/tests/expansion/ifs1.035
-rw-r--r--bin/sh/tests/expansion/ifs2.024
-rw-r--r--bin/sh/tests/expansion/ifs3.021
-rw-r--r--bin/sh/tests/expansion/ifs4.039
-rw-r--r--bin/sh/tests/expansion/ifs5.04
-rw-r--r--bin/sh/tests/expansion/ifs6.06
-rw-r--r--bin/sh/tests/expansion/ifs7.05
-rw-r--r--bin/sh/tests/expansion/length1.012
-rw-r--r--bin/sh/tests/expansion/length2.04
-rw-r--r--bin/sh/tests/expansion/length3.010
-rw-r--r--bin/sh/tests/expansion/length4.011
-rw-r--r--bin/sh/tests/expansion/length5.027
-rw-r--r--bin/sh/tests/expansion/length6.08
-rw-r--r--bin/sh/tests/expansion/length7.014
-rw-r--r--bin/sh/tests/expansion/length8.014
-rw-r--r--bin/sh/tests/expansion/local1.028
-rw-r--r--bin/sh/tests/expansion/local2.034
-rw-r--r--bin/sh/tests/expansion/pathname1.065
-rw-r--r--bin/sh/tests/expansion/pathname2.035
-rw-r--r--bin/sh/tests/expansion/pathname3.029
-rw-r--r--bin/sh/tests/expansion/pathname4.028
-rw-r--r--bin/sh/tests/expansion/pathname5.03
-rw-r--r--bin/sh/tests/expansion/pathname6.029
-rw-r--r--bin/sh/tests/expansion/plus-minus1.076
-rw-r--r--bin/sh/tests/expansion/plus-minus2.04
-rw-r--r--bin/sh/tests/expansion/plus-minus3.044
-rw-r--r--bin/sh/tests/expansion/plus-minus4.038
-rw-r--r--bin/sh/tests/expansion/plus-minus5.031
-rw-r--r--bin/sh/tests/expansion/plus-minus6.034
-rw-r--r--bin/sh/tests/expansion/plus-minus7.026
-rw-r--r--bin/sh/tests/expansion/plus-minus8.05
-rw-r--r--bin/sh/tests/expansion/question1.022
-rw-r--r--bin/sh/tests/expansion/readonly1.07
-rw-r--r--bin/sh/tests/expansion/redir1.026
-rw-r--r--bin/sh/tests/expansion/set-u1.029
-rw-r--r--bin/sh/tests/expansion/set-u2.012
-rw-r--r--bin/sh/tests/expansion/set-u3.06
-rw-r--r--bin/sh/tests/expansion/tilde1.056
-rw-r--r--bin/sh/tests/expansion/tilde2.090
-rw-r--r--bin/sh/tests/expansion/trim1.085
-rw-r--r--bin/sh/tests/expansion/trim2.055
-rw-r--r--bin/sh/tests/expansion/trim3.046
-rw-r--r--bin/sh/tests/expansion/trim4.015
-rw-r--r--bin/sh/tests/expansion/trim5.028
-rw-r--r--bin/sh/tests/expansion/trim6.022
-rw-r--r--bin/sh/tests/expansion/trim7.016
-rw-r--r--bin/sh/tests/expansion/trim8.075
-rw-r--r--bin/sh/tests/expansion/trim9.061
-rwxr-xr-xbin/sh/tests/functional_test.sh72
-rw-r--r--bin/sh/tests/invocation/Makefile16
-rw-r--r--bin/sh/tests/invocation/sh-ac1.07
-rw-r--r--bin/sh/tests/invocation/sh-c-missing1.03
-rw-r--r--bin/sh/tests/invocation/sh-c1.04
-rw-r--r--bin/sh/tests/invocation/sh-ca1.07
-rw-r--r--bin/sh/tests/invocation/sh-fca1.07
-rw-r--r--bin/sh/tests/parameters/Makefile29
-rw-r--r--bin/sh/tests/parameters/Makefile.depend11
-rw-r--r--bin/sh/tests/parameters/env1.011
-rw-r--r--bin/sh/tests/parameters/exitstatus1.09
-rw-r--r--bin/sh/tests/parameters/ifs1.010
-rw-r--r--bin/sh/tests/parameters/mail1.015
-rw-r--r--bin/sh/tests/parameters/mail2.015
-rw-r--r--bin/sh/tests/parameters/optind1.03
-rw-r--r--bin/sh/tests/parameters/optind2.03
-rw-r--r--bin/sh/tests/parameters/positional1.013
-rw-r--r--bin/sh/tests/parameters/positional2.065
-rw-r--r--bin/sh/tests/parameters/positional3.04
-rw-r--r--bin/sh/tests/parameters/positional4.04
-rw-r--r--bin/sh/tests/parameters/positional5.014
-rw-r--r--bin/sh/tests/parameters/positional6.07
-rw-r--r--bin/sh/tests/parameters/positional7.08
-rw-r--r--bin/sh/tests/parameters/positional8.031
-rw-r--r--bin/sh/tests/parameters/positional9.018
-rw-r--r--bin/sh/tests/parameters/pwd1.011
-rw-r--r--bin/sh/tests/parameters/pwd2.024
-rw-r--r--bin/sh/tests/parser/Makefile88
-rw-r--r--bin/sh/tests/parser/Makefile.depend11
-rw-r--r--bin/sh/tests/parser/alias1.05
-rw-r--r--bin/sh/tests/parser/alias10.09
-rw-r--r--bin/sh/tests/parser/alias11.06
-rw-r--r--bin/sh/tests/parser/alias12.06
-rw-r--r--bin/sh/tests/parser/alias13.06
-rw-r--r--bin/sh/tests/parser/alias14.06
-rw-r--r--bin/sh/tests/parser/alias15.012
-rw-r--r--bin/sh/tests/parser/alias15.0.stdout4
-rw-r--r--bin/sh/tests/parser/alias16.07
-rw-r--r--bin/sh/tests/parser/alias17.07
-rw-r--r--bin/sh/tests/parser/alias18.08
-rw-r--r--bin/sh/tests/parser/alias2.06
-rw-r--r--bin/sh/tests/parser/alias3.06
-rw-r--r--bin/sh/tests/parser/alias4.05
-rw-r--r--bin/sh/tests/parser/alias5.05
-rw-r--r--bin/sh/tests/parser/alias6.06
-rw-r--r--bin/sh/tests/parser/alias7.04
-rw-r--r--bin/sh/tests/parser/alias8.04
-rw-r--r--bin/sh/tests/parser/alias9.06
-rw-r--r--bin/sh/tests/parser/and-pipe-not.02
-rw-r--r--bin/sh/tests/parser/case1.014
-rw-r--r--bin/sh/tests/parser/case2.032
-rw-r--r--bin/sh/tests/parser/comment1.03
-rw-r--r--bin/sh/tests/parser/comment2.424
-rw-r--r--bin/sh/tests/parser/dollar-quote1.012
-rw-r--r--bin/sh/tests/parser/dollar-quote10.010
-rw-r--r--bin/sh/tests/parser/dollar-quote11.08
-rw-r--r--bin/sh/tests/parser/dollar-quote12.07
-rw-r--r--bin/sh/tests/parser/dollar-quote13.08
-rw-r--r--bin/sh/tests/parser/dollar-quote2.05
-rw-r--r--bin/sh/tests/parser/dollar-quote3.022
-rw-r--r--bin/sh/tests/parser/dollar-quote4.019
-rw-r--r--bin/sh/tests/parser/dollar-quote5.012
-rw-r--r--bin/sh/tests/parser/dollar-quote6.05
-rw-r--r--bin/sh/tests/parser/dollar-quote7.06
-rw-r--r--bin/sh/tests/parser/dollar-quote8.011
-rw-r--r--bin/sh/tests/parser/dollar-quote9.08
-rw-r--r--bin/sh/tests/parser/empty-braces1.07
-rw-r--r--bin/sh/tests/parser/empty-cmd1.03
-rw-r--r--bin/sh/tests/parser/for1.029
-rw-r--r--bin/sh/tests/parser/for2.015
-rw-r--r--bin/sh/tests/parser/func1.025
-rw-r--r--bin/sh/tests/parser/func2.06
-rw-r--r--bin/sh/tests/parser/func3.06
-rw-r--r--bin/sh/tests/parser/heredoc1.085
-rw-r--r--bin/sh/tests/parser/heredoc10.049
-rw-r--r--bin/sh/tests/parser/heredoc11.026
-rw-r--r--bin/sh/tests/parser/heredoc12.047
-rw-r--r--bin/sh/tests/parser/heredoc13.021
-rw-r--r--bin/sh/tests/parser/heredoc2.039
-rw-r--r--bin/sh/tests/parser/heredoc3.07
-rw-r--r--bin/sh/tests/parser/heredoc4.044
-rw-r--r--bin/sh/tests/parser/heredoc5.056
-rw-r--r--bin/sh/tests/parser/heredoc6.05
-rw-r--r--bin/sh/tests/parser/heredoc7.019
-rw-r--r--bin/sh/tests/parser/heredoc8.020
-rw-r--r--bin/sh/tests/parser/heredoc9.058
-rw-r--r--bin/sh/tests/parser/line-cont1.016
-rw-r--r--bin/sh/tests/parser/line-cont10.018
-rw-r--r--bin/sh/tests/parser/line-cont11.023
-rw-r--r--bin/sh/tests/parser/line-cont2.04
-rw-r--r--bin/sh/tests/parser/line-cont3.07
-rw-r--r--bin/sh/tests/parser/line-cont4.08
-rw-r--r--bin/sh/tests/parser/line-cont5.014
-rw-r--r--bin/sh/tests/parser/line-cont6.023
-rw-r--r--bin/sh/tests/parser/line-cont7.07
-rw-r--r--bin/sh/tests/parser/line-cont8.06
-rw-r--r--bin/sh/tests/parser/line-cont9.06
-rw-r--r--bin/sh/tests/parser/no-space1.018
-rw-r--r--bin/sh/tests/parser/no-space2.07
-rw-r--r--bin/sh/tests/parser/nul1.012
-rw-r--r--bin/sh/tests/parser/only-redir1.03
-rw-r--r--bin/sh/tests/parser/only-redir2.02
-rw-r--r--bin/sh/tests/parser/only-redir3.02
-rw-r--r--bin/sh/tests/parser/only-redir4.02
-rw-r--r--bin/sh/tests/parser/pipe-not1.03
-rw-r--r--bin/sh/tests/parser/set-v1.08
-rw-r--r--bin/sh/tests/parser/set-v1.0.stderr5
-rw-r--r--bin/sh/tests/parser/var-assign1.019
-rw-r--r--bin/sh/tests/set-e/Makefile46
-rw-r--r--bin/sh/tests/set-e/Makefile.depend11
-rw-r--r--bin/sh/tests/set-e/and1.03
-rw-r--r--bin/sh/tests/set-e/and2.14
-rw-r--r--bin/sh/tests/set-e/and3.04
-rw-r--r--bin/sh/tests/set-e/and4.04
-rw-r--r--bin/sh/tests/set-e/background1.03
-rw-r--r--bin/sh/tests/set-e/cmd1.03
-rw-r--r--bin/sh/tests/set-e/cmd2.14
-rw-r--r--bin/sh/tests/set-e/elif1.07
-rw-r--r--bin/sh/tests/set-e/elif2.07
-rw-r--r--bin/sh/tests/set-e/eval1.03
-rw-r--r--bin/sh/tests/set-e/eval2.14
-rw-r--r--bin/sh/tests/set-e/for1.09
-rw-r--r--bin/sh/tests/set-e/func1.07
-rw-r--r--bin/sh/tests/set-e/func2.17
-rw-r--r--bin/sh/tests/set-e/if1.05
-rw-r--r--bin/sh/tests/set-e/if2.07
-rw-r--r--bin/sh/tests/set-e/if3.05
-rw-r--r--bin/sh/tests/set-e/not1.04
-rw-r--r--bin/sh/tests/set-e/not2.04
-rw-r--r--bin/sh/tests/set-e/or1.03
-rw-r--r--bin/sh/tests/set-e/or2.03
-rw-r--r--bin/sh/tests/set-e/or3.14
-rw-r--r--bin/sh/tests/set-e/pipe1.14
-rw-r--r--bin/sh/tests/set-e/pipe2.03
-rw-r--r--bin/sh/tests/set-e/return1.011
-rw-r--r--bin/sh/tests/set-e/semi1.14
-rw-r--r--bin/sh/tests/set-e/semi2.14
-rw-r--r--bin/sh/tests/set-e/subshell1.03
-rw-r--r--bin/sh/tests/set-e/subshell2.14
-rw-r--r--bin/sh/tests/set-e/until1.05
-rw-r--r--bin/sh/tests/set-e/until2.05
-rw-r--r--bin/sh/tests/set-e/until3.09
-rw-r--r--bin/sh/tests/set-e/while1.05
-rw-r--r--bin/sh/tests/set-e/while2.05
-rw-r--r--bin/sh/tests/set-e/while3.09
-rw-r--r--bin/sh/trap.c551
-rw-r--r--bin/sh/trap.h48
-rw-r--r--bin/sh/var.c967
-rw-r--r--bin/sh/var.h130
582 files changed, 29234 insertions, 0 deletions
diff --git a/bin/sh/Makefile b/bin/sh/Makefile
new file mode 100644
index 000000000000..98c0caaf671e
--- /dev/null
+++ b/bin/sh/Makefile
@@ -0,0 +1,70 @@
+# @(#)Makefile 8.4 (Berkeley) 5/5/95
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PACKAGE=runtime
+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:H}/kill \
+ ${.CURDIR:H}/test \
+ ${SRCTOP}/usr.bin/printf
+
+CLEANFILES+= mknodes mknodes.o \
+ mksyntax mksyntax.o
+CLEANFILES+= ${GENSRCS} ${GENHDRS}
+
+build-tools: mknodes mksyntax
+
+.ORDER: builtins.c builtins.h
+builtins.h: .NOMETA
+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.
+mknodes.o mksyntax.o: ${BUILD_TOOLS_META}
+ ${CC} ${CFLAGS} ${LDFLAGS} ${.IMPSRC} ${LDLIBS} -o ${.TARGET}
+mknodes: mknodes.o ${BUILD_TOOLS_META}
+mksyntax: mksyntax.o ${BUILD_TOOLS_META}
+
+.ORDER: nodes.c nodes.h
+nodes.h: .NOMETA
+nodes.c nodes.h: mknodes nodetypes nodes.c.pat
+ ${BTOOLSPATH:U.}/mknodes ${.CURDIR}/nodetypes ${.CURDIR}/nodes.c.pat
+
+.ORDER: syntax.c syntax.h
+syntax.h: .NOMETA
+syntax.c syntax.h: mksyntax
+ ${BTOOLSPATH:U.}/mksyntax
+
+token.h: mktokens
+ sh ${.CURDIR}/mktokens
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+.include <bsd.prog.mk>
diff --git a/bin/sh/Makefile.depend b/bin/sh/Makefile.depend
new file mode 100644
index 000000000000..3d9203935abf
--- /dev/null
+++ b/bin/sh/Makefile.depend
@@ -0,0 +1,20 @@
+# $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
+.endif
diff --git a/bin/sh/TOUR b/bin/sh/TOUR
new file mode 100644
index 000000000000..14dc3458b65c
--- /dev/null
+++ b/bin/sh/TOUR
@@ -0,0 +1,301 @@
+# @(#)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.def 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 Parameter expansion
+ CTLENDVAR End of parameter expansion
+ CTLBACKQ Command substitution
+ CTLBACKQ|CTLQUOTE Command substitution inside double quotes
+ CTLARI Arithmetic expansion
+ CTLENDARI End of arithmetic expansion
+ 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}
+ VSTRIMLEFT ${var#text}
+ VSTRIMLEFTMAX ${var##text}
+ VSTRIMRIGHT ${var%text}
+ VSTRIMRIGHTMAX ${var%%text}
+ VSLENGTH ${#var}
+ VSERROR delayed error
+
+In addition, the type field will have the VSQUOTE flag set if the
+variable is enclosed in double quotes and the VSLINENO flag if
+LINENO is being expanded (the parameter name is the decimal line
+number). The parameter's name comes next, terminated by an equals
+sign. If the type is not VSNORMAL (including when it is VSLENGTH),
+then the text field in the substitution follows, terminated by a
+CTLENDVAR byte.
+
+The type VSERROR is used to allow parsing bad substitutions like
+${var[7]} and generate an error when they are expanded.
+
+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.
+
+Arithmetic expansion starts with CTLARI and ends with CTLENDARI.
+
+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 file name generation,
+have the CTLESC characters removed during the variable and command
+substitution phase. Words which are subject to file name
+generation have the CTLESC characters removed 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: As the routine argstr generates words by parameter
+expansion, command substitution and arithmetic expansion, it
+performs word splitting on the result. As each word is output,
+the routine expandmeta performs file name generation (if enabled).
+
+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.def, which is processed by the
+mkbuiltins 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 non-
+special 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 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 program is to be compiled
+stand-alone. A similar approach is used for a few utilities from
+bin and usr.bin.
+
+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 its 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/bin/sh/alias.c b/bin/sh/alias.c
new file mode 100644
index 000000000000..bbcf5fbe17e3
--- /dev/null
+++ b/bin/sh/alias.c
@@ -0,0 +1,256 @@
+/*-
+ * 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.
+ */
+
+#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"
+#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;
+
+ unalias(name);
+ app = hashalias(name);
+ 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 void
+freealias(struct alias *ap)
+{
+ ckfree(ap->name);
+ ckfree(ap->val);
+ ckfree(ap);
+}
+
+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;
+ freealias(ap);
+ INTON;
+ }
+ aliases--;
+ return (0);
+ }
+ }
+
+ return (1);
+}
+
+static void
+rmaliases(void)
+{
+ struct alias *ap, **app;
+ int i;
+
+ INTOFF;
+ for (i = 0; i < ATABSIZE; i++) {
+ app = &atab[i];
+ while (*app) {
+ ap = *app;
+ if (ap->flag & ALIASINUSE) {
+ *ap->name = '\0';
+ app = &(*app)->next;
+ } else {
+ *app = ap->next;
+ freealias(ap);
+ }
+ }
+ }
+ aliases = 0;
+ INTON;
+}
+
+struct alias *
+lookupalias(const char *name, int check)
+{
+ struct alias *ap;
+
+ if (aliases == 0)
+ return (NULL);
+ for (ap = *hashalias(name); 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/bin/sh/alias.h b/bin/sh/alias.h
new file mode 100644
index 000000000000..92de705bc963
--- /dev/null
+++ b/bin/sh/alias.h
@@ -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.
+ * 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.
+ *
+ * @(#)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/bin/sh/arith.h b/bin/sh/arith.h
new file mode 100644
index 000000000000..569d0c58651f
--- /dev/null
+++ b/bin/sh/arith.h
@@ -0,0 +1,37 @@
+/*-
+ * 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.
+ * 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.
+ *
+ * @(#)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 *);
diff --git a/bin/sh/arith_yacc.c b/bin/sh/arith_yacc.c
new file mode 100644
index 000000000000..5000c6b0d1be
--- /dev/null
+++ b/bin/sh/arith_yacc.c
@@ -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/bin/sh/arith_yacc.h b/bin/sh/arith_yacc.h
new file mode 100644
index 000000000000..ca92e6f7fa17
--- /dev/null
+++ b/bin/sh/arith_yacc.h
@@ -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/bin/sh/arith_yylex.c b/bin/sh/arith_yylex.c
new file mode 100644
index 000000000000..f7eaf3e3fa6d
--- /dev/null
+++ b/bin/sh/arith_yylex.c
@@ -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/bin/sh/bltin/bltin.h b/bin/sh/bltin/bltin.h
new file mode 100644
index 000000000000..5b6392788fdc
--- /dev/null
+++ b/bin/sh/bltin/bltin.h
@@ -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.
+ * 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.
+ *
+ * @(#)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/bin/sh/bltin/echo.c b/bin/sh/bltin/echo.c
new file mode 100644
index 000000000000..e07a8c8e4ddf
--- /dev/null
+++ b/bin/sh/bltin/echo.c
@@ -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.
+ * 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.
+ *
+ * @(#)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/bin/sh/builtins.def b/bin/sh/builtins.def
new file mode 100644
index 000000000000..a3cea9f6e441
--- /dev/null
+++ b/bin/sh/builtins.def
@@ -0,0 +1,96 @@
+#!/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.
+# 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.
+#
+# @(#)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 -n flag specifies that this command can safely be run in the same
+# process when it is the only command in a command substitution. Some
+# commands have special logic defined in safe_builtin().
+# 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 -n builtin
+aliascmd alias
+bgcmd -j bg
+bindcmd bind
+breakcmd -s break -s continue
+cdcmd cd chdir
+commandcmd -n command
+dotcmd -s .
+echocmd -n echo
+evalcmd -s eval
+execcmd -s exec
+exitcmd -s exit
+letcmd let
+exportcmd -s export -s readonly
+#exprcmd expr
+falsecmd -n false
+fgcmd -j fg
+freebsd_wordexpcmd freebsd_wordexp
+getoptscmd getopts
+hashcmd hash
+histcmd -h fc
+jobidcmd -n jobid
+jobscmd -n jobs
+killcmd -n kill
+localcmd local
+printfcmd -n printf
+pwdcmd -n pwd
+readcmd read
+returncmd -s return
+setcmd -s set
+setvarcmd setvar
+shiftcmd -s shift
+testcmd -n test [
+timescmd -n -s times
+trapcmd -s trap
+truecmd -n -s : true
+typecmd -n type
+ulimitcmd ulimit
+umaskcmd umask
+unaliascmd unalias
+unsetcmd -s unset
+waitcmd wait
+wordexpcmd wordexp
diff --git a/bin/sh/cd.c b/bin/sh/cd.c
new file mode 100644
index 000000000000..b7af5ba9c05b
--- /dev/null
+++ b/bin/sh/cd.c
@@ -0,0 +1,430 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#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(char **);
+static char *findcwd(char *);
+static void updatepwd(char *);
+static char *getpwd(void);
+static char *getpwd2(void);
+
+static char *curdir = NULL; /* current working directory */
+
+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 = bltinlookup("OLDPWD", 1);
+ if (dest == NULL)
+ error("OLDPWD not set");
+ print = 1;
+ }
+ 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);
+ /*
+ * Ignore write errors to preserve the invariant that the
+ * current directory is changed iff the exit status is 0
+ * (or 1 if -e was given and the full pathname could not be
+ * determined).
+ */
+ flushout(out1);
+ outclearerror(out1);
+ }
+
+ return (rc);
+}
+
+static int
+cdlogical(char *dest)
+{
+ char *p;
+ char *q;
+ char *component;
+ char *path;
+ 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;
+ path = stsavestr(dest);
+ STARTSTACKSTR(p);
+ if (*dest == '/') {
+ STPUTC('/', p);
+ path++;
+ }
+ first = 1;
+ while ((q = getcomponent(&path)) != 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 *path.
+ * This routine overwrites *path and the string pointed to by it.
+ */
+static char *
+getcomponent(char **path)
+{
+ char *p;
+ char *start;
+
+ if ((p = *path) == NULL)
+ return NULL;
+ start = *path;
+ while (*p != '/' && *p != '\0')
+ p++;
+ if (*p == '\0') {
+ *path = NULL;
+ } else {
+ *p++ = '\0';
+ *path = p;
+ }
+ return start;
+}
+
+
+static char *
+findcwd(char *dir)
+{
+ char *new;
+ char *p;
+ char *path;
+
+ /*
+ * 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();
+ path = stsavestr(dir);
+ STARTSTACKSTR(new);
+ if (*dir != '/') {
+ STPUTS(curdir, new);
+ if (STTOPC(new) == '/')
+ STUNPUTC(new);
+ }
+ while ((p = getcomponent(&path)) != 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)
+{
+ char *prevdir;
+
+ hashcd(); /* update command hash table */
+
+ setvar("PWD", dir, VEXPORT);
+ setvar("OLDPWD", curdir, VEXPORT);
+ prevdir = curdir;
+ curdir = dir ? savestr(dir) : NULL;
+ ckfree(prevdir);
+}
+
+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/bin/sh/cd.h b/bin/sh/cd.h
new file mode 100644
index 000000000000..a82d6370d57c
--- /dev/null
+++ b/bin/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.
+ * 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$
+ */
+
+void pwd_init(int);
diff --git a/bin/sh/error.c b/bin/sh/error.c
new file mode 100644
index 000000000000..784d2a9eb91a
--- /dev/null
+++ b/bin/sh/error.c
@@ -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.
+ * 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.
+ */
+
+#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/bin/sh/error.h b/bin/sh/error.h
new file mode 100644
index 000000000000..1872e8377333
--- /dev/null
+++ b/bin/sh/error.h
@@ -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.
+ * 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.
+ *
+ * @(#)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) do { suppressint = (s); if (suppressint == 0 && intpending) onint(); } while (0)
+#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/bin/sh/eval.c b/bin/sh/eval.c
new file mode 100644
index 000000000000..7898693c968c
--- /dev/null
+++ b/bin/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.
+ * 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.
+ */
+
+#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 arglist *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;
+ int i;
+ int status;
+
+ emptyarglist(&arglist);
+ for (argp = n->nfor.args ; argp ; argp = argp->narg.next) {
+ oexitstatus = exitstatus;
+ expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+ }
+
+ loopnest++;
+ status = 0;
+ for (i = 0; i < arglist.count; i++) {
+ setvar(n->nfor.var, arglist.args[i], 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;
+
+ emptyarglist(&arglist);
+ 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.args[0])) {
+ 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;
+ FORCEINTON;
+ 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;
+ unsigned char saveoptreset;
+
+ redir->nhere.expdoc = "";
+ savelocalvars = localvars;
+ localvars = NULL;
+ saveoptreset = shellparam.reset;
+ 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->args[0];
+ INTOFF;
+ }
+ handler = savehandler;
+ forcelocal--;
+ poplocalvars();
+ localvars = savelocalvars;
+ shellparam.reset = saveoptreset;
+ 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;
+ emptyarglist(&fn);
+ switch (redir->type) {
+ case NFROM:
+ case NTO:
+ case NFROMTO:
+ case NAPPEND:
+ case NCLOBBER:
+ expandarg(redir->nfile.fname, &fn, EXP_TILDE);
+ redir->nfile.expfname = fn.args[0];
+ break;
+ case NFROMFD:
+ case NTOFD:
+ if (redir->ndup.vname) {
+ expandarg(redir->ndup.vname, &fn, EXP_TILDE);
+ fixredir(redir, fn.args[0], 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;
+ unsigned char saveoptreset;
+
+ 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;
+ saveoptreset = shellparam.reset;
+ forcelocal++;
+ savehandler = handler;
+ if (setjmp(jmploc.loc)) {
+ if (exception == EXERROR || exception == EXEXEC)
+ exitstatus = 2;
+ else if (exception != 0) {
+ handler = savehandler;
+ forcelocal--;
+ poplocalvars();
+ localvars = savelocalvars;
+ shellparam.reset = saveoptreset;
+ longjmp(handler->loc, 1);
+ }
+ } else {
+ handler = &jmploc;
+ evalcommand(n, EV_BACKCMD, result);
+ }
+ handler = savehandler;
+ forcelocal--;
+ poplocalvars();
+ localvars = savelocalvars;
+ shellparam.reset = saveoptreset;
+ } 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, int argc, char **argv)
+{
+ char sep = 0;
+ const char *text, *p, *ps4;
+ int i;
+
+ ps4 = expandstr(ps4val());
+ out2str(ps4 != NULL ? ps4 : ps4val());
+ for (i = 0; i < varlist->count; i++) {
+ text = varlist->args[i];
+ if (sep != 0)
+ out2c(' ');
+ p = strchr(text, '=');
+ if (p != NULL) {
+ p++;
+ outbin(text, p - text, out2);
+ out2qstr(p);
+ } else
+ out2qstr(text);
+ sep = ' ';
+ }
+ for (i = 0; i < argc; i++) {
+ text = argv[i];
+ if (sep != 0)
+ out2c(' ');
+ out2qstr(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)
+{
+ /* Generated from builtins.def. */
+ if (safe_builtin_always(idx))
+ 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;
+ 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();
+ int i;
+
+ /* First expand the arguments. */
+ TRACE(("evalcommand(%p, %d) called\n", (void *)cmd, flags));
+ emptyarglist(&arglist);
+ emptyarglist(&varlist);
+ varflag = 1;
+ jp = NULL;
+ do_clearcmdentry = 0;
+ oexitstatus = exitstatus;
+ exitstatus = 0;
+ /* Add one slot at the beginning for tryexec(). */
+ appendarglist(&arglist, nullstr);
+ 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);
+ }
+ appendarglist(&arglist, nullstr);
+ expredir(cmd->ncmd.redirect);
+ argc = arglist.count - 2;
+ argv = &arglist.args[1];
+
+ argv[argc] = NULL;
+ lastarg = NULL;
+ if (iflag && funcnest == 0 && argc > 0)
+ lastarg = argv[argc - 1];
+
+ /* Print the command if xflag is set. */
+ if (xflag)
+ xtracecommand(&varlist, argc, argv);
+
+ /* 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 (i = 0; i < varlist.count; i++)
+ if (strncmp(varlist.args[i], PATH, sizeof(PATH) - 1) == 0) {
+ path = varlist.args[i] + 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.count == 0 &&
+ (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)) {
+ popredir();
+ unreffunc(cmdentry.u.func);
+ poplocalvars();
+ localvars = savelocalvars;
+ freeparam(&shellparam);
+ shellparam = saveparam;
+ funcnest--;
+ handler = savehandler;
+ longjmp(handler->loc, 1);
+ }
+ handler = &jmploc;
+ funcnest++;
+ redirect(cmd->ncmd.redirect, REDIR_PUSH);
+ INTON;
+ for (i = 0; i < varlist.count; i++)
+ mklocal(varlist.args[i]);
+ 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.nextc = memout.buf;
+ mode |= REDIR_BACKQ;
+ }
+ savecmdname = commandname;
+ savetopfile = getcurrentfile();
+ cmdenviron = &varlist;
+ 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.buf != NULL ?
+ memout.nextc - memout.buf : 0;
+ memout.buf = NULL;
+ memout.nextc = NULL;
+ memout.bufend = NULL;
+ memout.bufsize = 64;
+ }
+ 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 (i = 0; i < varlist.count; i++)
+ setvareq(varlist.args[i], 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 command substitution
+ * 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)
+{
+ int i;
+
+ /*
+ * Because we have historically not supported any options,
+ * only treat "--" specially.
+ */
+ if (argc > 1 && strcmp(argv[1], "--") == 0)
+ argc--, argv++;
+ if (argc > 1) {
+ iflag = 0; /* exit on error */
+ mflag = 0;
+ optschanged();
+ for (i = 0; i < cmdenviron->count; i++)
+ setvareq(cmdenviron->args[i], 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/bin/sh/eval.h b/bin/sh/eval.h
new file mode 100644
index 000000000000..d8a12eb79c7f
--- /dev/null
+++ b/bin/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.
+ * 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.
+ *
+ * @(#)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 arglist *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/bin/sh/exec.c b/bin/sh/exec.c
new file mode 100644
index 000000000000..fb0868f5c889
--- /dev/null
+++ b/bin/sh/exec.c
@@ -0,0 +1,776 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#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"
+
+
+#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;
+ entry->special = 0;
+ return;
+ }
+
+ cd = 0;
+
+ /* If name is in the table, 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;
+ cmdp->special = 0;
+ 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;
+ entry->special = 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 unsigned char *bp;
+ size_t len;
+
+ len = strlen(name);
+ for (bp = builtincmd ; *bp ; bp += 2 + bp[0]) {
+ if (bp[0] == len && memcmp(bp + 2, name, len) == 0) {
+ *special = (bp[1] & BUILTIN_SPECIAL) != 0;
+ return bp[1] & ~BUILTIN_SPECIAL;
+ }
+ }
+ 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 cached utility locations.
+ */
+
+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;
+ cmdp->special = entry->special;
+ 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);
+ entry.special = 0;
+ 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/bin/sh/exec.h b/bin/sh/exec.h
new file mode 100644
index 000000000000..2435c097c273
--- /dev/null
+++ b/bin/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.
+ * 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.
+ *
+ * @(#)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/bin/sh/expand.c b/bin/sh/expand.c
new file mode 100644
index 000000000000..3b44d5569a66
--- /dev/null
+++ b/bin/sh/expand.c
@@ -0,0 +1,1550 @@
+/*-
+ * 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.
+ * Copyright (c) 2010-2015
+ * Jilles Tjoelker <jilles@stack.nl>. 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.
+ */
+
+#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"
+
+enum wordstate { WORD_IDLE, WORD_WS_DELIMITED, WORD_QUOTEMARK };
+
+struct worddest {
+ struct arglist *list;
+ enum wordstate state;
+};
+
+static char *expdest; /* output of current string */
+
+static const char *argstr(const char *, struct nodelist **restrict, int,
+ struct worddest *);
+static const char *exptilde(const char *, int);
+static const char *expari(const char *, struct nodelist **restrict, int,
+ struct worddest *);
+static void expbackq(union node *, int, int, struct worddest *);
+static const char *subevalvar_trim(const char *, struct nodelist **restrict,
+ int, int, int);
+static const char *subevalvar_misc(const char *, struct nodelist **restrict,
+ const char *, int, int, int);
+static const char *evalvar(const char *, struct nodelist **restrict, int,
+ struct worddest *);
+static int varisset(const char *, int);
+static void strtodest(const char *, int, int, int, struct worddest *);
+static void reprocess(int, int, int, int, struct worddest *);
+static void varvalue(const char *, int, int, int, struct worddest *);
+static void expandmeta(char *, struct arglist *);
+static void expmeta(char *, char *, struct arglist *);
+static int expsortcmp(const void *, const void *);
+static int patmatch(const char *, const char *);
+static void cvtnum(int, char *);
+static int collate_range_cmp(wchar_t, wchar_t);
+
+void
+emptyarglist(struct arglist *list)
+{
+
+ list->args = list->smallarg;
+ list->count = 0;
+ list->capacity = sizeof(list->smallarg) / sizeof(list->smallarg[0]);
+}
+
+void
+appendarglist(struct arglist *list, char *str)
+{
+ char **newargs;
+ int newcapacity;
+
+ if (list->count >= list->capacity) {
+ newcapacity = list->capacity * 2;
+ if (newcapacity < 16)
+ newcapacity = 16;
+ if (newcapacity > INT_MAX / (int)sizeof(newargs[0]))
+ error("Too many entries in arglist");
+ newargs = stalloc(newcapacity * sizeof(newargs[0]));
+ memcpy(newargs, list->args, list->count * sizeof(newargs[0]));
+ list->args = newargs;
+ list->capacity = newcapacity;
+ }
+ list->args[list->count++] = str;
+}
+
+static int
+collate_range_cmp(wchar_t c1, wchar_t c2)
+{
+ wchar_t s1[2], s2[2];
+
+ s1[0] = c1;
+ s1[1] = L'\0';
+ s2[0] = c2;
+ s2[1] = L'\0';
+ 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)
+
+static char *
+nextword(char c, int flag, char *p, struct worddest *dst)
+{
+ int is_ws;
+
+ is_ws = c == '\t' || c == '\n' || c == ' ';
+ if (p != stackblock() || (is_ws ? dst->state == WORD_QUOTEMARK :
+ dst->state != WORD_WS_DELIMITED) || c == '\0') {
+ STPUTC('\0', p);
+ if (flag & EXP_GLOB)
+ expandmeta(grabstackstr(p), dst->list);
+ else
+ appendarglist(dst->list, grabstackstr(p));
+ dst->state = is_ws ? WORD_WS_DELIMITED : WORD_IDLE;
+ } else if (!is_ws && dst->state == WORD_WS_DELIMITED)
+ dst->state = WORD_IDLE;
+ /* Reserve space while the stack string is empty. */
+ appendarglist(dst->list, NULL);
+ dst->list->count--;
+ STARTSTACKSTR(p);
+ return p;
+}
+#define NEXTWORD(c, flag, p, dstlist) p = nextword(c, flag, p, dstlist)
+
+static char *
+stputs_split(const char *data, const char *syntax, int flag, char *p,
+ struct worddest *dst)
+{
+ const char *ifs;
+ char c;
+
+ ifs = ifsset() ? ifsval() : " \t\n";
+ while (*data) {
+ CHECKSTRSPACE(2, p);
+ c = *data++;
+ if (strchr(ifs, c) != NULL) {
+ NEXTWORD(c, flag, p, dst);
+ continue;
+ }
+ if (flag & EXP_GLOB && syntax[(int)c] == CCTL)
+ USTPUTC(CTLESC, p);
+ USTPUTC(c, p);
+ }
+ return (p);
+}
+#define STPUTS_SPLIT(data, syntax, flag, p, dst) p = stputs_split((data), syntax, flag, p, dst)
+
+/*
+ * 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.
+ *
+ * When doing something that may cause this to be re-entered, make sure
+ * the stack string is empty via grabstackstr() and do not assume expdest
+ * remains valid.
+ */
+void
+expandarg(union node *arg, struct arglist *arglist, int flag)
+{
+ struct worddest exparg;
+ struct nodelist *argbackq;
+
+ if (fflag)
+ flag &= ~EXP_GLOB;
+ argbackq = arg->narg.backquote;
+ exparg.list = arglist;
+ exparg.state = WORD_IDLE;
+ STARTSTACKSTR(expdest);
+ argstr(arg->narg.text, &argbackq, flag, &exparg);
+ if (arglist == NULL) {
+ STACKSTRNUL(expdest);
+ return; /* here document expanded */
+ }
+ if ((flag & EXP_SPLIT) == 0 || expdest != stackblock() ||
+ exparg.state == WORD_QUOTEMARK) {
+ STPUTC('\0', expdest);
+ if (flag & EXP_SPLIT) {
+ if (flag & EXP_GLOB)
+ expandmeta(grabstackstr(expdest), exparg.list);
+ else
+ appendarglist(exparg.list, grabstackstr(expdest));
+ }
+ }
+ if ((flag & EXP_SPLIT) == 0)
+ appendarglist(arglist, grabstackstr(expdest));
+}
+
+
+
+/*
+ * 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_GLOB or EXP_CASE are set, keep and/or generate CTLESC
+ * characters to allow for further processing.
+ *
+ * If EXP_SPLIT is set, dst receives any complete words produced.
+ */
+static const char *
+argstr(const char *p, struct nodelist **restrict argbackq, int flag,
+ struct worddest *dst)
+{
+ char c;
+ int quotes = flag & (EXP_GLOB | 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[1] & VSQUOTE) != 0 &&
+ p[2] == '@' && p[3] == '=')
+ break;
+ if ((flag & EXP_SPLIT) != 0 && expdest == stackblock())
+ dst->state = WORD_QUOTEMARK;
+ break;
+ case CTLQUOTEEND:
+ lit_quoted = 0;
+ break;
+ case CTLESC:
+ c = *p++;
+ if (split_lit && !lit_quoted &&
+ strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) {
+ NEXTWORD(c, flag, expdest, dst);
+ break;
+ }
+ if (quotes)
+ USTPUTC(CTLESC, expdest);
+ USTPUTC(c, expdest);
+ break;
+ case CTLVAR:
+ p = evalvar(p, argbackq, flag, dst);
+ break;
+ case CTLBACKQ:
+ case CTLBACKQ|CTLQUOTE:
+ expbackq((*argbackq)->n, c & CTLQUOTE, flag, dst);
+ *argbackq = (*argbackq)->next;
+ break;
+ case CTLARI:
+ p = expari(p, argbackq, flag, dst);
+ break;
+ case ':':
+ case '=':
+ /*
+ * sort of a hack - expand tildes in variable
+ * assignments (after the first '=' and after ':'s).
+ */
+ if (split_lit && !lit_quoted &&
+ strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) {
+ NEXTWORD(c, flag, expdest, dst);
+ break;
+ }
+ USTPUTC(c, expdest);
+ if (flag & EXP_VARTILDE && *p == '~' &&
+ (c != '=' || firsteq)) {
+ if (c == '=')
+ firsteq = 0;
+ p = exptilde(p, flag);
+ }
+ break;
+ default:
+ if (split_lit && !lit_quoted &&
+ strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) {
+ NEXTWORD(c, flag, expdest, dst);
+ break;
+ }
+ USTPUTC(c, expdest);
+ }
+ }
+}
+
+/*
+ * Perform tilde expansion, placing the result in the stack string and
+ * returning the next position in the input string to process.
+ */
+static const char *
+exptilde(const char *p, int flag)
+{
+ char c;
+ const char *startp = p;
+ const char *user;
+ struct passwd *pw;
+ char *home;
+ int len;
+
+ 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:
+ len = p - startp - 1;
+ STPUTBIN(startp + 1, len, expdest);
+ STACKSTRNUL(expdest);
+ user = expdest - len;
+ if (*user == '\0') {
+ home = lookupvar("HOME");
+ } else {
+ pw = getpwnam(user);
+ home = pw != NULL ? pw->pw_dir : NULL;
+ }
+ STADJUST(-len, expdest);
+ if (home == NULL || *home == '\0')
+ return (startp);
+ strtodest(home, flag, VSNORMAL, 1, NULL);
+ return (p);
+ }
+ p++;
+ }
+}
+
+
+/*
+ * Expand arithmetic expression.
+ */
+static const char *
+expari(const char *p, struct nodelist **restrict argbackq, int flag,
+ struct worddest *dst)
+{
+ char *q, *start;
+ arith_t result;
+ int begoff;
+ int quoted;
+ int adj;
+
+ quoted = *p++ == '"';
+ begoff = expdest - stackblock();
+ p = argstr(p, argbackq, 0, NULL);
+ 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 this is quoted, a '-' must not indicate a range in [...].
+ * If this is not quoted, splitting may occur.
+ */
+ if (quoted ?
+ result < 0 && begoff > 1 && flag & (EXP_GLOB | EXP_CASE) :
+ flag & EXP_SPLIT)
+ reprocess(expdest - adj - stackblock(), flag, VSNORMAL, quoted,
+ dst);
+ return p;
+}
+
+
+/*
+ * Perform command substitution.
+ */
+static void
+expbackq(union node *cmd, int quoted, int flag, struct worddest *dst)
+{
+ struct backcmd in;
+ int i;
+ char buf[128];
+ char *p;
+ char *dest = expdest;
+ char lastc;
+ char const *syntax = quoted? DQSYNTAX : BASESYNTAX;
+ int quotes = flag & (EXP_GLOB | EXP_CASE);
+ size_t nnl;
+ const char *ifs;
+ int startloc;
+
+ INTOFF;
+ p = grabstackstr(dest);
+ evalbackcmd(cmd, &in);
+ ungrabstackstr(p, dest);
+
+ p = in.buf;
+ startloc = dest - stackblock();
+ nnl = 0;
+ if (!quoted && flag & EXP_SPLIT)
+ ifs = ifsset() ? ifsval() : " \t\n";
+ else
+ ifs = "";
+ /* Remove 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')
+ continue;
+ if (nnl > 0 && lastc != '\n') {
+ NEXTWORD('\n', flag, dest, dst);
+ nnl = 0;
+ }
+ if (strchr(ifs, lastc) != NULL) {
+ if (lastc == '\n')
+ nnl++;
+ else
+ NEXTWORD(lastc, flag, dest, dst);
+ } else {
+ CHECKSTRSPACE(2, dest);
+ if (quotes && syntax[(int)lastc] == CCTL)
+ USTPUTC(CTLESC, dest);
+ USTPUTC(lastc, dest);
+ }
+ }
+ while (dest > stackblock() + startloc && STTOPC(dest) == '\n')
+ STUNPUTC(dest);
+
+ if (in.fd >= 0)
+ close(in.fd);
+ if (in.buf)
+ ckfree(in.buf);
+ if (in.jp) {
+ p = grabstackstr(dest);
+ exitstatus = waitforjob(in.jp, (int *)NULL);
+ ungrabstackstr(p, dest);
+ }
+ TRACE(("expbackq: done\n"));
+ 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 const char *
+subevalvar_trim(const char *p, struct nodelist **restrict argbackq, int strloc,
+ int subtype, int startloc)
+{
+ char *startp;
+ char *loc = NULL;
+ char *str;
+ int c = 0;
+ int amount;
+
+ p = argstr(p, argbackq, EXP_CASE | EXP_TILDE, NULL);
+ STACKSTRNUL(expdest);
+ startp = stackblock() + startloc;
+ str = stackblock() + strloc;
+
+ switch (subtype) {
+ case VSTRIMLEFT:
+ for (loc = startp; loc < str; loc++) {
+ c = *loc;
+ *loc = '\0';
+ if (patmatch(str, startp)) {
+ *loc = c;
+ recordleft(str, loc, startp);
+ return p;
+ }
+ *loc = c;
+ }
+ break;
+
+ case VSTRIMLEFTMAX:
+ for (loc = str - 1; loc >= startp;) {
+ c = *loc;
+ *loc = '\0';
+ if (patmatch(str, startp)) {
+ *loc = c;
+ recordleft(str, loc, startp);
+ return p;
+ }
+ *loc = c;
+ loc--;
+ }
+ break;
+
+ case VSTRIMRIGHT:
+ for (loc = str - 1; loc >= startp;) {
+ if (patmatch(str, loc)) {
+ amount = loc - expdest;
+ STADJUST(amount, expdest);
+ return p;
+ }
+ loc--;
+ }
+ break;
+
+ case VSTRIMRIGHTMAX:
+ for (loc = startp; loc < str - 1; loc++) {
+ if (patmatch(str, loc)) {
+ amount = loc - expdest;
+ STADJUST(amount, expdest);
+ return p;
+ }
+ }
+ break;
+
+
+ default:
+ abort();
+ }
+ amount = (expdest - stackblock() - strloc) + 1;
+ STADJUST(-amount, expdest);
+ return p;
+}
+
+
+static const char *
+subevalvar_misc(const char *p, struct nodelist **restrict argbackq,
+ const char *var, int subtype, int startloc, int varflags)
+{
+ char *startp;
+ int amount;
+
+ p = argstr(p, argbackq, EXP_TILDE, NULL);
+ STACKSTRNUL(expdest);
+ startp = stackblock() + startloc;
+
+ switch (subtype) {
+ case VSASSIGN:
+ setvar(var, startp, 0);
+ amount = startp - expdest;
+ STADJUST(amount, expdest);
+ return p;
+
+ case VSQUESTION:
+ if (*p != CTLENDVAR) {
+ outfmt(out2, "%s\n", startp);
+ error((char *)NULL);
+ }
+ error("%.*s: parameter %snot set", (int)(p - var - 1),
+ var, (varflags & VSNUL) ? "null or " : "");
+
+ default:
+ abort();
+ }
+}
+
+
+/*
+ * Expand a variable, and return a pointer to the next character in the
+ * input string.
+ */
+
+static const char *
+evalvar(const char *p, struct nodelist **restrict argbackq, int flag,
+ struct worddest *dst)
+{
+ int subtype;
+ int varflags;
+ const char *var;
+ const char *val;
+ int patloc;
+ int c;
+ int set;
+ int special;
+ int startloc;
+ int varlen;
+ int varlenb;
+ char buf[21];
+
+ varflags = (unsigned char)*p++;
+ subtype = varflags & VSTYPE;
+ var = p;
+ special = 0;
+ if (! is_name(*p))
+ special = 1;
+ p = strchr(p, '=') + 1;
+ 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) {
+ if (p - var > (ptrdiff_t)sizeof(buf))
+ abort();
+ memcpy(buf, var, p - var - 1);
+ buf[p - var - 1] = '\0';
+ strtodest(buf, flag, subtype,
+ varflags & VSQUOTE, dst);
+ } else
+ varvalue(var, varflags & VSQUOTE, subtype, flag,
+ dst);
+ 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, dst);
+ }
+ }
+
+ if (subtype == VSPLUS)
+ set = ! set;
+
+ switch (subtype) {
+ case VSLENGTH:
+ cvtnum(varlen, buf);
+ strtodest(buf, flag, VSNORMAL, varflags & VSQUOTE, dst);
+ break;
+
+ case VSNORMAL:
+ return p;
+
+ case VSPLUS:
+ case VSMINUS:
+ if (!set) {
+ return argstr(p, argbackq,
+ flag | (flag & EXP_SPLIT ? EXP_SPLIT_LIT : 0) |
+ (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0), dst);
+ }
+ 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();
+ p = subevalvar_trim(p, argbackq, patloc, subtype, startloc);
+ reprocess(startloc, flag, VSNORMAL, varflags & VSQUOTE, dst);
+ if (flag & EXP_SPLIT && *var == '@' && varflags & VSQUOTE)
+ dst->state = WORD_QUOTEMARK;
+ return p;
+
+ case VSASSIGN:
+ case VSQUESTION:
+ if (!set) {
+ p = subevalvar_misc(p, argbackq, var, subtype,
+ startloc, varflags);
+ /* assert(subtype == VSASSIGN); */
+ val = lookupvar(var);
+ strtodest(val, flag, subtype, varflags & VSQUOTE, dst);
+ return p;
+ }
+ break;
+
+ case VSERROR:
+ c = p - var - 1;
+ error("${%.*s%s}: Bad substitution", c, var,
+ (c > 0 && *p != CTLENDVAR) ? "..." : "");
+
+ default:
+ abort();
+ }
+
+ { /* skip to end of alternative */
+ int nesting = 1;
+ for (;;) {
+ if ((c = *p++) == CTLESC)
+ p++;
+ else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE))
+ *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 special or positional parameter 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,
+ struct worddest *dst)
+{
+ if (subtype == VSLENGTH || subtype == VSTRIMLEFT ||
+ subtype == VSTRIMLEFTMAX || subtype == VSTRIMRIGHT ||
+ subtype == VSTRIMRIGHTMAX)
+ STPUTS(p, expdest);
+ else if (flag & EXP_SPLIT && !quoted && dst != NULL)
+ STPUTS_SPLIT(p, BASESYNTAX, flag, expdest, dst);
+ else if (flag & (EXP_GLOB | EXP_CASE))
+ STPUTS_QUOTES(p, quoted ? DQSYNTAX : BASESYNTAX, expdest);
+ else
+ STPUTS(p, expdest);
+}
+
+static void
+reprocess(int startloc, int flag, int subtype, int quoted,
+ struct worddest *dst)
+{
+ static char *buf = NULL;
+ static size_t buflen = 0;
+ char *startp;
+ size_t len, zpos, zlen;
+
+ startp = stackblock() + startloc;
+ len = expdest - startp;
+ if (len >= SIZE_MAX / 2)
+ abort();
+ INTOFF;
+ if (len >= buflen) {
+ ckfree(buf);
+ buf = NULL;
+ }
+ if (buflen < 128)
+ buflen = 128;
+ while (len >= buflen)
+ buflen <<= 1;
+ if (buf == NULL)
+ buf = ckmalloc(buflen);
+ INTON;
+ memcpy(buf, startp, len);
+ buf[len] = '\0';
+ STADJUST(-len, expdest);
+ for (zpos = 0;;) {
+ zlen = strlen(buf + zpos);
+ strtodest(buf + zpos, flag, subtype, quoted, dst);
+ zpos += zlen + 1;
+ if (zpos == len + 1)
+ break;
+ if (flag & EXP_SPLIT && (quoted || (zlen > 0 && zpos < len)))
+ NEXTWORD('\0', flag, expdest, dst);
+ }
+}
+
+/*
+ * Add the value of a special or positional parameter to the stack string.
+ */
+
+static void
+varvalue(const char *name, int quoted, int subtype, int flag,
+ struct worddest *dst)
+{
+ int num;
+ char *p;
+ int i;
+ int splitlater;
+ char sep[2];
+ char **ap;
+ char buf[(NSHORTOPTS > 10 ? NSHORTOPTS : 10) + 1];
+
+ if (subtype == VSLENGTH)
+ flag &= ~EXP_FULL;
+ splitlater = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX ||
+ subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX;
+
+ switch (*name) {
+ case '$':
+ num = rootpid;
+ break;
+ case '?':
+ num = oexitstatus;
+ break;
+ case '#':
+ num = shellparam.nparam;
+ break;
+ case '!':
+ num = backgndpidval();
+ break;
+ case '-':
+ p = buf;
+ for (i = 0 ; i < NSHORTOPTS ; i++) {
+ if (optval[i])
+ *p++ = optletter[i];
+ }
+ *p = '\0';
+ strtodest(buf, flag, subtype, quoted, dst);
+ return;
+ case '@':
+ if (flag & EXP_SPLIT && quoted) {
+ for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
+ strtodest(p, flag, subtype, quoted, dst);
+ if (*ap) {
+ if (splitlater)
+ STPUTC('\0', expdest);
+ else
+ NEXTWORD('\0', flag, expdest,
+ dst);
+ }
+ }
+ if (shellparam.nparam > 0)
+ dst->state = WORD_QUOTEMARK;
+ 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, dst);
+ if (!*ap)
+ break;
+ if (sep[0])
+ strtodest(sep, flag, subtype, quoted, dst);
+ else if (flag & EXP_SPLIT && !quoted && **ap != '\0') {
+ if (splitlater)
+ STPUTC('\0', expdest);
+ else
+ NEXTWORD('\0', flag, expdest, dst);
+ }
+ }
+ 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, dst);
+ }
+ return;
+ }
+ cvtnum(num, buf);
+ strtodest(buf, flag, subtype, quoted, dst);
+}
+
+
+
+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.
+ * The results are stored in the list dstlist.
+ */
+static void
+expandmeta(char *pattern, struct arglist *dstlist)
+{
+ char *p;
+ int firstmatch;
+ char c;
+
+ firstmatch = dstlist->count;
+ p = pattern;
+ for (; (c = *p) != '\0'; p++) {
+ /* fast check for meta chars */
+ if (c == '*' || c == '?' || c == '[') {
+ INTOFF;
+ expmeta(expdir, pattern, dstlist);
+ INTON;
+ break;
+ }
+ }
+ if (dstlist->count == firstmatch) {
+ /*
+ * no matches
+ */
+ rmescapes(pattern);
+ appendarglist(dstlist, pattern);
+ } else {
+ qsort(&dstlist->args[firstmatch],
+ dstlist->count - firstmatch,
+ sizeof(dstlist->args[0]), expsortcmp);
+ }
+}
+
+
+/*
+ * Do metacharacter (i.e. *, ?, [...]) expansion.
+ */
+
+static void
+expmeta(char *enddir, char *name, struct arglist *arglist)
+{
+ 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 (;;) {
+ if (*q == CTLESC)
+ q++;
+ if (*q == '/' || *q == '\0')
+ break;
+ if (*++q == ']') {
+ metaflag = 1;
+ break;
+ }
+ }
+ } else if (*p == '\0')
+ break;
+ 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 == CTLESC)
+ p++;
+ *enddir++ = *p;
+ if (*p == '\0')
+ break;
+ if (enddir == expdir_end)
+ return;
+ }
+ if (metaflag == 0 || lstat(expdir, &statb) >= 0)
+ appendarglist(arglist, stsavestr(expdir));
+ return;
+ }
+ endname = name + (p - name);
+ if (start != name) {
+ p = name;
+ while (p < start) {
+ 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;
+ 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)) {
+ namlen = dp->d_namlen;
+ if (enddir + namlen + 1 > expdir_end)
+ continue;
+ memcpy(enddir, dp->d_name, namlen + 1);
+ if (atend)
+ appendarglist(arglist, stsavestr(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, arglist);
+ }
+ }
+ }
+ closedir(dirp);
+ if (! atend)
+ endname[-esc - 1] = esc ? CTLESC : '/';
+}
+
+
+static int
+expsortcmp(const void *p1, const void *p2)
+{
+ const char *s1 = *(const char * const *)p1;
+ const char *s2 = *(const char * const *)p2;
+
+ return (strcoll(s1, s2));
+}
+
+
+
+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)
+{
+ 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 (*q++ != *p++)
+ goto backtrack;
+ break;
+ case '?':
+ 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
+ q++;
+ break;
+ case '*':
+ c = *p;
+ while (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 *savep, *saveq;
+ int invert, found;
+ wchar_t chr;
+
+ savep = p, saveq = q;
+ invert = 0;
+ if (*p == '!' || *p == '^') {
+ invert++;
+ p++;
+ }
+ found = 0;
+ 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 == '\0') {
+ p = savep, q = saveq;
+ c = '[';
+ goto dft;
+ }
+ 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++;
+ 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 (*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 (*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;
+ struct nodelist *argbackq;
+ int result;
+ char *p;
+
+ setstackmark(&smark);
+ argbackq = pattern->narg.backquote;
+ STARTSTACKSTR(expdest);
+ argstr(pattern->narg.text, &argbackq, EXP_TILDE | EXP_CASE, NULL);
+ STPUTC('\0', expdest);
+ p = grabstackstr(expdest);
+ result = patmatch(p, val);
+ popstackmark(&smark);
+ return result;
+}
+
+/*
+ * Our own itoa().
+ */
+
+static void
+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 = '-';
+
+ memcpy(buf, p, temp + 32 - p);
+}
+
+/*
+ * 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);
+}
+
+/*
+ * Do most of the work for wordexp(3), new version.
+ */
+
+int
+freebsd_wordexpcmd(int argc __unused, char **argv __unused)
+{
+ struct arglist arglist;
+ union node *args, *n;
+ size_t len;
+ int ch;
+ int protected = 0;
+ int fd = -1;
+ int i;
+
+ while ((ch = nextopt("f:p")) != '\0') {
+ switch (ch) {
+ case 'f':
+ fd = number(shoptarg);
+ break;
+ case 'p':
+ protected = 1;
+ break;
+ }
+ }
+ if (*argptr != NULL)
+ error("wrong number of arguments");
+ if (fd < 0)
+ error("missing fd");
+ INTOFF;
+ setinputfd(fd, 1);
+ INTON;
+ args = parsewordexp();
+ popfile(); /* will also close fd */
+ if (protected)
+ for (n = args; n != NULL; n = n->narg.next) {
+ if (n->narg.backquote != NULL) {
+ outcslow('C', out1);
+ error("command substitution disabled");
+ }
+ }
+ outcslow(' ', out1);
+ emptyarglist(&arglist);
+ for (n = args; n != NULL; n = n->narg.next)
+ expandarg(n, &arglist, EXP_FULL | EXP_TILDE);
+ for (i = 0, len = 0; i < arglist.count; i++)
+ len += strlen(arglist.args[i]);
+ out1fmt("%016x %016zx", arglist.count, len);
+ for (i = 0; i < arglist.count; i++)
+ outbin(arglist.args[i], strlen(arglist.args[i]) + 1, out1);
+ return (0);
+}
diff --git a/bin/sh/expand.h b/bin/sh/expand.h
new file mode 100644
index 000000000000..a60ea29514d2
--- /dev/null
+++ b/bin/sh/expand.h
@@ -0,0 +1,62 @@
+/*-
+ * 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.
+ * 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.
+ *
+ * @(#)expand.h 8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+struct arglist {
+ char **args;
+ int count;
+ int capacity;
+ char *smallarg[1];
+};
+
+/*
+ * expandarg() flags
+ */
+#define EXP_SPLIT 0x1 /* perform word splitting */
+#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 */
+#define EXP_GLOB 0x80 /* perform file globbing */
+
+#define EXP_FULL (EXP_SPLIT | EXP_GLOB)
+
+
+void emptyarglist(struct arglist *);
+void appendarglist(struct arglist *, char *);
+union node;
+void expandarg(union node *, struct arglist *, int);
+void rmescapes(char *);
+int casematch(union node *, const char *);
diff --git a/bin/sh/funcs/cmv b/bin/sh/funcs/cmv
new file mode 100644
index 000000000000..815861eb5ebb
--- /dev/null
+++ b/bin/sh/funcs/cmv
@@ -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.
+# 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.
+#
+# @(#)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/bin/sh/funcs/dirs b/bin/sh/funcs/dirs
new file mode 100644
index 000000000000..cac4c59af088
--- /dev/null
+++ b/bin/sh/funcs/dirs
@@ -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.
+# 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.
+#
+# @(#)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/bin/sh/funcs/login b/bin/sh/funcs/login
new file mode 100644
index 000000000000..055bbe5a8fad
--- /dev/null
+++ b/bin/sh/funcs/login
@@ -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.
+# 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.
+#
+# @(#)login 8.2 (Berkeley) 5/4/95
+# $FreeBSD$
+
+# replaces the login builtin in the BSD shell
+login () exec login "$@"
diff --git a/bin/sh/funcs/newgrp b/bin/sh/funcs/newgrp
new file mode 100644
index 000000000000..d1f8b84800d8
--- /dev/null
+++ b/bin/sh/funcs/newgrp
@@ -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.
+# 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.
+#
+# @(#)newgrp 8.2 (Berkeley) 5/4/95
+# $FreeBSD$
+
+newgrp() exec newgrp "$@"
diff --git a/bin/sh/funcs/popd b/bin/sh/funcs/popd
new file mode 100644
index 000000000000..805a4aff4f94
--- /dev/null
+++ b/bin/sh/funcs/popd
@@ -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.
+# 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.
+#
+# @(#)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/bin/sh/funcs/pushd b/bin/sh/funcs/pushd
new file mode 100644
index 000000000000..12b6fcf71d13
--- /dev/null
+++ b/bin/sh/funcs/pushd
@@ -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.
+# 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.
+#
+# @(#)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/bin/sh/funcs/suspend b/bin/sh/funcs/suspend
new file mode 100644
index 000000000000..aaa2f7370cde
--- /dev/null
+++ b/bin/sh/funcs/suspend
@@ -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.
+# 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.
+#
+# @(#)suspend 8.2 (Berkeley) 5/4/95
+# $FreeBSD$
+
+suspend() {
+ local -
+ set +m
+ kill -TSTP 0
+}
diff --git a/bin/sh/histedit.c b/bin/sh/histedit.c
new file mode 100644
index 000000000000..8e2f927cfe06
--- /dev/null
+++ b/bin/sh/histedit.c
@@ -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.
+ * 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.
+ */
+
+#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.
+ */
+ 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);
+ INTON;
+ editcmd = stalloc(strlen(editor) + strlen(editfile) + 2);
+ sprintf(editcmd, "%s %s", editor, editfile);
+ evalstring(editcmd, 0); /* XXX - should use no JC command */
+ 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/bin/sh/input.c b/bin/sh/input.c
new file mode 100644
index 000000000000..391953190b54
--- /dev/null
+++ b/bin/sh/input.c
@@ -0,0 +1,518 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#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 */
+
+EditLine *el; /* cookie for editline package */
+
+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, *r, *end;
+ 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;
+
+again:
+ if (parselleft <= 0) {
+ if ((parselleft = preadfd()) == -1) {
+ parselleft = parsenleft = EOF_NLEFT;
+ return PEOF;
+ }
+ }
+
+ p = parsefile->buf + (parsenextc - parsefile->buf);
+ end = p + parselleft;
+ *end = '\0';
+ q = strchrnul(p, '\n');
+ if (q != end && *q == '\0') {
+ /* delete nul characters */
+ for (r = q; q != end; q++) {
+ if (*q != '\0')
+ *r++ = *q;
+ }
+ parselleft -= end - r;
+ if (parselleft == 0)
+ goto again;
+ end = p + parselleft;
+ *end = '\0';
+ q = strchrnul(p, '\n');
+ }
+ if (q == end) {
+ parsenleft = parselleft;
+ parselleft = 0;
+ } else /* *q == '\n' */ {
+ q++;
+ parsenleft = q - parsenextc;
+ parselleft -= parsenleft;
+ }
+ parsenleft--;
+
+ 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/bin/sh/input.h b/bin/sh/input.h
new file mode 100644
index 000000000000..71046b9731c3
--- /dev/null
+++ b/bin/sh/input.h
@@ -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.
+ * 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.
+ *
+ * @(#)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/bin/sh/jobs.c b/bin/sh/jobs.c
new file mode 100644
index 000000000000..e2434493417d
--- /dev/null
+++ b/bin/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.
+ * 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.
+ */
+
+#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 statebuf[16];
+ const char *statestr, *coredump;
+ 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
+ coredump = "";
+ ps = jp->ps + jp->nprocs - 1;
+ if (jp->state == 0) {
+ 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;
+ statestr = strsignal(i);
+ if (statestr == NULL)
+ statestr = "Suspended";
+#endif
+ } else if (WIFEXITED(ps->status)) {
+ if (WEXITSTATUS(ps->status) == 0)
+ statestr = "Done";
+ else {
+ fmtstr(statebuf, sizeof(statebuf), "Done(%d)",
+ WEXITSTATUS(ps->status));
+ statestr = statebuf;
+ }
+ } else {
+ i = WTERMSIG(ps->status);
+ statestr = strsignal(i);
+ if (statestr == NULL)
+ statestr = "Unknown signal";
+ if (WCOREDUMP(ps->status))
+ coredump = " (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);
+ out1str(coredump);
+ col += strlen(statestr) + strlen(coredump);
+ }
+ 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/bin/sh/jobs.h b/bin/sh/jobs.h
new file mode 100644
index 000000000000..888d6042a3f5
--- /dev/null
+++ b/bin/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.
+ * 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.
+ *
+ * @(#)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/bin/sh/mail.c b/bin/sh/mail.c
new file mode 100644
index 000000000000..0a77bae7a808
--- /dev/null
+++ b/bin/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.
+ * 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.
+ */
+
+#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/bin/sh/mail.h b/bin/sh/mail.h
new file mode 100644
index 000000000000..a577a0fb21a6
--- /dev/null
+++ b/bin/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.
+ * 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.
+ *
+ * @(#)mail.h 8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+void chkmail(int);
diff --git a/bin/sh/main.c b/bin/sh/main.c
new file mode 100644
index 000000000000..c483d19b41c3
--- /dev/null
+++ b/bin/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.
+ * 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.
+ */
+
+#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/bin/sh/main.h b/bin/sh/main.h
new file mode 100644
index 000000000000..7c9e250460fb
--- /dev/null
+++ b/bin/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.
+ * 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.
+ *
+ * @(#)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/bin/sh/memalloc.c b/bin/sh/memalloc.c
new file mode 100644
index 000000000000..9deddb128967
--- /dev/null
+++ b/bin/sh/memalloc.c
@@ -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.
+ * 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.
+ */
+
+#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/bin/sh/memalloc.h b/bin/sh/memalloc.h
new file mode 100644
index 000000000000..71c080ffe380
--- /dev/null
+++ b/bin/sh/memalloc.h
@@ -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.
+ * 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.
+ *
+ * @(#)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/bin/sh/miscbltin.c b/bin/sh/miscbltin.c
new file mode 100644
index 000000000000..0154e032f010
--- /dev/null
+++ b/bin/sh/miscbltin.c
@@ -0,0 +1,532 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#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;
+ ptrdiff_t lastnonifs, lastnonifsws;
+ 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);
+ lastnonifs = lastnonifsws = -1;
+ 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;
+ if (c != '\n') {
+ startword = 0;
+ lastnonifs = lastnonifsws = p - stackblock();
+ 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) {
+ lastnonifsws = p - stackblock();
+ 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++;
+ lastnonifs = lastnonifsws = p - stackblock();
+ USTPUTC(c, p);
+ continue;
+ }
+
+ /* end of variable... */
+ startword = is_ifs;
+
+ if (ap[1] == NULL) {
+ /* Last variable needs all IFS chars */
+ saveall++;
+ if (is_ifs == 2)
+ lastnonifsws = p - stackblock();
+ USTPUTC(c, p);
+ continue;
+ }
+
+ STACKSTRNUL(p);
+ setvar(*ap, stackblock(), 0);
+ ap++;
+ STARTSTACKSTR(p);
+ lastnonifs = lastnonifsws = -1;
+ }
+ STACKSTRNUL(p);
+
+ /*
+ * Remove trailing IFS chars: always remove whitespace, don't remove
+ * non-whitespace unless it was naked
+ */
+ if (saveall <= 1)
+ lastnonifsws = lastnonifs;
+ stackblock()[lastnonifsws + 1] = '\0';
+ 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)) == NULL)
+ 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;
+ short 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
+ { "socket buffer size", "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
+#ifdef RLIMIT_UMTXP
+ { "umtx shared locks", (char *)0, RLIMIT_UMTXP, 1, 'o' },
+#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("HSatfdsmcnuvlbpwko")) != '\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 (val < 0 || (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/bin/sh/mkbuiltins b/bin/sh/mkbuiltins
new file mode 100755
index 000000000000..fb77f509b0ba
--- /dev/null
+++ b/bin/sh/mkbuiltins
@@ -0,0 +1,137 @@
+#!/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.
+# 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.
+#
+# @(#)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 unsigned char builtincmd[] = {'
+awk '{ for (i = 2 ; i <= NF ; i++) {
+ if ($i == "-s") {
+ spc = 1;
+ } else if ($i == "-n") {
+ # Handled later for builtins.h
+ continue
+ } else {
+ printf "\t\"\\%03o\\%03o%s\"\n", length($i), (spc ? 128 : 0) + NR-1, $i
+ spc = 0;
+ }
+ }}' $temp
+echo '};'
+
+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 '
+#define BUILTIN_SPECIAL 0x80
+
+extern int (*const builtinfunc[])(int, char **);
+extern const unsigned char builtincmd[];
+'
+awk '{ printf "int %s(int, char **);\n", $1}' $temp
+
+# Build safe_builtin_always()
+cat <<EOF
+
+static inline int
+safe_builtin_always(int idx)
+{
+EOF
+awk '
+BEGIN { printed = 0 }
+{
+ for (i = 2 ; i <= NF ; i++) {
+ if ($i == "-s") {
+ continue
+ } else if ($i == "-n") {
+ nofork = 1;
+ } else {
+ if (nofork == 0) {
+ continue
+ }
+ if (printed == 1) {
+ printf " || \n\t "
+ } else {
+ printf "\tif ("
+ }
+ printf "idx == " toupper($1)
+ printed = 1
+ nofork = 0;
+ # Only need to check each once
+ break
+ }
+ }
+}' $temp
+
+cat << EOF
+)
+ return (1);
+ return(0);
+}
+EOF
+
+rm -f $temp
diff --git a/bin/sh/mknodes.c b/bin/sh/mknodes.c
new file mode 100644
index 000000000000..e528cc866ee6
--- /dev/null
+++ b/bin/sh/mknodes.c
@@ -0,0 +1,459 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#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>
+
+#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 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(FILE *);
+static void error(const char *, ...) __printf0like(1, 2) __dead2;
+static char *savestr(const char *);
+
+
+int
+main(int argc, char *argv[])
+{
+ FILE *infp;
+
+ if (argc != 3)
+ error("usage: mknodes file");
+ if ((infp = fopen(argv[1], "r")) == NULL)
+ error("Can't open %s: %s", argv[1], strerror(errno));
+ while (readline(infp)) {
+ if (line[0] == ' ' || line[0] == '\t')
+ parsefield();
+ else if (line[0] != '\0')
+ parsenode();
+ }
+ fclose(infp);
+ 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);
+ if (ferror(hfile))
+ error("Can't write to nodes.h");
+ if (fclose(hfile))
+ error("Can't close nodes.h");
+
+ 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);
+ }
+ fclose(patfile);
+ if (ferror(cfile))
+ error("Can't write to nodes.c");
+ if (fclose(cfile))
+ error("Can't close nodes.c");
+}
+
+
+
+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(" result->blocksize += nodesize[n->type];\n", cfile);
+ else {
+ fputs(" new = state->block;\n", cfile);
+ fputs(" state->block = (char *)state->block + 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, result);\n",
+ sp->tag, fp->name);
+ } else {
+ indent(12, cfile);
+ fprintf(cfile, "new->%s.%s = copynode(n->%s.%s, state);\n",
+ sp->tag, fp->name, sp->tag, fp->name);
+ }
+ break;
+ case T_NODELIST:
+ if (calcsize) {
+ indent(12, cfile);
+ fprintf(cfile, "sizenodelist(n->%s.%s, result);\n",
+ sp->tag, fp->name);
+ } else {
+ indent(12, cfile);
+ fprintf(cfile, "new->%s.%s = copynodelist(n->%s.%s, state);\n",
+ sp->tag, fp->name, sp->tag, fp->name);
+ }
+ break;
+ case T_STRING:
+ if (calcsize) {
+ indent(12, cfile);
+ fprintf(cfile, "result->stringsize += strlen(n->%s.%s) + 1;\n",
+ sp->tag, fp->name);
+ } else {
+ indent(12, cfile);
+ fprintf(cfile, "new->%s.%s = nodesavestr(n->%s.%s, state);\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(FILE *infp)
+{
+ 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/bin/sh/mksyntax.c b/bin/sh/mksyntax.c
new file mode 100644
index 000000000000..f71a5a1a132b
--- /dev/null
+++ b/bin/sh/mksyntax.c
@@ -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.
+ * 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.
+ */
+
+#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/bin/sh/mktokens b/bin/sh/mktokens
new file mode 100644
index 000000000000..8c65c8ddffa9
--- /dev/null
+++ b/bin/sh/mktokens
@@ -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.
+# 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.
+#
+# @(#)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/bin/sh/myhistedit.h b/bin/sh/myhistedit.h
new file mode 100644
index 000000000000..791532cfc645
--- /dev/null
+++ b/bin/sh/myhistedit.h
@@ -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.
+ * 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.
+ *
+ * @(#)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/bin/sh/mystring.c b/bin/sh/mystring.c
new file mode 100644
index 000000000000..c3f9f4cce971
--- /dev/null
+++ b/bin/sh/mystring.c
@@ -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.
+ * 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.
+ */
+
+#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/bin/sh/mystring.h b/bin/sh/mystring.h
new file mode 100644
index 000000000000..1d9ac2f4358c
--- /dev/null
+++ b/bin/sh/mystring.h
@@ -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.
+ * 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.
+ *
+ * @(#)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/bin/sh/nodes.c.pat b/bin/sh/nodes.c.pat
new file mode 100644
index 000000000000..43fa515fe905
--- /dev/null
+++ b/bin/sh/nodes.c.pat
@@ -0,0 +1,193 @@
+/*-
+ * 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.
+ * 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.
+ *
+ * @(#)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"
+
+
+struct nodesize {
+ int blocksize; /* size of structures in function */
+ int stringsize; /* size of strings in node */
+};
+
+struct nodecopystate {
+ pointer block; /* block to allocate function from */
+ char *string; /* block to allocate strings from */
+};
+
+%SIZES
+
+
+static void calcsize(union node *, struct nodesize *);
+static void sizenodelist(struct nodelist *, struct nodesize *);
+static union node *copynode(union node *, struct nodecopystate *);
+static struct nodelist *copynodelist(struct nodelist *, struct nodecopystate *);
+static char *nodesavestr(const char *, struct nodecopystate *);
+
+
+struct funcdef {
+ unsigned int refcount;
+ union node n;
+};
+
+/*
+ * Make a copy of a parse tree.
+ */
+
+struct funcdef *
+copyfunc(union node *n)
+{
+ struct nodesize sz;
+ struct nodecopystate st;
+ struct funcdef *fn;
+
+ if (n == NULL)
+ return NULL;
+ sz.blocksize = offsetof(struct funcdef, n);
+ sz.stringsize = 0;
+ calcsize(n, &sz);
+ fn = ckmalloc(sz.blocksize + sz.stringsize);
+ fn->refcount = 1;
+ st.block = (char *)fn + offsetof(struct funcdef, n);
+ st.string = (char *)fn + sz.blocksize;
+ copynode(n, &st);
+ return fn;
+}
+
+
+union node *
+getfuncnode(struct funcdef *fn)
+{
+ return fn == NULL ? NULL : &fn->n;
+}
+
+
+static void
+calcsize(union node *n, struct nodesize *result)
+{
+ %CALCSIZE
+}
+
+
+
+static void
+sizenodelist(struct nodelist *lp, struct nodesize *result)
+{
+ while (lp) {
+ result->blocksize += ALIGN(sizeof(struct nodelist));
+ calcsize(lp->n, result);
+ lp = lp->next;
+ }
+}
+
+
+
+static union node *
+copynode(union node *n, struct nodecopystate *state)
+{
+ union node *new;
+
+ %COPY
+ return new;
+}
+
+
+static struct nodelist *
+copynodelist(struct nodelist *lp, struct nodecopystate *state)
+{
+ struct nodelist *start;
+ struct nodelist **lpp;
+
+ lpp = &start;
+ while (lp) {
+ *lpp = state->block;
+ state->block = (char *)state->block +
+ ALIGN(sizeof(struct nodelist));
+ (*lpp)->n = copynode(lp->n, state);
+ lp = lp->next;
+ lpp = &(*lpp)->next;
+ }
+ *lpp = NULL;
+ return start;
+}
+
+
+
+static char *
+nodesavestr(const char *s, struct nodecopystate *state)
+{
+ const char *p = s;
+ char *q = state->string;
+ char *rtn = state->string;
+
+ while ((*q++ = *p++) != '\0')
+ continue;
+ state->string = 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/bin/sh/nodetypes b/bin/sh/nodetypes
new file mode 100644
index 000000000000..496ffba05924
--- /dev/null
+++ b/bin/sh/nodetypes
@@ -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.
+# 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.
+#
+# @(#)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/bin/sh/options.c b/bin/sh/options.c
new file mode 100644
index 000000000000..d580436c134e
--- /dev/null
+++ b/bin/sh/options.c
@@ -0,0 +1,592 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#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 void setoptionbyindex(int, int);
+static void setparam(int, char **);
+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++)
+ optval[i] = 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 (optval[i] == 2)
+ optval[i] = 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();
+#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.
+ * If cmdline is true, process the shell's argv; otherwise, process arguments
+ * to the set special builtin.
+ */
+
+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;
+
+ q = *argptr++;
+ if (q == NULL || minusc != NULL)
+ error("Bad -c option");
+ minusc = q;
+ } 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(0, 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;
+ const unsigned char *on;
+ size_t len;
+
+ if (name == NULL) {
+ if (val) {
+ /* "Pretty" output. */
+ out1str("Current option settings\n");
+ for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1)
+ out1fmt("%-16.*s%s\n", *on, on + 1,
+ optval[i] ? "on" : "off");
+ } else {
+ /* Output suitable for re-input to shell. */
+ for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1)
+ out1fmt("%s %co %.*s%s",
+ i % 6 == 0 ? "set" : "",
+ optval[i] ? '-' : '+',
+ *on, on + 1,
+ i % 6 == 5 || i == NOPTS - 1 ? "\n" : "");
+ }
+ } else {
+ len = strlen(name);
+ for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1)
+ if (*on == len && memcmp(on + 1, name, len) == 0) {
+ setoptionbyindex(i, val);
+ return;
+ }
+ error("Illegal option -o %s", name);
+ }
+}
+
+
+static void
+setoptionbyindex(int idx, int val)
+{
+ if (&optval[idx] == &privileged && !val && privileged) {
+ if (setgid(getgid()) == -1)
+ error("setgid");
+ if (setuid(getuid()) == -1)
+ error("setuid");
+ }
+ optval[idx] = val;
+ if (val) {
+ /* #%$ hack for ksh semantics */
+ if (&optval[idx] == &Vflag)
+ Eflag = 0;
+ else if (&optval[idx] == &Eflag)
+ Vflag = 0;
+ }
+}
+
+static void
+setoption(int flag, int val)
+{
+ int i;
+
+ for (i = 0; i < NSHORTOPTS; i++)
+ if (optletter[i] == flag) {
+ setoptionbyindex(i, val);
+ return;
+ }
+ error("Illegal option -%c", flag);
+}
+
+
+/*
+ * Set the shell parameters.
+ */
+
+static void
+setparam(int argc, char **argv)
+{
+ char **newparam;
+ char **ap;
+
+ ap = newparam = ckmalloc((argc + 1) * sizeof *ap);
+ while (*argv) {
+ *ap++ = savestr(*argv++);
+ }
+ *ap = NULL;
+ freeparam(&shellparam);
+ shellparam.malloc = 1;
+ shellparam.nparam = argc;
+ 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 i, n;
+
+ n = 1;
+ if (argc > 1)
+ n = number(argv[1]);
+ if (n > shellparam.nparam)
+ return 1;
+ INTOFF;
+ shellparam.nparam -= n;
+ if (shellparam.malloc)
+ for (i = 0; i < n; i++)
+ ckfree(shellparam.p[i]);
+ memmove(shellparam.p, shellparam.p + n,
+ (shellparam.nparam + 1) * sizeof(shellparam.p[0]));
+ shellparam.reset = 1;
+ INTON;
+ return 0;
+}
+
+
+
+/*
+ * The set builtin command.
+ */
+
+int
+setcmd(int argc, char **argv)
+{
+ if (argc == 1)
+ return showvarscmd(argc, argv);
+ INTOFF;
+ options(0);
+ optschanged();
+ if (*argptr != NULL) {
+ setparam(argc - (argptr - argv), 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 returns the option, 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/bin/sh/options.h b/bin/sh/options.h
new file mode 100644
index 000000000000..d57bce8a7a15
--- /dev/null
+++ b/bin/sh/options.h
@@ -0,0 +1,113 @@
+/*-
+ * 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.
+ * 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.
+ *
+ * @(#)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 optval[0]
+#define fflag optval[1]
+#define Iflag optval[2]
+#define iflag optval[3]
+#define mflag optval[4]
+#define nflag optval[5]
+#define sflag optval[6]
+#define xflag optval[7]
+#define vflag optval[8]
+#define Vflag optval[9]
+#define Eflag optval[10]
+#define Cflag optval[11]
+#define aflag optval[12]
+#define bflag optval[13]
+#define uflag optval[14]
+#define privileged optval[15]
+#define Tflag optval[16]
+#define Pflag optval[17]
+#define hflag optval[18]
+#define nologflag optval[19]
+
+#define NSHORTOPTS 19
+#define NOPTS 20
+
+extern char optval[NOPTS];
+extern const char optletter[NSHORTOPTS];
+#ifdef DEFINE_OPTIONS
+char optval[NOPTS];
+const char optletter[NSHORTOPTS] = "efIimnsxvVECabupTPh";
+static const unsigned char optname[] =
+ "\007errexit"
+ "\006noglob"
+ "\011ignoreeof"
+ "\013interactive"
+ "\007monitor"
+ "\006noexec"
+ "\005stdin"
+ "\006xtrace"
+ "\007verbose"
+ "\002vi"
+ "\005emacs"
+ "\011noclobber"
+ "\011allexport"
+ "\006notify"
+ "\007nounset"
+ "\012privileged"
+ "\012trapsasync"
+ "\010physical"
+ "\010trackall"
+ "\005nolog"
+;
+#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 freeparam(struct shparam *);
+int nextopt(const char *);
+void getoptsreset(const char *);
diff --git a/bin/sh/output.c b/bin/sh/output.c
new file mode 100644
index 000000000000..ced01d246a1c
--- /dev/null
+++ b/bin/sh/output.c
@@ -0,0 +1,368 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#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, NULL, NULL, OUTBUFSIZ, 1, 0};
+struct output errout = {NULL, NULL, NULL, 256, 2, 0};
+struct output memout = {NULL, NULL, NULL, 64, 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, newsize;
+
+ if (dest->buf == NULL) {
+ INTOFF;
+ dest->buf = ckmalloc(dest->bufsize);
+ dest->nextc = dest->buf;
+ dest->bufend = dest->buf + dest->bufsize;
+ INTON;
+ } else if (dest->fd == MEM_OUT) {
+ offset = dest->nextc - dest->buf;
+ newsize = dest->bufsize << 1;
+ INTOFF;
+ dest->buf = ckrealloc(dest->buf, newsize);
+ dest->bufsize = newsize;
+ dest->bufend = dest->buf + newsize;
+ dest->nextc = dest->buf + offset;
+ INTON;
+ } else {
+ flushout(dest);
+ }
+}
+
+
+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;
+}
+
+
+void
+freestdout(void)
+{
+ output.nextc = output.buf;
+}
+
+
+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/bin/sh/output.h b/bin/sh/output.h
new file mode 100644
index 000000000000..f43a60cc2aaa
--- /dev/null
+++ b/bin/sh/output.h
@@ -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.
+ * 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.
+ *
+ * @(#)output.h 8.2 (Berkeley) 5/4/95
+ * $FreeBSD$
+ */
+
+#ifndef OUTPUT_INCL
+
+#include <stdarg.h>
+#include <stddef.h>
+
+struct output {
+ char *nextc;
+ char *bufend;
+ 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)->nextc == (file)->bufend ? (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/bin/sh/parser.c b/bin/sh/parser.c
new file mode 100644
index 000000000000..3948364d3793
--- /dev/null
+++ b/bin/sh/parser.c
@@ -0,0 +1,2118 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#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;
+
+#define NOEOFMARK ((const char *)&heredoclist)
+
+
+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);
+}
+
+
+/*
+ * Read and parse words for wordexp.
+ * Returns a list of NARG nodes; NULL if there are no words.
+ */
+union node *
+parsewordexp(void)
+{
+ union node *n, *first = NULL, **pnext;
+ 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 = 0;
+ setprompt(0);
+ needprompt = 0;
+ pnext = &first;
+ while ((t = readtoken()) != TEOF) {
+ if (t != TWORD)
+ synexpect(TWORD);
+ n = makename();
+ *pnext = n;
+ pnext = &n->narg.next;
+ }
+ return first;
+}
+
+
+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 == NULL)
+ 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);
+ }
+ } else if (c == '\n' && *eofmark == '\0') {
+ c = PEOF;
+ plinno++;
+ needprompt = doprompt;
+ }
+ 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, vc, i, n;
+ unsigned int v;
+
+ 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");
+ }
+ vc = (char)v;
+ /*
+ * We can't handle NUL bytes.
+ * POSIX says we should skip till the closing quote.
+ */
+ if (vc == '\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[vc] == CCTL)
+ USTPUTC(CTLESC, out);
+ USTPUTC(vc, 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 && eofmark != NOEOFMARK)
+ /* 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: {
+ int subtype;
+ int typeloc;
+ int flags;
+ char *p;
+ static const char types[] = "}-+?=";
+ 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 == '{') {
+ 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. */
+ STADJUST(-6, out);
+ CHECKSTRSPACE(11, out);
+ linno = plinno;
+ if (funclinno != 0)
+ linno -= funclinno - 1;
+ length = snprintf(out, 11, "%d", linno);
+ if (length > 10)
+ length = 10;
+ out += length;
+ flags |= VSLINENO;
+ }
+ } else if (is_digit(c)) {
+ if (subtype != VSNORMAL) {
+ do {
+ STPUTC(c, out);
+ c = pgetc_linecont();
+ } while (is_digit(c));
+ } else {
+ USTPUTC(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 if (BASESYNTAX[c] != CCTL)
+ 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);
+ if (BASESYNTAX[c] != CCTL)
+ 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;
+ if (which == 0)
+ return;
+
+#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 < PROMPTLEN - 1) && (*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 - 1);
+ ps[PROMPTLEN - 1] = '\0';
+ /* Skip to end of hostname. */
+ trim = (*fmt == 'h') ? '.' : '\0';
+ while ((ps[i] != '\0') && (ps[i] != trim))
+ i++;
+ --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 == '\0')
+ 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] = '\\';
+ if (i < PROMPTLEN - 2)
+ 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, NOEOFMARK, 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/bin/sh/parser.h b/bin/sh/parser.h
new file mode 100644
index 000000000000..c75b138af48c
--- /dev/null
+++ b/bin/sh/parser.h
@@ -0,0 +1,85 @@
+/*-
+ * 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.
+ * 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.
+ *
+ * @(#)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);
+union node *parsewordexp(void);
+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/bin/sh/redir.c b/bin/sh/redir.c
new file mode 100644
index 000000000000..9a1892d32a7e
--- /dev/null
+++ b/bin/sh/redir.c
@@ -0,0 +1,363 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#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;
+ unsigned int empty_redirs;
+};
+
+
+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;
+
+/* Number of redirtabs that have not been allocated. */
+static unsigned int empty_redirs = 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) {
+ empty_redirs++;
+ if (redir != NULL) {
+ sv = ckmalloc(sizeof (struct redirtab));
+ for (i = 0 ; i < 10 ; i++)
+ sv->renamed[i] = EMPTY;
+ sv->fd0_redirected = fd0_redirected;
+ sv->empty_redirs = empty_redirs - 1;
+ sv->next = redirlist;
+ redirlist = sv;
+ empty_redirs = 0;
+ }
+ }
+ 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;
+
+ INTOFF;
+ if (empty_redirs > 0) {
+ empty_redirs--;
+ INTON;
+ return;
+ }
+ 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);
+ }
+ }
+ }
+ fd0_redirected = rp->fd0_redirected;
+ empty_redirs = rp->empty_redirs;
+ 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/bin/sh/redir.h b/bin/sh/redir.h
new file mode 100644
index 000000000000..b9e6975e933b
--- /dev/null
+++ b/bin/sh/redir.h
@@ -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.
+ * 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.
+ *
+ * @(#)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/bin/sh/sh.1 b/bin/sh/sh.1
new file mode 100644
index 000000000000..9163ad16dde4
--- /dev/null
+++ b/bin/sh/sh.1
@@ -0,0 +1,2879 @@
+.\"-
+.\" 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.
+.\" 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.
+.\"
+.\" from: @(#)sh.1 8.6 (Berkeley) 5/4/95
+.\" $FreeBSD$
+.\"
+.Dd October 8, 2016
+.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.
+.It nolog
+Another do-nothing option for
+.Tn POSIX
+compliance.
+It only has a long name.
+.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 ) 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
+(including those that only have long names)
+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.
+This is initialized at startup to
+.Aq space ,
+.Aq tab ,
+and
+.Aq newline
+in that order.
+This value 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
+This system's fully-qualified hostname (FQDN).
+.It Li \eh
+This system's 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
+.It Ic cd Fl
+Switch to the specified
+.Ar directory ,
+to the directory specified in the
+.Va HOME
+environment variable if no
+.Ar directory
+is specified or
+to the directory specified in the
+.Va OLDPWD
+environment variable if
+.Ar directory
+is
+.Fl .
+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 the
+.Va CDPATH
+mechanism was used or if
+.Ar directory
+was
+.Fl .
+.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.
+.Pp
+If changing the directory fails, the exit status is greater than 1.
+If the directory is changed, the exit status is 0, or also 1 if
+.Fl e
+was given.
+.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 HSabcdfklmnopstuvw 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 o Ar umtxp
+The maximal number of process-shared locks
+(see
+.Xr pthread 3 )
+for this user ID.
+.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 OLDPWD
+The previous current directory.
+This is used and updated by
+.Ic cd .
+.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/bin/sh/shell.h b/bin/sh/shell.h
new file mode 100644
index 000000000000..74f11a33fda7
--- /dev/null
+++ b/bin/sh/shell.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.
+ * 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.
+ *
+ * @(#)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/bin/sh/show.c b/bin/sh/show.c
new file mode 100644
index 000000000000..2437063a76c5
--- /dev/null
+++ b/bin/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.
+ * 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.
+ */
+
+#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/bin/sh/show.h b/bin/sh/show.h
new file mode 100644
index 000000000000..b48830fba0c1
--- /dev/null
+++ b/bin/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.
+ * 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.
+ *
+ * @(#)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/bin/sh/tests/Makefile b/bin/sh/tests/Makefile
new file mode 100644
index 000000000000..59835cb73520
--- /dev/null
+++ b/bin/sh/tests/Makefile
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+.include <bsd.own.mk>
+
+TESTS_SUBDIRS+= builtins
+TESTS_SUBDIRS+= errors
+TESTS_SUBDIRS+= execution
+TESTS_SUBDIRS+= expansion
+TESTS_SUBDIRS+= invocation
+TESTS_SUBDIRS+= parameters
+TESTS_SUBDIRS+= parser
+TESTS_SUBDIRS+= set-e
+
+.include <bsd.test.mk>
diff --git a/bin/sh/tests/Makefile.depend b/bin/sh/tests/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/bin/sh/tests/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/bin/sh/tests/builtins/Makefile b/bin/sh/tests/builtins/Makefile
new file mode 100644
index 000000000000..cb69b1358311
--- /dev/null
+++ b/bin/sh/tests/builtins/Makefile
@@ -0,0 +1,185 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+.include <src.opts.mk>
+
+TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH= functional_test
+
+${PACKAGE}FILES+= alias.0 alias.0.stdout
+${PACKAGE}FILES+= alias.1 alias.1.stderr
+${PACKAGE}FILES+= alias3.0 alias3.0.stdout
+${PACKAGE}FILES+= alias4.0
+${PACKAGE}FILES+= break1.0
+${PACKAGE}FILES+= break2.0 break2.0.stdout
+${PACKAGE}FILES+= break3.0
+${PACKAGE}FILES+= break4.4
+${PACKAGE}FILES+= break5.4
+${PACKAGE}FILES+= break6.0
+${PACKAGE}FILES+= builtin1.0
+${PACKAGE}FILES+= case1.0
+${PACKAGE}FILES+= case2.0
+${PACKAGE}FILES+= case3.0
+${PACKAGE}FILES+= case4.0
+${PACKAGE}FILES+= case5.0
+${PACKAGE}FILES+= case6.0
+${PACKAGE}FILES+= case7.0
+${PACKAGE}FILES+= case8.0
+${PACKAGE}FILES+= case9.0
+${PACKAGE}FILES+= case10.0
+${PACKAGE}FILES+= case11.0
+${PACKAGE}FILES+= case12.0
+${PACKAGE}FILES+= case13.0
+${PACKAGE}FILES+= case14.0
+${PACKAGE}FILES+= case15.0
+${PACKAGE}FILES+= case16.0
+${PACKAGE}FILES+= case17.0
+${PACKAGE}FILES+= case18.0
+${PACKAGE}FILES+= case19.0
+${PACKAGE}FILES+= case20.0
+${PACKAGE}FILES+= case21.0
+${PACKAGE}FILES+= case22.0
+${PACKAGE}FILES+= cd1.0
+${PACKAGE}FILES+= cd2.0
+${PACKAGE}FILES+= cd3.0
+${PACKAGE}FILES+= cd4.0
+${PACKAGE}FILES+= cd5.0
+${PACKAGE}FILES+= cd6.0
+${PACKAGE}FILES+= cd7.0
+${PACKAGE}FILES+= cd8.0
+${PACKAGE}FILES+= cd9.0 cd9.0.stdout
+${PACKAGE}FILES+= cd10.0
+${PACKAGE}FILES+= command1.0
+${PACKAGE}FILES+= command2.0
+${PACKAGE}FILES+= command3.0
+${PACKAGE}FILES+= command3.0.stdout
+${PACKAGE}FILES+= command4.0
+${PACKAGE}FILES+= command5.0
+${PACKAGE}FILES+= command5.0.stdout
+${PACKAGE}FILES+= command6.0
+${PACKAGE}FILES+= command6.0.stdout
+${PACKAGE}FILES+= command7.0
+${PACKAGE}FILES+= command8.0
+${PACKAGE}FILES+= command9.0
+${PACKAGE}FILES+= command10.0
+${PACKAGE}FILES+= command11.0
+${PACKAGE}FILES+= command12.0
+${PACKAGE}FILES+= dot1.0
+${PACKAGE}FILES+= dot2.0
+${PACKAGE}FILES+= dot3.0
+${PACKAGE}FILES+= dot4.0
+${PACKAGE}FILES+= echo1.0
+${PACKAGE}FILES+= echo2.0
+${PACKAGE}FILES+= echo3.0
+${PACKAGE}FILES+= eval1.0
+${PACKAGE}FILES+= eval2.0
+${PACKAGE}FILES+= eval3.0
+${PACKAGE}FILES+= eval4.0
+${PACKAGE}FILES+= eval5.0
+${PACKAGE}FILES+= eval6.0
+${PACKAGE}FILES+= eval7.0
+${PACKAGE}FILES+= eval8.7
+${PACKAGE}FILES+= exec1.0
+${PACKAGE}FILES+= exec2.0
+${PACKAGE}FILES+= exit1.0
+${PACKAGE}FILES+= exit2.8
+${PACKAGE}FILES+= exit3.0
+${PACKAGE}FILES+= export1.0
+${PACKAGE}FILES+= fc1.0
+${PACKAGE}FILES+= fc2.0
+${PACKAGE}FILES+= for1.0
+${PACKAGE}FILES+= for2.0
+${PACKAGE}FILES+= for3.0
+${PACKAGE}FILES+= getopts1.0 getopts1.0.stdout
+${PACKAGE}FILES+= getopts2.0 getopts2.0.stdout
+${PACKAGE}FILES+= getopts3.0
+${PACKAGE}FILES+= getopts4.0
+${PACKAGE}FILES+= getopts5.0
+${PACKAGE}FILES+= getopts6.0
+${PACKAGE}FILES+= getopts7.0
+${PACKAGE}FILES+= getopts8.0 getopts8.0.stdout
+${PACKAGE}FILES+= getopts9.0 getopts9.0.stdout
+${PACKAGE}FILES+= getopts10.0
+${PACKAGE}FILES+= hash1.0 hash1.0.stdout
+${PACKAGE}FILES+= hash2.0 hash2.0.stdout
+${PACKAGE}FILES+= hash3.0 hash3.0.stdout
+${PACKAGE}FILES+= hash4.0
+${PACKAGE}FILES+= jobid1.0
+${PACKAGE}FILES+= jobid2.0
+${PACKAGE}FILES+= kill1.0 kill2.0
+${PACKAGE}FILES+= lineno.0 lineno.0.stdout
+${PACKAGE}FILES+= lineno2.0
+${PACKAGE}FILES+= lineno3.0 lineno3.0.stdout
+${PACKAGE}FILES+= local1.0
+${PACKAGE}FILES+= local2.0
+${PACKAGE}FILES+= local3.0
+${PACKAGE}FILES+= local4.0
+${PACKAGE}FILES+= local5.0
+${PACKAGE}FILES+= local6.0
+${PACKAGE}FILES+= local7.0
+.if ${MK_NLS} != "no"
+${PACKAGE}FILES+= locale1.0
+.endif
+${PACKAGE}FILES+= locale2.0
+${PACKAGE}FILES+= printf1.0
+${PACKAGE}FILES+= printf2.0
+${PACKAGE}FILES+= printf3.0
+${PACKAGE}FILES+= printf4.0
+${PACKAGE}FILES+= read1.0 read1.0.stdout
+${PACKAGE}FILES+= read2.0
+${PACKAGE}FILES+= read3.0 read3.0.stdout
+${PACKAGE}FILES+= read4.0 read4.0.stdout
+${PACKAGE}FILES+= read5.0
+${PACKAGE}FILES+= read6.0
+${PACKAGE}FILES+= read7.0
+${PACKAGE}FILES+= read8.0
+${PACKAGE}FILES+= read9.0
+${PACKAGE}FILES+= return1.0
+${PACKAGE}FILES+= return2.1
+${PACKAGE}FILES+= return3.1
+${PACKAGE}FILES+= return4.0
+${PACKAGE}FILES+= return5.0
+${PACKAGE}FILES+= return6.4
+${PACKAGE}FILES+= return7.4
+${PACKAGE}FILES+= return8.0
+${PACKAGE}FILES+= set1.0
+${PACKAGE}FILES+= set2.0
+${PACKAGE}FILES+= set3.0
+${PACKAGE}FILES+= trap1.0
+${PACKAGE}FILES+= trap10.0
+${PACKAGE}FILES+= trap11.0
+${PACKAGE}FILES+= trap12.0
+${PACKAGE}FILES+= trap13.0
+${PACKAGE}FILES+= trap14.0
+${PACKAGE}FILES+= trap15.0
+${PACKAGE}FILES+= trap16.0
+${PACKAGE}FILES+= trap17.0
+${PACKAGE}FILES+= trap2.0
+${PACKAGE}FILES+= trap3.0
+${PACKAGE}FILES+= trap4.0
+${PACKAGE}FILES+= trap5.0
+${PACKAGE}FILES+= trap6.0
+${PACKAGE}FILES+= trap7.0
+${PACKAGE}FILES+= trap8.0
+${PACKAGE}FILES+= trap9.0
+${PACKAGE}FILES+= type1.0 type1.0.stderr
+${PACKAGE}FILES+= type2.0
+${PACKAGE}FILES+= type3.0
+${PACKAGE}FILES+= unalias.0
+${PACKAGE}FILES+= var-assign.0
+${PACKAGE}FILES+= var-assign2.0
+${PACKAGE}FILES+= wait1.0
+${PACKAGE}FILES+= wait2.0
+${PACKAGE}FILES+= wait3.0
+${PACKAGE}FILES+= wait4.0
+${PACKAGE}FILES+= wait5.0
+${PACKAGE}FILES+= wait6.0
+${PACKAGE}FILES+= wait7.0
+${PACKAGE}FILES+= wait8.0
+${PACKAGE}FILES+= wait9.127
+${PACKAGE}FILES+= wait10.0
+
+.include <bsd.test.mk>
diff --git a/bin/sh/tests/builtins/Makefile.depend b/bin/sh/tests/builtins/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/bin/sh/tests/builtins/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/bin/sh/tests/builtins/alias.0 b/bin/sh/tests/builtins/alias.0
new file mode 100644
index 000000000000..d9b27969cfd6
--- /dev/null
+++ b/bin/sh/tests/builtins/alias.0
@@ -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/bin/sh/tests/builtins/alias.0.stdout b/bin/sh/tests/builtins/alias.0.stdout
new file mode 100644
index 000000000000..52efaf0b72e7
--- /dev/null
+++ b/bin/sh/tests/builtins/alias.0.stdout
@@ -0,0 +1,4 @@
+bar=''
+foo=bar
+quux='1 2 3'
+foo=bar
diff --git a/bin/sh/tests/builtins/alias.1 b/bin/sh/tests/builtins/alias.1
new file mode 100644
index 000000000000..31403dc760bf
--- /dev/null
+++ b/bin/sh/tests/builtins/alias.1
@@ -0,0 +1,3 @@
+# $FreeBSD$
+unalias -a
+alias foo
diff --git a/bin/sh/tests/builtins/alias.1.stderr b/bin/sh/tests/builtins/alias.1.stderr
new file mode 100644
index 000000000000..c9f4011bea00
--- /dev/null
+++ b/bin/sh/tests/builtins/alias.1.stderr
@@ -0,0 +1 @@
+alias: foo: not found
diff --git a/bin/sh/tests/builtins/alias3.0 b/bin/sh/tests/builtins/alias3.0
new file mode 100644
index 000000000000..fe65e31f5f03
--- /dev/null
+++ b/bin/sh/tests/builtins/alias3.0
@@ -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/bin/sh/tests/builtins/alias3.0.stdout b/bin/sh/tests/builtins/alias3.0.stdout
new file mode 100644
index 000000000000..52efaf0b72e7
--- /dev/null
+++ b/bin/sh/tests/builtins/alias3.0.stdout
@@ -0,0 +1,4 @@
+bar=''
+foo=bar
+quux='1 2 3'
+foo=bar
diff --git a/bin/sh/tests/builtins/alias4.0 b/bin/sh/tests/builtins/alias4.0
new file mode 100644
index 000000000000..3d5efeccf2c7
--- /dev/null
+++ b/bin/sh/tests/builtins/alias4.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+unalias -a
+alias --
diff --git a/bin/sh/tests/builtins/break1.0 b/bin/sh/tests/builtins/break1.0
new file mode 100644
index 000000000000..ba0cbb4bd268
--- /dev/null
+++ b/bin/sh/tests/builtins/break1.0
@@ -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/bin/sh/tests/builtins/break2.0 b/bin/sh/tests/builtins/break2.0
new file mode 100644
index 000000000000..ff52dd321469
--- /dev/null
+++ b/bin/sh/tests/builtins/break2.0
@@ -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/bin/sh/tests/builtins/break2.0.stdout b/bin/sh/tests/builtins/break2.0.stdout
new file mode 100644
index 000000000000..12799ccbe7ce
--- /dev/null
+++ b/bin/sh/tests/builtins/break2.0.stdout
@@ -0,0 +1 @@
+good
diff --git a/bin/sh/tests/builtins/break3.0 b/bin/sh/tests/builtins/break3.0
new file mode 100644
index 000000000000..10a5ca88edff
--- /dev/null
+++ b/bin/sh/tests/builtins/break3.0
@@ -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/bin/sh/tests/builtins/break4.4 b/bin/sh/tests/builtins/break4.4
new file mode 100644
index 000000000000..d52ff52be4d4
--- /dev/null
+++ b/bin/sh/tests/builtins/break4.4
@@ -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/bin/sh/tests/builtins/break5.4 b/bin/sh/tests/builtins/break5.4
new file mode 100644
index 000000000000..7df8e186311c
--- /dev/null
+++ b/bin/sh/tests/builtins/break5.4
@@ -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/bin/sh/tests/builtins/break6.0 b/bin/sh/tests/builtins/break6.0
new file mode 100644
index 000000000000..09fc0d85eaa3
--- /dev/null
+++ b/bin/sh/tests/builtins/break6.0
@@ -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/bin/sh/tests/builtins/builtin1.0 b/bin/sh/tests/builtins/builtin1.0
new file mode 100644
index 000000000000..b6083858f671
--- /dev/null
+++ b/bin/sh/tests/builtins/builtin1.0
@@ -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/bin/sh/tests/builtins/case1.0 b/bin/sh/tests/builtins/case1.0
new file mode 100644
index 000000000000..860fc67dacda
--- /dev/null
+++ b/bin/sh/tests/builtins/case1.0
@@ -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/bin/sh/tests/builtins/case10.0 b/bin/sh/tests/builtins/case10.0
new file mode 100644
index 000000000000..a627b5cd996f
--- /dev/null
+++ b/bin/sh/tests/builtins/case10.0
@@ -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/bin/sh/tests/builtins/case11.0 b/bin/sh/tests/builtins/case11.0
new file mode 100644
index 000000000000..0e66e11f0dee
--- /dev/null
+++ b/bin/sh/tests/builtins/case11.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+false
+case x in
+*)
+esac
diff --git a/bin/sh/tests/builtins/case12.0 b/bin/sh/tests/builtins/case12.0
new file mode 100644
index 000000000000..2a442ba7993a
--- /dev/null
+++ b/bin/sh/tests/builtins/case12.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+false
+case x in
+y)
+esac
diff --git a/bin/sh/tests/builtins/case13.0 b/bin/sh/tests/builtins/case13.0
new file mode 100644
index 000000000000..78f4e9bdffde
--- /dev/null
+++ b/bin/sh/tests/builtins/case13.0
@@ -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/bin/sh/tests/builtins/case14.0 b/bin/sh/tests/builtins/case14.0
new file mode 100644
index 000000000000..0338e8a224e3
--- /dev/null
+++ b/bin/sh/tests/builtins/case14.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+case `false` in
+no) exit 3 ;;
+esac
diff --git a/bin/sh/tests/builtins/case15.0 b/bin/sh/tests/builtins/case15.0
new file mode 100644
index 000000000000..09b0e1133544
--- /dev/null
+++ b/bin/sh/tests/builtins/case15.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+case x in
+`false`) exit 3 ;;
+esac
diff --git a/bin/sh/tests/builtins/case16.0 b/bin/sh/tests/builtins/case16.0
new file mode 100644
index 000000000000..24303027ba91
--- /dev/null
+++ b/bin/sh/tests/builtins/case16.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+f() { return 42; }
+f
+case x in
+x) [ $? = 42 ] ;;
+esac
diff --git a/bin/sh/tests/builtins/case17.0 b/bin/sh/tests/builtins/case17.0
new file mode 100644
index 000000000000..ed1d25f0277a
--- /dev/null
+++ b/bin/sh/tests/builtins/case17.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+! case x in x) false ;& y) esac
diff --git a/bin/sh/tests/builtins/case18.0 b/bin/sh/tests/builtins/case18.0
new file mode 100644
index 000000000000..470253f622ea
--- /dev/null
+++ b/bin/sh/tests/builtins/case18.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+case x$(false) in
+x) ;&
+y) [ $? != 0 ] ;;
+z) false ;;
+esac
diff --git a/bin/sh/tests/builtins/case19.0 b/bin/sh/tests/builtins/case19.0
new file mode 100644
index 000000000000..215066ad6321
--- /dev/null
+++ b/bin/sh/tests/builtins/case19.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+[ "`case x in
+x) false ;&
+y) ;&
+z) echo $? ;;
+esac`" != 0 ]
diff --git a/bin/sh/tests/builtins/case2.0 b/bin/sh/tests/builtins/case2.0
new file mode 100644
index 000000000000..e319148cdf5d
--- /dev/null
+++ b/bin/sh/tests/builtins/case2.0
@@ -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/bin/sh/tests/builtins/case20.0 b/bin/sh/tests/builtins/case20.0
new file mode 100644
index 000000000000..03a4eb2c9a23
--- /dev/null
+++ b/bin/sh/tests/builtins/case20.0
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+# Shells do not agree about what this pattern should match, but it is
+# certain that it must not crash and the missing close bracket must not
+# be simply ignored.
+
+case B in
+[[:alpha:]) echo bad ;;
+esac
diff --git a/bin/sh/tests/builtins/case21.0 b/bin/sh/tests/builtins/case21.0
new file mode 100644
index 000000000000..ea7fb054a546
--- /dev/null
+++ b/bin/sh/tests/builtins/case21.0
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+case 5 in
+[0$((-9))]) ;;
+*) echo bad1 ;;
+esac
+
+case - in
+[0$((-9))]) echo bad2 ;;
+esac
diff --git a/bin/sh/tests/builtins/case22.0 b/bin/sh/tests/builtins/case22.0
new file mode 100644
index 000000000000..ddc80051fa0a
--- /dev/null
+++ b/bin/sh/tests/builtins/case22.0
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+case 5 in
+[0"$((-9))"]) echo bad1 ;;
+esac
+
+case - in
+[0"$((-9))"]) ;;
+*) echo bad2 ;;
+esac
diff --git a/bin/sh/tests/builtins/case3.0 b/bin/sh/tests/builtins/case3.0
new file mode 100644
index 000000000000..42e53d62a3cb
--- /dev/null
+++ b/bin/sh/tests/builtins/case3.0
@@ -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/bin/sh/tests/builtins/case4.0 b/bin/sh/tests/builtins/case4.0
new file mode 100644
index 000000000000..18219f51ee09
--- /dev/null
+++ b/bin/sh/tests/builtins/case4.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+set -- "*"
+case x in
+"$1") echo failed ;;
+esac
diff --git a/bin/sh/tests/builtins/case5.0 b/bin/sh/tests/builtins/case5.0
new file mode 100644
index 000000000000..8c6db5ab8e57
--- /dev/null
+++ b/bin/sh/tests/builtins/case5.0
@@ -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/bin/sh/tests/builtins/case6.0 b/bin/sh/tests/builtins/case6.0
new file mode 100644
index 000000000000..8d791831c3d8
--- /dev/null
+++ b/bin/sh/tests/builtins/case6.0
@@ -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/bin/sh/tests/builtins/case7.0 b/bin/sh/tests/builtins/case7.0
new file mode 100644
index 000000000000..96b9de66fe27
--- /dev/null
+++ b/bin/sh/tests/builtins/case7.0
@@ -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/bin/sh/tests/builtins/case8.0 b/bin/sh/tests/builtins/case8.0
new file mode 100644
index 000000000000..8d9f8b604d88
--- /dev/null
+++ b/bin/sh/tests/builtins/case8.0
@@ -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/bin/sh/tests/builtins/case9.0 b/bin/sh/tests/builtins/case9.0
new file mode 100644
index 000000000000..476caec261ba
--- /dev/null
+++ b/bin/sh/tests/builtins/case9.0
@@ -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/bin/sh/tests/builtins/cd1.0 b/bin/sh/tests/builtins/cd1.0
new file mode 100644
index 000000000000..bc5108e6be3b
--- /dev/null
+++ b/bin/sh/tests/builtins/cd1.0
@@ -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/bin/sh/tests/builtins/cd10.0 b/bin/sh/tests/builtins/cd10.0
new file mode 100644
index 000000000000..f4b5e0ffca5c
--- /dev/null
+++ b/bin/sh/tests/builtins/cd10.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+# Precondition
+(cd /bin) || exit
+# Verify write error is ignored.
+$SH +m -ic 'CDPATH=/:; cd bin 1</dev/null'
diff --git a/bin/sh/tests/builtins/cd2.0 b/bin/sh/tests/builtins/cd2.0
new file mode 100644
index 000000000000..f2b6416a12ec
--- /dev/null
+++ b/bin/sh/tests/builtins/cd2.0
@@ -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/bin/sh/tests/builtins/cd3.0 b/bin/sh/tests/builtins/cd3.0
new file mode 100644
index 000000000000..7729c54a6895
--- /dev/null
+++ b/bin/sh/tests/builtins/cd3.0
@@ -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/bin/sh/tests/builtins/cd4.0 b/bin/sh/tests/builtins/cd4.0
new file mode 100644
index 000000000000..df3a9a48961c
--- /dev/null
+++ b/bin/sh/tests/builtins/cd4.0
@@ -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/bin/sh/tests/builtins/cd5.0 b/bin/sh/tests/builtins/cd5.0
new file mode 100644
index 000000000000..3dff604583c9
--- /dev/null
+++ b/bin/sh/tests/builtins/cd5.0
@@ -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/bin/sh/tests/builtins/cd6.0 b/bin/sh/tests/builtins/cd6.0
new file mode 100644
index 000000000000..083a06190e64
--- /dev/null
+++ b/bin/sh/tests/builtins/cd6.0
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+set -e
+cd -P /bin
+d=$PWD
+CDPATH=/:
+cd -P .
+[ "$d" = "$PWD" ]
+cd -P ./
+[ "$d" = "$PWD" ]
diff --git a/bin/sh/tests/builtins/cd7.0 b/bin/sh/tests/builtins/cd7.0
new file mode 100644
index 000000000000..9adda86c1aed
--- /dev/null
+++ b/bin/sh/tests/builtins/cd7.0
@@ -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/bin/sh/tests/builtins/cd8.0 b/bin/sh/tests/builtins/cd8.0
new file mode 100644
index 000000000000..a68f77f26f75
--- /dev/null
+++ b/bin/sh/tests/builtins/cd8.0
@@ -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/bin/sh/tests/builtins/cd9.0 b/bin/sh/tests/builtins/cd9.0
new file mode 100644
index 000000000000..78bcdff9a933
--- /dev/null
+++ b/bin/sh/tests/builtins/cd9.0
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+cd /dev
+cd /bin
+cd - >/dev/null
+pwd
+cd - >/dev/null
+pwd
diff --git a/bin/sh/tests/builtins/cd9.0.stdout b/bin/sh/tests/builtins/cd9.0.stdout
new file mode 100644
index 000000000000..dac16a768d5e
--- /dev/null
+++ b/bin/sh/tests/builtins/cd9.0.stdout
@@ -0,0 +1,2 @@
+/dev
+/bin
diff --git a/bin/sh/tests/builtins/command1.0 b/bin/sh/tests/builtins/command1.0
new file mode 100644
index 000000000000..fd0afdfa0300
--- /dev/null
+++ b/bin/sh/tests/builtins/command1.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+true() {
+ false
+}
+command true
diff --git a/bin/sh/tests/builtins/command10.0 b/bin/sh/tests/builtins/command10.0
new file mode 100644
index 000000000000..2c1adf1eb0bd
--- /dev/null
+++ b/bin/sh/tests/builtins/command10.0
@@ -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/bin/sh/tests/builtins/command11.0 b/bin/sh/tests/builtins/command11.0
new file mode 100644
index 000000000000..10c86479d81b
--- /dev/null
+++ b/bin/sh/tests/builtins/command11.0
@@ -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/bin/sh/tests/builtins/command12.0 b/bin/sh/tests/builtins/command12.0
new file mode 100644
index 000000000000..f981db324946
--- /dev/null
+++ b/bin/sh/tests/builtins/command12.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+alias aa=echo\ \'\"\'
+cmd=$(command -v aa)
+alias aa=echo\ bad
+eval "$cmd"
+[ "$(eval aa)" = \" ]
diff --git a/bin/sh/tests/builtins/command2.0 b/bin/sh/tests/builtins/command2.0
new file mode 100644
index 000000000000..ff7b5f2c8dc8
--- /dev/null
+++ b/bin/sh/tests/builtins/command2.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+PATH=
+command -p cat < /dev/null
diff --git a/bin/sh/tests/builtins/command3.0 b/bin/sh/tests/builtins/command3.0
new file mode 100644
index 000000000000..9d4ae89e0e2e
--- /dev/null
+++ b/bin/sh/tests/builtins/command3.0
@@ -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/bin/sh/tests/builtins/command3.0.stdout b/bin/sh/tests/builtins/command3.0.stdout
new file mode 100644
index 000000000000..67b869156a75
--- /dev/null
+++ b/bin/sh/tests/builtins/command3.0.stdout
@@ -0,0 +1,7 @@
+/bin/ls
+true
+/bin/ls
+fun
+break
+if
+alias foo=bar
diff --git a/bin/sh/tests/builtins/command4.0 b/bin/sh/tests/builtins/command4.0
new file mode 100644
index 000000000000..3e496137ff58
--- /dev/null
+++ b/bin/sh/tests/builtins/command4.0
@@ -0,0 +1,2 @@
+# $FreeBSD$
+! command -v nonexisting
diff --git a/bin/sh/tests/builtins/command5.0 b/bin/sh/tests/builtins/command5.0
new file mode 100644
index 000000000000..13b3fe1fe3f4
--- /dev/null
+++ b/bin/sh/tests/builtins/command5.0
@@ -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/bin/sh/tests/builtins/command5.0.stdout b/bin/sh/tests/builtins/command5.0.stdout
new file mode 100644
index 000000000000..523f7b22a187
--- /dev/null
+++ b/bin/sh/tests/builtins/command5.0.stdout
@@ -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/bin/sh/tests/builtins/command6.0 b/bin/sh/tests/builtins/command6.0
new file mode 100644
index 000000000000..5b63bfecc0f0
--- /dev/null
+++ b/bin/sh/tests/builtins/command6.0
@@ -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/bin/sh/tests/builtins/command6.0.stdout b/bin/sh/tests/builtins/command6.0.stdout
new file mode 100644
index 000000000000..3180207a4e2b
--- /dev/null
+++ b/bin/sh/tests/builtins/command6.0.stdout
@@ -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/bin/sh/tests/builtins/command7.0 b/bin/sh/tests/builtins/command7.0
new file mode 100644
index 000000000000..fc652f207584
--- /dev/null
+++ b/bin/sh/tests/builtins/command7.0
@@ -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/bin/sh/tests/builtins/command8.0 b/bin/sh/tests/builtins/command8.0
new file mode 100644
index 000000000000..9e3a2b645fbb
--- /dev/null
+++ b/bin/sh/tests/builtins/command8.0
@@ -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/bin/sh/tests/builtins/command9.0 b/bin/sh/tests/builtins/command9.0
new file mode 100644
index 000000000000..212e52aafc98
--- /dev/null
+++ b/bin/sh/tests/builtins/command9.0
@@ -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/bin/sh/tests/builtins/dot1.0 b/bin/sh/tests/builtins/dot1.0
new file mode 100644
index 000000000000..43eab0ddeb33
--- /dev/null
+++ b/bin/sh/tests/builtins/dot1.0
@@ -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/bin/sh/tests/builtins/dot2.0 b/bin/sh/tests/builtins/dot2.0
new file mode 100644
index 000000000000..ed6379b993c3
--- /dev/null
+++ b/bin/sh/tests/builtins/dot2.0
@@ -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/bin/sh/tests/builtins/dot3.0 b/bin/sh/tests/builtins/dot3.0
new file mode 100644
index 000000000000..b337f0f6d338
--- /dev/null
+++ b/bin/sh/tests/builtins/dot3.0
@@ -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/bin/sh/tests/builtins/dot4.0 b/bin/sh/tests/builtins/dot4.0
new file mode 100644
index 000000000000..b898131c0e1c
--- /dev/null
+++ b/bin/sh/tests/builtins/dot4.0
@@ -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/bin/sh/tests/builtins/echo1.0 b/bin/sh/tests/builtins/echo1.0
new file mode 100644
index 000000000000..75ed8fbbc45f
--- /dev/null
+++ b/bin/sh/tests/builtins/echo1.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+# Not specified by POSIX.
+
+[ "`echo -n a b; echo c d; echo e f`" = "a bc d
+e f" ]
diff --git a/bin/sh/tests/builtins/echo2.0 b/bin/sh/tests/builtins/echo2.0
new file mode 100644
index 000000000000..254b5c4dca4f
--- /dev/null
+++ b/bin/sh/tests/builtins/echo2.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+# Not specified by POSIX.
+
+a=`echo -e '\a\b\e\f\n\r\t\v\\\\\0041\c'; echo .`
+b=`printf '\a\b\033\f\n\r\t\v\\\\!.'`
+[ "$a" = "$b" ]
diff --git a/bin/sh/tests/builtins/echo3.0 b/bin/sh/tests/builtins/echo3.0
new file mode 100644
index 000000000000..ff8f6c2f26e4
--- /dev/null
+++ b/bin/sh/tests/builtins/echo3.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+# Not specified by POSIX.
+
+[ "`echo -e 'a\cb' c; echo d`" = "ad" ]
diff --git a/bin/sh/tests/builtins/eval1.0 b/bin/sh/tests/builtins/eval1.0
new file mode 100644
index 000000000000..04606a4a87fe
--- /dev/null
+++ b/bin/sh/tests/builtins/eval1.0
@@ -0,0 +1,9 @@
+# $FreeBSD$
+set -e
+
+eval
+eval "" ""
+eval "true"
+! eval "false
+
+"
diff --git a/bin/sh/tests/builtins/eval2.0 b/bin/sh/tests/builtins/eval2.0
new file mode 100644
index 000000000000..bf06b6e14e0c
--- /dev/null
+++ b/bin/sh/tests/builtins/eval2.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+eval '
+false
+
+' && exit 1
+exit 0
diff --git a/bin/sh/tests/builtins/eval3.0 b/bin/sh/tests/builtins/eval3.0
new file mode 100644
index 000000000000..dfb8357c727c
--- /dev/null
+++ b/bin/sh/tests/builtins/eval3.0
@@ -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/bin/sh/tests/builtins/eval4.0 b/bin/sh/tests/builtins/eval4.0
new file mode 100644
index 000000000000..67da2f5c832d
--- /dev/null
+++ b/bin/sh/tests/builtins/eval4.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+# eval should preserve $? from command substitutions when starting
+# the parsed command.
+[ $(eval 'echo $?' $(false)) = 1 ]
diff --git a/bin/sh/tests/builtins/eval5.0 b/bin/sh/tests/builtins/eval5.0
new file mode 100644
index 000000000000..3e86de92fbae
--- /dev/null
+++ b/bin/sh/tests/builtins/eval5.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+# eval should return 0 if no command was executed.
+eval $(false)
diff --git a/bin/sh/tests/builtins/eval6.0 b/bin/sh/tests/builtins/eval6.0
new file mode 100644
index 000000000000..6752bb65ad4d
--- /dev/null
+++ b/bin/sh/tests/builtins/eval6.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+# eval should preserve $? from command substitutions when starting
+# the parsed command.
+[ $(false; eval 'echo $?' $(:)) = 0 ]
diff --git a/bin/sh/tests/builtins/eval7.0 b/bin/sh/tests/builtins/eval7.0
new file mode 100644
index 000000000000..a309c917b102
--- /dev/null
+++ b/bin/sh/tests/builtins/eval7.0
@@ -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/bin/sh/tests/builtins/eval8.7 b/bin/sh/tests/builtins/eval8.7
new file mode 100644
index 000000000000..af6064c388df
--- /dev/null
+++ b/bin/sh/tests/builtins/eval8.7
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+f() {
+ eval "return 7
+echo bad2"
+}
+f
diff --git a/bin/sh/tests/builtins/exec1.0 b/bin/sh/tests/builtins/exec1.0
new file mode 100644
index 000000000000..dd30a4c9aa93
--- /dev/null
+++ b/bin/sh/tests/builtins/exec1.0
@@ -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/bin/sh/tests/builtins/exec2.0 b/bin/sh/tests/builtins/exec2.0
new file mode 100644
index 000000000000..3dcb6c41156a
--- /dev/null
+++ b/bin/sh/tests/builtins/exec2.0
@@ -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/bin/sh/tests/builtins/exit1.0 b/bin/sh/tests/builtins/exit1.0
new file mode 100644
index 000000000000..496d448761cc
--- /dev/null
+++ b/bin/sh/tests/builtins/exit1.0
@@ -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/bin/sh/tests/builtins/exit2.8 b/bin/sh/tests/builtins/exit2.8
new file mode 100644
index 000000000000..124c32e01a3c
--- /dev/null
+++ b/bin/sh/tests/builtins/exit2.8
@@ -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/bin/sh/tests/builtins/exit3.0 b/bin/sh/tests/builtins/exit3.0
new file mode 100644
index 000000000000..80655ac539c5
--- /dev/null
+++ b/bin/sh/tests/builtins/exit3.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+# exit without arguments differs from exit $? in an EXIT trap.
+
+trap 'false; exit' 0
diff --git a/bin/sh/tests/builtins/export1.0 b/bin/sh/tests/builtins/export1.0
new file mode 100644
index 000000000000..7b08c9de4349
--- /dev/null
+++ b/bin/sh/tests/builtins/export1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+env @badness=1 ${SH} -c 'v=`export -p`; eval "$v"'
diff --git a/bin/sh/tests/builtins/fc1.0 b/bin/sh/tests/builtins/fc1.0
new file mode 100644
index 000000000000..ab7a387b5077
--- /dev/null
+++ b/bin/sh/tests/builtins/fc1.0
@@ -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/bin/sh/tests/builtins/fc2.0 b/bin/sh/tests/builtins/fc2.0
new file mode 100644
index 000000000000..7eb92acc2cdf
--- /dev/null
+++ b/bin/sh/tests/builtins/fc2.0
@@ -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/bin/sh/tests/builtins/for1.0 b/bin/sh/tests/builtins/for1.0
new file mode 100644
index 000000000000..cd55e2ca029e
--- /dev/null
+++ b/bin/sh/tests/builtins/for1.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+false
+for i in `false`; do exit 3; done
diff --git a/bin/sh/tests/builtins/for2.0 b/bin/sh/tests/builtins/for2.0
new file mode 100644
index 000000000000..48c22ce71119
--- /dev/null
+++ b/bin/sh/tests/builtins/for2.0
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+r=x
+f() { return 42; }
+f
+for i in x; do
+ r=$?
+done
+[ "$r" = 42 ]
diff --git a/bin/sh/tests/builtins/for3.0 b/bin/sh/tests/builtins/for3.0
new file mode 100644
index 000000000000..cc37238abbc6
--- /dev/null
+++ b/bin/sh/tests/builtins/for3.0
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+r=x
+f() { return 42; }
+for i in x`f`; do
+ r=$?
+done
+[ "$r" = 42 ]
diff --git a/bin/sh/tests/builtins/getopts1.0 b/bin/sh/tests/builtins/getopts1.0
new file mode 100644
index 000000000000..10d2b59208e5
--- /dev/null
+++ b/bin/sh/tests/builtins/getopts1.0
@@ -0,0 +1,25 @@
+# $FreeBSD$
+
+printf -- '-1-\n'
+set -- -abc
+getopts "ab:" OPTION
+printf '%s\n' "${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
+printf '%s\n' "${OPTION}"
+getopts "ab:" OPTION 3>&2 2>&1 >&3 3>&-
+printf '%s\n' "${OPTION}"
+
+# The 'shift' is aimed at causing an error.
+printf -- '-3-\n'
+shift 1
+getopts "ab:" OPTION
+printf '%s\n' "${OPTION}"
diff --git a/bin/sh/tests/builtins/getopts1.0.stdout b/bin/sh/tests/builtins/getopts1.0.stdout
new file mode 100644
index 000000000000..a0a347eea8f8
--- /dev/null
+++ b/bin/sh/tests/builtins/getopts1.0.stdout
@@ -0,0 +1,8 @@
+-1-
+a
+-2-
+a
+No arg for -b option
+?
+-3-
+?
diff --git a/bin/sh/tests/builtins/getopts10.0 b/bin/sh/tests/builtins/getopts10.0
new file mode 100644
index 000000000000..a88e6ca3e85e
--- /dev/null
+++ b/bin/sh/tests/builtins/getopts10.0
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+set -- -x arg
+opt=not
+getopts x opt
+r1=$? OPTIND1=$OPTIND opt1=$opt
+: $(: $((OPTIND = 1)))
+getopts x opt
+r2=$? OPTIND2=$OPTIND
+[ "$r1" = 0 ] && [ "$OPTIND1" = 2 ] && [ "$opt1" = x ] && [ "$r2" != 0 ] &&
+ [ "$OPTIND2" = 2 ]
diff --git a/bin/sh/tests/builtins/getopts2.0 b/bin/sh/tests/builtins/getopts2.0
new file mode 100644
index 000000000000..1bd2c32db81c
--- /dev/null
+++ b/bin/sh/tests/builtins/getopts2.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+set - -ax
+getopts ax option
+set -C
+getopts ax option
+printf '%s\n' "$option"
diff --git a/bin/sh/tests/builtins/getopts2.0.stdout b/bin/sh/tests/builtins/getopts2.0.stdout
new file mode 100644
index 000000000000..587be6b4c3f9
--- /dev/null
+++ b/bin/sh/tests/builtins/getopts2.0.stdout
@@ -0,0 +1 @@
+x
diff --git a/bin/sh/tests/builtins/getopts3.0 b/bin/sh/tests/builtins/getopts3.0
new file mode 100644
index 000000000000..d02469b20469
--- /dev/null
+++ b/bin/sh/tests/builtins/getopts3.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+shift $#
+getopts x opt
+r=$?
+[ "$r" != 0 ] && [ "$OPTIND" = 1 ]
diff --git a/bin/sh/tests/builtins/getopts4.0 b/bin/sh/tests/builtins/getopts4.0
new file mode 100644
index 000000000000..61d5c2b6b15c
--- /dev/null
+++ b/bin/sh/tests/builtins/getopts4.0
@@ -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/bin/sh/tests/builtins/getopts5.0 b/bin/sh/tests/builtins/getopts5.0
new file mode 100644
index 000000000000..666ee7652de5
--- /dev/null
+++ b/bin/sh/tests/builtins/getopts5.0
@@ -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/bin/sh/tests/builtins/getopts6.0 b/bin/sh/tests/builtins/getopts6.0
new file mode 100644
index 000000000000..1d3c39ba953e
--- /dev/null
+++ b/bin/sh/tests/builtins/getopts6.0
@@ -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/bin/sh/tests/builtins/getopts7.0 b/bin/sh/tests/builtins/getopts7.0
new file mode 100644
index 000000000000..3745555f8c1a
--- /dev/null
+++ b/bin/sh/tests/builtins/getopts7.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+set -- -x
+getopts :x: var
+r=$?
+[ r != 0 ] && [ "$OPTIND" = 2 ]
diff --git a/bin/sh/tests/builtins/getopts8.0 b/bin/sh/tests/builtins/getopts8.0
new file mode 100644
index 000000000000..da4af6bd0b56
--- /dev/null
+++ b/bin/sh/tests/builtins/getopts8.0
@@ -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/bin/sh/tests/builtins/getopts8.0.stdout b/bin/sh/tests/builtins/getopts8.0.stdout
new file mode 100644
index 000000000000..f10cefcd8c1b
--- /dev/null
+++ b/bin/sh/tests/builtins/getopts8.0.stdout
@@ -0,0 +1,5 @@
+?:y
+?:z
+?:w
+x:unset
+OPTIND=3
diff --git a/bin/sh/tests/builtins/getopts9.0 b/bin/sh/tests/builtins/getopts9.0
new file mode 100644
index 000000000000..1c35fc68c2d1
--- /dev/null
+++ b/bin/sh/tests/builtins/getopts9.0
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+args='-ab'
+getopts ab opt $args
+printf '%s\n' "$?:$opt:$OPTARG"
+for dummy in dummy1 dummy2; do
+ getopts ab opt $args
+ printf '%s\n' "$?:$opt:$OPTARG"
+done
diff --git a/bin/sh/tests/builtins/getopts9.0.stdout b/bin/sh/tests/builtins/getopts9.0.stdout
new file mode 100644
index 000000000000..4d32063faa4d
--- /dev/null
+++ b/bin/sh/tests/builtins/getopts9.0.stdout
@@ -0,0 +1,3 @@
+0:a:
+0:b:
+1:?:
diff --git a/bin/sh/tests/builtins/hash1.0 b/bin/sh/tests/builtins/hash1.0
new file mode 100644
index 000000000000..45cc3003fb59
--- /dev/null
+++ b/bin/sh/tests/builtins/hash1.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+cat /dev/null
+hash
+hash -r
+hash
diff --git a/bin/sh/tests/builtins/hash1.0.stdout b/bin/sh/tests/builtins/hash1.0.stdout
new file mode 100644
index 000000000000..3afc3e7b3839
--- /dev/null
+++ b/bin/sh/tests/builtins/hash1.0.stdout
@@ -0,0 +1 @@
+/bin/cat
diff --git a/bin/sh/tests/builtins/hash2.0 b/bin/sh/tests/builtins/hash2.0
new file mode 100644
index 000000000000..e5cd21bc57dd
--- /dev/null
+++ b/bin/sh/tests/builtins/hash2.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+hash
+hash cat
+hash
diff --git a/bin/sh/tests/builtins/hash2.0.stdout b/bin/sh/tests/builtins/hash2.0.stdout
new file mode 100644
index 000000000000..3afc3e7b3839
--- /dev/null
+++ b/bin/sh/tests/builtins/hash2.0.stdout
@@ -0,0 +1 @@
+/bin/cat
diff --git a/bin/sh/tests/builtins/hash3.0 b/bin/sh/tests/builtins/hash3.0
new file mode 100644
index 000000000000..eade0b319546
--- /dev/null
+++ b/bin/sh/tests/builtins/hash3.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+hash -v cat
+hash
diff --git a/bin/sh/tests/builtins/hash3.0.stdout b/bin/sh/tests/builtins/hash3.0.stdout
new file mode 100644
index 000000000000..a34864cd6dff
--- /dev/null
+++ b/bin/sh/tests/builtins/hash3.0.stdout
@@ -0,0 +1,2 @@
+/bin/cat
+/bin/cat
diff --git a/bin/sh/tests/builtins/hash4.0 b/bin/sh/tests/builtins/hash4.0
new file mode 100644
index 000000000000..dec584c4538f
--- /dev/null
+++ b/bin/sh/tests/builtins/hash4.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+exec 3>&1
+m=`hash nosuchtool 2>&1 >&3`
+r=$?
+[ "$r" != 0 ] && [ -n "$m" ]
diff --git a/bin/sh/tests/builtins/jobid1.0 b/bin/sh/tests/builtins/jobid1.0
new file mode 100644
index 000000000000..483fda20743c
--- /dev/null
+++ b/bin/sh/tests/builtins/jobid1.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+# Non-standard builtin.
+
+: &
+p1=$!
+p2=$(jobid)
+[ "${p1:?}" = "${p2:?}" ]
diff --git a/bin/sh/tests/builtins/jobid2.0 b/bin/sh/tests/builtins/jobid2.0
new file mode 100644
index 000000000000..101831a2e4b8
--- /dev/null
+++ b/bin/sh/tests/builtins/jobid2.0
@@ -0,0 +1,9 @@
+# $FreeBSD$
+
+: &
+p1=$(jobid)
+p2=$(jobid --)
+p3=$(jobid %+)
+p4=$(jobid -- %+)
+[ "${p1:?}" = "${p2:?}" ] && [ "${p2:?}" = "${p3:?}" ] &&
+[ "${p3:?}" = "${p4:?}" ] && [ "${p4:?}" = "${p1:?}" ]
diff --git a/bin/sh/tests/builtins/kill1.0 b/bin/sh/tests/builtins/kill1.0
new file mode 100644
index 000000000000..c1b85503848e
--- /dev/null
+++ b/bin/sh/tests/builtins/kill1.0
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+: &
+p1=$!
+: &
+p2=$!
+wait $p2
+kill %1
diff --git a/bin/sh/tests/builtins/kill2.0 b/bin/sh/tests/builtins/kill2.0
new file mode 100644
index 000000000000..31e0ba362b80
--- /dev/null
+++ b/bin/sh/tests/builtins/kill2.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+sleep 1 | sleep 1 &
+kill %+
+wait "$!"
+r=$?
+[ "$r" -gt 128 ] && [ "$(kill -l "$r")" = TERM ]
diff --git a/bin/sh/tests/builtins/lineno.0 b/bin/sh/tests/builtins/lineno.0
new file mode 100644
index 000000000000..c9311f82d253
--- /dev/null
+++ b/bin/sh/tests/builtins/lineno.0
@@ -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/bin/sh/tests/builtins/lineno.0.stdout b/bin/sh/tests/builtins/lineno.0.stdout
new file mode 100644
index 000000000000..82583a93c82e
--- /dev/null
+++ b/bin/sh/tests/builtins/lineno.0.stdout
@@ -0,0 +1,9 @@
+2
+3
+2
+3
+12
+13
+foo
+foo
+2
diff --git a/bin/sh/tests/builtins/lineno2.0 b/bin/sh/tests/builtins/lineno2.0
new file mode 100644
index 000000000000..ddbd10433a36
--- /dev/null
+++ b/bin/sh/tests/builtins/lineno2.0
@@ -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/bin/sh/tests/builtins/lineno3.0 b/bin/sh/tests/builtins/lineno3.0
new file mode 100644
index 000000000000..eb8f9ab7db94
--- /dev/null
+++ b/bin/sh/tests/builtins/lineno3.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+echo before: $LINENO
+dummy=$'a\0
+'
+echo after: $LINENO
diff --git a/bin/sh/tests/builtins/lineno3.0.stdout b/bin/sh/tests/builtins/lineno3.0.stdout
new file mode 100644
index 000000000000..6e0e4ac8ca57
--- /dev/null
+++ b/bin/sh/tests/builtins/lineno3.0.stdout
@@ -0,0 +1,2 @@
+before: 3
+after: 6
diff --git a/bin/sh/tests/builtins/local1.0 b/bin/sh/tests/builtins/local1.0
new file mode 100644
index 000000000000..b28837ec005f
--- /dev/null
+++ b/bin/sh/tests/builtins/local1.0
@@ -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/bin/sh/tests/builtins/local2.0 b/bin/sh/tests/builtins/local2.0
new file mode 100644
index 000000000000..cc8c10f40618
--- /dev/null
+++ b/bin/sh/tests/builtins/local2.0
@@ -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/bin/sh/tests/builtins/local3.0 b/bin/sh/tests/builtins/local3.0
new file mode 100644
index 000000000000..39ee370099e7
--- /dev/null
+++ b/bin/sh/tests/builtins/local3.0
@@ -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/bin/sh/tests/builtins/local4.0 b/bin/sh/tests/builtins/local4.0
new file mode 100644
index 000000000000..3955aaa12f1a
--- /dev/null
+++ b/bin/sh/tests/builtins/local4.0
@@ -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/bin/sh/tests/builtins/local5.0 b/bin/sh/tests/builtins/local5.0
new file mode 100644
index 000000000000..2f2a14e110ae
--- /dev/null
+++ b/bin/sh/tests/builtins/local5.0
@@ -0,0 +1,15 @@
+# $FreeBSD$
+
+f() {
+ local PATH IFS elem
+ IFS=:
+ for elem in ''$PATH''; do
+ PATH=/var/empty/$elem:$PATH
+ done
+ ls -d / >/dev/null
+}
+
+p1=$(command -v ls)
+f
+p2=$(command -v ls)
+[ "$p1" = "$p2" ]
diff --git a/bin/sh/tests/builtins/local6.0 b/bin/sh/tests/builtins/local6.0
new file mode 100644
index 000000000000..017bb1234c8c
--- /dev/null
+++ b/bin/sh/tests/builtins/local6.0
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+f() {
+ local x
+ readonly x=2
+}
+x=3
+f
+x=4
+[ "$x" = 4 ]
diff --git a/bin/sh/tests/builtins/local7.0 b/bin/sh/tests/builtins/local7.0
new file mode 100644
index 000000000000..f7e6fc0aae97
--- /dev/null
+++ b/bin/sh/tests/builtins/local7.0
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+f() {
+ local x
+ readonly x=2
+}
+unset x
+f
+x=4
+[ "$x" = 4 ]
diff --git a/bin/sh/tests/builtins/locale1.0 b/bin/sh/tests/builtins/locale1.0
new file mode 100644
index 000000000000..90b10944c5a5
--- /dev/null
+++ b/bin/sh/tests/builtins/locale1.0
@@ -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/bin/sh/tests/builtins/locale2.0 b/bin/sh/tests/builtins/locale2.0
new file mode 100644
index 000000000000..86dd237ff54a
--- /dev/null
+++ b/bin/sh/tests/builtins/locale2.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+$SH -c 'LC_ALL=C true; kill -INT $$; echo continued'
+r=$?
+[ "$r" -gt 128 ] && [ "$(kill -l "$r")" = INT ]
diff --git a/bin/sh/tests/builtins/printf1.0 b/bin/sh/tests/builtins/printf1.0
new file mode 100644
index 000000000000..99a82d014794
--- /dev/null
+++ b/bin/sh/tests/builtins/printf1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+[ "$(printf '%c\0%s%d' x '\' 010 | tr '\0' Z)" = 'xZ\8' ]
diff --git a/bin/sh/tests/builtins/printf2.0 b/bin/sh/tests/builtins/printf2.0
new file mode 100644
index 000000000000..7763d6fe9636
--- /dev/null
+++ b/bin/sh/tests/builtins/printf2.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+[ "$(printf '%cZ%s%d' x '\' 010)" = 'xZ\8' ]
diff --git a/bin/sh/tests/builtins/printf3.0 b/bin/sh/tests/builtins/printf3.0
new file mode 100644
index 000000000000..0e7ea85cddb4
--- /dev/null
+++ b/bin/sh/tests/builtins/printf3.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+set -e
+v=$(! printf "%d" @wrong 2>/dev/null)
+[ "$v" = "0" ]
diff --git a/bin/sh/tests/builtins/printf4.0 b/bin/sh/tests/builtins/printf4.0
new file mode 100644
index 000000000000..2dd3e729574a
--- /dev/null
+++ b/bin/sh/tests/builtins/printf4.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+set -e
+v=$(! printf "%d" 4wrong 2>/dev/null)
+[ "$v" = "4" ]
diff --git a/bin/sh/tests/builtins/read1.0 b/bin/sh/tests/builtins/read1.0
new file mode 100644
index 000000000000..06a68faa32a3
--- /dev/null
+++ b/bin/sh/tests/builtins/read1.0
@@ -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/bin/sh/tests/builtins/read1.0.stdout b/bin/sh/tests/builtins/read1.0.stdout
new file mode 100644
index 000000000000..dbcb1af9bae7
--- /dev/null
+++ b/bin/sh/tests/builtins/read1.0.stdout
@@ -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/bin/sh/tests/builtins/read2.0 b/bin/sh/tests/builtins/read2.0
new file mode 100644
index 000000000000..fc7451191586
--- /dev/null
+++ b/bin/sh/tests/builtins/read2.0
@@ -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/bin/sh/tests/builtins/read3.0 b/bin/sh/tests/builtins/read3.0
new file mode 100644
index 000000000000..c6ae9c1023c0
--- /dev/null
+++ b/bin/sh/tests/builtins/read3.0
@@ -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/bin/sh/tests/builtins/read3.0.stdout b/bin/sh/tests/builtins/read3.0.stdout
new file mode 100644
index 000000000000..8ed98ca9d86e
--- /dev/null
+++ b/bin/sh/tests/builtins/read3.0.stdout
@@ -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/bin/sh/tests/builtins/read4.0 b/bin/sh/tests/builtins/read4.0
new file mode 100644
index 000000000000..7204a35aa0a5
--- /dev/null
+++ b/bin/sh/tests/builtins/read4.0
@@ -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/bin/sh/tests/builtins/read4.0.stdout b/bin/sh/tests/builtins/read4.0.stdout
new file mode 100644
index 000000000000..a8747a46ffeb
--- /dev/null
+++ b/bin/sh/tests/builtins/read4.0.stdout
@@ -0,0 +1,8 @@
+xa bxcx
+xaxb cx
+xa:bxcx
+xaxb:cx
+x\xax
+x\xax
+x\ axx
+x\:axx
diff --git a/bin/sh/tests/builtins/read5.0 b/bin/sh/tests/builtins/read5.0
new file mode 100644
index 000000000000..7d83391cba22
--- /dev/null
+++ b/bin/sh/tests/builtins/read5.0
@@ -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/bin/sh/tests/builtins/read6.0 b/bin/sh/tests/builtins/read6.0
new file mode 100644
index 000000000000..2168e10c841a
--- /dev/null
+++ b/bin/sh/tests/builtins/read6.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+: | read x
+r=$?
+[ "$r" = 1 ]
diff --git a/bin/sh/tests/builtins/read7.0 b/bin/sh/tests/builtins/read7.0
new file mode 100644
index 000000000000..e78f887b60b4
--- /dev/null
+++ b/bin/sh/tests/builtins/read7.0
@@ -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/bin/sh/tests/builtins/read8.0 b/bin/sh/tests/builtins/read8.0
new file mode 100644
index 000000000000..fb786ff008b9
--- /dev/null
+++ b/bin/sh/tests/builtins/read8.0
@@ -0,0 +1,17 @@
+# $FreeBSD$
+
+read a b c <<\EOF
+\
+A\
+ \
+ \
+ \
+B\
+ \
+ \
+C\
+ \
+ \
+ \
+EOF
+[ "$a.$b.$c" = "A.B.C" ]
diff --git a/bin/sh/tests/builtins/read9.0 b/bin/sh/tests/builtins/read9.0
new file mode 100644
index 000000000000..080549889839
--- /dev/null
+++ b/bin/sh/tests/builtins/read9.0
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+empty=''
+read a b c <<EOF
+\ \ A B\ \ B C\ \ $empty
+EOF
+read d e <<EOF
+D\ $empty
+EOF
+[ "$a.$b.$c.$d.$e" = " A.B B.C .D ." ]
diff --git a/bin/sh/tests/builtins/return1.0 b/bin/sh/tests/builtins/return1.0
new file mode 100644
index 000000000000..787e892a7698
--- /dev/null
+++ b/bin/sh/tests/builtins/return1.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+f() {
+ return 0
+ exit 1
+}
+
+f
diff --git a/bin/sh/tests/builtins/return2.1 b/bin/sh/tests/builtins/return2.1
new file mode 100644
index 000000000000..0ef817179d73
--- /dev/null
+++ b/bin/sh/tests/builtins/return2.1
@@ -0,0 +1,7 @@
+# $FreeBSD$
+f() {
+ true && return 1
+ return 0
+}
+
+f
diff --git a/bin/sh/tests/builtins/return3.1 b/bin/sh/tests/builtins/return3.1
new file mode 100644
index 000000000000..605ec680d66a
--- /dev/null
+++ b/bin/sh/tests/builtins/return3.1
@@ -0,0 +1,3 @@
+# $FreeBSD$
+return 1
+exit 0
diff --git a/bin/sh/tests/builtins/return4.0 b/bin/sh/tests/builtins/return4.0
new file mode 100644
index 000000000000..be5582b458b4
--- /dev/null
+++ b/bin/sh/tests/builtins/return4.0
@@ -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/bin/sh/tests/builtins/return5.0 b/bin/sh/tests/builtins/return5.0
new file mode 100644
index 000000000000..6e4b7bd751a8
--- /dev/null
+++ b/bin/sh/tests/builtins/return5.0
@@ -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/bin/sh/tests/builtins/return6.4 b/bin/sh/tests/builtins/return6.4
new file mode 100644
index 000000000000..e4d8e0d641a1
--- /dev/null
+++ b/bin/sh/tests/builtins/return6.4
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+while return 4; do exit 3; done
diff --git a/bin/sh/tests/builtins/return7.4 b/bin/sh/tests/builtins/return7.4
new file mode 100644
index 000000000000..2047373473ab
--- /dev/null
+++ b/bin/sh/tests/builtins/return7.4
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+f() {
+ while return 4; do exit 3; done
+}
+f
diff --git a/bin/sh/tests/builtins/return8.0 b/bin/sh/tests/builtins/return8.0
new file mode 100644
index 000000000000..f00e859a74ce
--- /dev/null
+++ b/bin/sh/tests/builtins/return8.0
@@ -0,0 +1,13 @@
+# $FreeBSD$
+
+if [ "$1" = nested ]; then
+ return 17
+fi
+
+f() {
+ set -- nested
+ . "$0"
+ return $(($? ^ 1))
+}
+f
+exit $(($? ^ 16))
diff --git a/bin/sh/tests/builtins/set1.0 b/bin/sh/tests/builtins/set1.0
new file mode 100644
index 000000000000..fc39fade6270
--- /dev/null
+++ b/bin/sh/tests/builtins/set1.0
@@ -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/bin/sh/tests/builtins/set2.0 b/bin/sh/tests/builtins/set2.0
new file mode 100644
index 000000000000..ad13eab0a7b8
--- /dev/null
+++ b/bin/sh/tests/builtins/set2.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+! env @badness=1 ${SH} -c 'v=`set`; eval "$v"' 2>&1 | grep @badness
diff --git a/bin/sh/tests/builtins/set3.0 b/bin/sh/tests/builtins/set3.0
new file mode 100644
index 000000000000..c5536e96b7a1
--- /dev/null
+++ b/bin/sh/tests/builtins/set3.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+settings1=$(set +o) && set -o nolog && settings2=$(set +o) &&
+[ "$settings1" != "$settings2" ]
diff --git a/bin/sh/tests/builtins/trap1.0 b/bin/sh/tests/builtins/trap1.0
new file mode 100644
index 000000000000..313f6a387678
--- /dev/null
+++ b/bin/sh/tests/builtins/trap1.0
@@ -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/bin/sh/tests/builtins/trap10.0 b/bin/sh/tests/builtins/trap10.0
new file mode 100644
index 000000000000..fa0e35d6a6b2
--- /dev/null
+++ b/bin/sh/tests/builtins/trap10.0
@@ -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/bin/sh/tests/builtins/trap11.0 b/bin/sh/tests/builtins/trap11.0
new file mode 100644
index 000000000000..cfeea9ed9ded
--- /dev/null
+++ b/bin/sh/tests/builtins/trap11.0
@@ -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/bin/sh/tests/builtins/trap12.0 b/bin/sh/tests/builtins/trap12.0
new file mode 100644
index 000000000000..8c62ffd2b570
--- /dev/null
+++ b/bin/sh/tests/builtins/trap12.0
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+f() {
+ trap 'return 42' USR1
+ kill -USR1 $$
+ return 3
+}
+f
+r=$?
+[ "$r" = 42 ]
diff --git a/bin/sh/tests/builtins/trap13.0 b/bin/sh/tests/builtins/trap13.0
new file mode 100644
index 000000000000..d90eb08eb242
--- /dev/null
+++ b/bin/sh/tests/builtins/trap13.0
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+{
+ trap 'exit 0' INT
+ ${SH} -c 'kill -INT $PPID'
+ exit 3
+} &
+wait $!
diff --git a/bin/sh/tests/builtins/trap14.0 b/bin/sh/tests/builtins/trap14.0
new file mode 100644
index 000000000000..97cce8d0d244
--- /dev/null
+++ b/bin/sh/tests/builtins/trap14.0
@@ -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/bin/sh/tests/builtins/trap15.0 b/bin/sh/tests/builtins/trap15.0
new file mode 100644
index 000000000000..6b9857df3d1a
--- /dev/null
+++ b/bin/sh/tests/builtins/trap15.0
@@ -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/bin/sh/tests/builtins/trap16.0 b/bin/sh/tests/builtins/trap16.0
new file mode 100644
index 000000000000..3d70cce8a721
--- /dev/null
+++ b/bin/sh/tests/builtins/trap16.0
@@ -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/bin/sh/tests/builtins/trap17.0 b/bin/sh/tests/builtins/trap17.0
new file mode 100644
index 000000000000..89be893edd1a
--- /dev/null
+++ b/bin/sh/tests/builtins/trap17.0
@@ -0,0 +1,10 @@
+# $FreeBSD$
+# This use-after-free bug probably needs non-default settings to show up.
+
+v1=nothing v2=nothing
+trap 'trap "echo bad" USR1
+v1=trap_received
+v2=trap_invoked
+:' USR1
+kill -USR1 "$$"
+[ "$v1.$v2" = trap_received.trap_invoked ]
diff --git a/bin/sh/tests/builtins/trap2.0 b/bin/sh/tests/builtins/trap2.0
new file mode 100644
index 000000000000..a05287a16771
--- /dev/null
+++ b/bin/sh/tests/builtins/trap2.0
@@ -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/bin/sh/tests/builtins/trap3.0 b/bin/sh/tests/builtins/trap3.0
new file mode 100644
index 000000000000..81607293531e
--- /dev/null
+++ b/bin/sh/tests/builtins/trap3.0
@@ -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/bin/sh/tests/builtins/trap4.0 b/bin/sh/tests/builtins/trap4.0
new file mode 100644
index 000000000000..7f2080ee4dca
--- /dev/null
+++ b/bin/sh/tests/builtins/trap4.0
@@ -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/bin/sh/tests/builtins/trap5.0 b/bin/sh/tests/builtins/trap5.0
new file mode 100644
index 000000000000..56e0fb1b89fe
--- /dev/null
+++ b/bin/sh/tests/builtins/trap5.0
@@ -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/bin/sh/tests/builtins/trap6.0 b/bin/sh/tests/builtins/trap6.0
new file mode 100644
index 000000000000..bd2bf7efe769
--- /dev/null
+++ b/bin/sh/tests/builtins/trap6.0
@@ -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/bin/sh/tests/builtins/trap7.0 b/bin/sh/tests/builtins/trap7.0
new file mode 100644
index 000000000000..35529b80f4bf
--- /dev/null
+++ b/bin/sh/tests/builtins/trap7.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+[ "$(trap 'echo trapped' EXIT)" = trapped ]
diff --git a/bin/sh/tests/builtins/trap8.0 b/bin/sh/tests/builtins/trap8.0
new file mode 100644
index 000000000000..cdce976e36da
--- /dev/null
+++ b/bin/sh/tests/builtins/trap8.0
@@ -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/bin/sh/tests/builtins/trap9.0 b/bin/sh/tests/builtins/trap9.0
new file mode 100644
index 000000000000..0f584ecec584
--- /dev/null
+++ b/bin/sh/tests/builtins/trap9.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+test "$(trap 'printf trap; echo ped' EXIT; f() { :; }; f)" = trapped || exit 1
diff --git a/bin/sh/tests/builtins/type1.0 b/bin/sh/tests/builtins/type1.0
new file mode 100644
index 000000000000..c5e456437946
--- /dev/null
+++ b/bin/sh/tests/builtins/type1.0
@@ -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/bin/sh/tests/builtins/type1.0.stderr b/bin/sh/tests/builtins/type1.0.stderr
new file mode 100644
index 000000000000..7853418a4827
--- /dev/null
+++ b/bin/sh/tests/builtins/type1.0.stderr
@@ -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/bin/sh/tests/builtins/type2.0 b/bin/sh/tests/builtins/type2.0
new file mode 100644
index 000000000000..fe44d957fc0a
--- /dev/null
+++ b/bin/sh/tests/builtins/type2.0
@@ -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/bin/sh/tests/builtins/type3.0 b/bin/sh/tests/builtins/type3.0
new file mode 100644
index 000000000000..87cccdd812cc
--- /dev/null
+++ b/bin/sh/tests/builtins/type3.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+[ "$(type type)" = "$(type -- type)" ]
diff --git a/bin/sh/tests/builtins/unalias.0 b/bin/sh/tests/builtins/unalias.0
new file mode 100644
index 000000000000..34d8d6e07d2e
--- /dev/null
+++ b/bin/sh/tests/builtins/unalias.0
@@ -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/bin/sh/tests/builtins/var-assign.0 b/bin/sh/tests/builtins/var-assign.0
new file mode 100644
index 000000000000..ace39c042d5f
--- /dev/null
+++ b/bin/sh/tests/builtins/var-assign.0
@@ -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/bin/sh/tests/builtins/var-assign2.0 b/bin/sh/tests/builtins/var-assign2.0
new file mode 100644
index 000000000000..eafec89a681b
--- /dev/null
+++ b/bin/sh/tests/builtins/var-assign2.0
@@ -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/bin/sh/tests/builtins/wait1.0 b/bin/sh/tests/builtins/wait1.0
new file mode 100644
index 000000000000..1ca85308c9ac
--- /dev/null
+++ b/bin/sh/tests/builtins/wait1.0
@@ -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/bin/sh/tests/builtins/wait10.0 b/bin/sh/tests/builtins/wait10.0
new file mode 100644
index 000000000000..864fc7817276
--- /dev/null
+++ b/bin/sh/tests/builtins/wait10.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+# Init cannot be a child of the shell.
+exit 49 & p49=$!
+wait 1 "$p49"
+[ "$?" = 49 ]
diff --git a/bin/sh/tests/builtins/wait2.0 b/bin/sh/tests/builtins/wait2.0
new file mode 100644
index 000000000000..e61455cf5f0e
--- /dev/null
+++ b/bin/sh/tests/builtins/wait2.0
@@ -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/bin/sh/tests/builtins/wait3.0 b/bin/sh/tests/builtins/wait3.0
new file mode 100644
index 000000000000..1ed52999630d
--- /dev/null
+++ b/bin/sh/tests/builtins/wait3.0
@@ -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/bin/sh/tests/builtins/wait4.0 b/bin/sh/tests/builtins/wait4.0
new file mode 100644
index 000000000000..79351315d384
--- /dev/null
+++ b/bin/sh/tests/builtins/wait4.0
@@ -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/bin/sh/tests/builtins/wait5.0 b/bin/sh/tests/builtins/wait5.0
new file mode 100644
index 000000000000..6874bf669302
--- /dev/null
+++ b/bin/sh/tests/builtins/wait5.0
@@ -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/bin/sh/tests/builtins/wait6.0 b/bin/sh/tests/builtins/wait6.0
new file mode 100644
index 000000000000..20e3c6808679
--- /dev/null
+++ b/bin/sh/tests/builtins/wait6.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+wait --
diff --git a/bin/sh/tests/builtins/wait7.0 b/bin/sh/tests/builtins/wait7.0
new file mode 100644
index 000000000000..0fb092f8f614
--- /dev/null
+++ b/bin/sh/tests/builtins/wait7.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+: &
+wait -- $!
diff --git a/bin/sh/tests/builtins/wait8.0 b/bin/sh/tests/builtins/wait8.0
new file mode 100644
index 000000000000..b59ff59622eb
--- /dev/null
+++ b/bin/sh/tests/builtins/wait8.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+exit 44 & p44=$!
+exit 45 & p45=$!
+exit 7 & p7=$!
+wait "$p44" "$p7" "$p45"
+[ "$?" = 45 ]
diff --git a/bin/sh/tests/builtins/wait9.127 b/bin/sh/tests/builtins/wait9.127
new file mode 100644
index 000000000000..661f275421f8
--- /dev/null
+++ b/bin/sh/tests/builtins/wait9.127
@@ -0,0 +1,3 @@
+# $FreeBSD$
+# Init cannot be a child of the shell.
+wait 1
diff --git a/bin/sh/tests/errors/Makefile b/bin/sh/tests/errors/Makefile
new file mode 100644
index 000000000000..868f43599973
--- /dev/null
+++ b/bin/sh/tests/errors/Makefile
@@ -0,0 +1,35 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH= functional_test
+
+${PACKAGE}FILES+= assignment-error1.0
+${PACKAGE}FILES+= assignment-error2.0
+${PACKAGE}FILES+= backquote-error1.0
+${PACKAGE}FILES+= backquote-error2.0
+${PACKAGE}FILES+= bad-binary1.126
+${PACKAGE}FILES+= bad-keyword1.0
+${PACKAGE}FILES+= bad-parm-exp1.0
+${PACKAGE}FILES+= bad-parm-exp2.2 bad-parm-exp2.2.stderr
+${PACKAGE}FILES+= bad-parm-exp3.2 bad-parm-exp3.2.stderr
+${PACKAGE}FILES+= bad-parm-exp4.2 bad-parm-exp4.2.stderr
+${PACKAGE}FILES+= bad-parm-exp5.2 bad-parm-exp5.2.stderr
+${PACKAGE}FILES+= bad-parm-exp6.2 bad-parm-exp6.2.stderr
+${PACKAGE}FILES+= bad-parm-exp7.0
+${PACKAGE}FILES+= bad-parm-exp8.0
+${PACKAGE}FILES+= option-error.0
+${PACKAGE}FILES+= redirection-error.0
+${PACKAGE}FILES+= redirection-error2.2
+${PACKAGE}FILES+= redirection-error3.0
+${PACKAGE}FILES+= redirection-error4.0
+${PACKAGE}FILES+= redirection-error5.0
+${PACKAGE}FILES+= redirection-error6.0
+${PACKAGE}FILES+= redirection-error7.0
+${PACKAGE}FILES+= redirection-error8.0
+${PACKAGE}FILES+= write-error1.0
+
+.include <bsd.test.mk>
diff --git a/bin/sh/tests/errors/Makefile.depend b/bin/sh/tests/errors/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/bin/sh/tests/errors/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/bin/sh/tests/errors/assignment-error1.0 b/bin/sh/tests/errors/assignment-error1.0
new file mode 100644
index 000000000000..00eaed9b8671
--- /dev/null
+++ b/bin/sh/tests/errors/assignment-error1.0
@@ -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/bin/sh/tests/errors/assignment-error2.0 b/bin/sh/tests/errors/assignment-error2.0
new file mode 100644
index 000000000000..ff4e62995262
--- /dev/null
+++ b/bin/sh/tests/errors/assignment-error2.0
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+set -e
+HOME=/
+readonly HOME
+cd /sbin
+{ HOME=/bin cd; } 2>/dev/null || :
+[ "$(pwd)" != /bin ]
diff --git a/bin/sh/tests/errors/backquote-error1.0 b/bin/sh/tests/errors/backquote-error1.0
new file mode 100644
index 000000000000..43e33034af9f
--- /dev/null
+++ b/bin/sh/tests/errors/backquote-error1.0
@@ -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/bin/sh/tests/errors/backquote-error2.0 b/bin/sh/tests/errors/backquote-error2.0
new file mode 100644
index 000000000000..5b49e2be17ce
--- /dev/null
+++ b/bin/sh/tests/errors/backquote-error2.0
@@ -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/bin/sh/tests/errors/bad-binary1.126 b/bin/sh/tests/errors/bad-binary1.126
new file mode 100644
index 000000000000..d92e9ded5689
--- /dev/null
+++ b/bin/sh/tests/errors/bad-binary1.126
@@ -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/bin/sh/tests/errors/bad-keyword1.0 b/bin/sh/tests/errors/bad-keyword1.0
new file mode 100644
index 000000000000..ac0153655f8d
--- /dev/null
+++ b/bin/sh/tests/errors/bad-keyword1.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+echo ':; fi' | ${SH} -n 2>/dev/null && exit 1
+exit 0
diff --git a/bin/sh/tests/errors/bad-parm-exp1.0 b/bin/sh/tests/errors/bad-parm-exp1.0
new file mode 100644
index 000000000000..6e949945f9f3
--- /dev/null
+++ b/bin/sh/tests/errors/bad-parm-exp1.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+false && {
+ ${}
+ ${foo/}
+ ${foo@bar}
+}
+:
diff --git a/bin/sh/tests/errors/bad-parm-exp2.2 b/bin/sh/tests/errors/bad-parm-exp2.2
new file mode 100644
index 000000000000..a0826ecf9a8b
--- /dev/null
+++ b/bin/sh/tests/errors/bad-parm-exp2.2
@@ -0,0 +1,2 @@
+# $FreeBSD$
+eval '${}'
diff --git a/bin/sh/tests/errors/bad-parm-exp2.2.stderr b/bin/sh/tests/errors/bad-parm-exp2.2.stderr
new file mode 100644
index 000000000000..51ea69ca9709
--- /dev/null
+++ b/bin/sh/tests/errors/bad-parm-exp2.2.stderr
@@ -0,0 +1 @@
+eval: ${}: Bad substitution
diff --git a/bin/sh/tests/errors/bad-parm-exp3.2 b/bin/sh/tests/errors/bad-parm-exp3.2
new file mode 100644
index 000000000000..bb41208f258d
--- /dev/null
+++ b/bin/sh/tests/errors/bad-parm-exp3.2
@@ -0,0 +1,2 @@
+# $FreeBSD$
+eval '${foo/}'
diff --git a/bin/sh/tests/errors/bad-parm-exp3.2.stderr b/bin/sh/tests/errors/bad-parm-exp3.2.stderr
new file mode 100644
index 000000000000..70473f9a87b6
--- /dev/null
+++ b/bin/sh/tests/errors/bad-parm-exp3.2.stderr
@@ -0,0 +1 @@
+eval: ${foo/}: Bad substitution
diff --git a/bin/sh/tests/errors/bad-parm-exp4.2 b/bin/sh/tests/errors/bad-parm-exp4.2
new file mode 100644
index 000000000000..2837f9b5be9e
--- /dev/null
+++ b/bin/sh/tests/errors/bad-parm-exp4.2
@@ -0,0 +1,2 @@
+# $FreeBSD$
+eval '${foo:@abc}'
diff --git a/bin/sh/tests/errors/bad-parm-exp4.2.stderr b/bin/sh/tests/errors/bad-parm-exp4.2.stderr
new file mode 100644
index 000000000000..3363f5177f2f
--- /dev/null
+++ b/bin/sh/tests/errors/bad-parm-exp4.2.stderr
@@ -0,0 +1 @@
+eval: ${foo:@...}: Bad substitution
diff --git a/bin/sh/tests/errors/bad-parm-exp5.2 b/bin/sh/tests/errors/bad-parm-exp5.2
new file mode 100644
index 000000000000..1ba343bb710d
--- /dev/null
+++ b/bin/sh/tests/errors/bad-parm-exp5.2
@@ -0,0 +1,2 @@
+# $FreeBSD$
+eval '${/}'
diff --git a/bin/sh/tests/errors/bad-parm-exp5.2.stderr b/bin/sh/tests/errors/bad-parm-exp5.2.stderr
new file mode 100644
index 000000000000..13763f8ed9ca
--- /dev/null
+++ b/bin/sh/tests/errors/bad-parm-exp5.2.stderr
@@ -0,0 +1 @@
+eval: ${/}: Bad substitution
diff --git a/bin/sh/tests/errors/bad-parm-exp6.2 b/bin/sh/tests/errors/bad-parm-exp6.2
new file mode 100644
index 000000000000..b53a91b364b5
--- /dev/null
+++ b/bin/sh/tests/errors/bad-parm-exp6.2
@@ -0,0 +1,2 @@
+# $FreeBSD$
+eval '${#foo^}'
diff --git a/bin/sh/tests/errors/bad-parm-exp6.2.stderr b/bin/sh/tests/errors/bad-parm-exp6.2.stderr
new file mode 100644
index 000000000000..cc56f65b62ec
--- /dev/null
+++ b/bin/sh/tests/errors/bad-parm-exp6.2.stderr
@@ -0,0 +1 @@
+eval: ${foo...}: Bad substitution
diff --git a/bin/sh/tests/errors/bad-parm-exp7.0 b/bin/sh/tests/errors/bad-parm-exp7.0
new file mode 100644
index 000000000000..b8562fbed47b
--- /dev/null
+++ b/bin/sh/tests/errors/bad-parm-exp7.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+v=1
+eval ": $(printf '${v-${\372}}')"
diff --git a/bin/sh/tests/errors/bad-parm-exp8.0 b/bin/sh/tests/errors/bad-parm-exp8.0
new file mode 100644
index 000000000000..28f00cde0f9f
--- /dev/null
+++ b/bin/sh/tests/errors/bad-parm-exp8.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+v=1
+eval ": $(printf '${v-${w\372}}')"
diff --git a/bin/sh/tests/errors/option-error.0 b/bin/sh/tests/errors/option-error.0
new file mode 100644
index 000000000000..b4b44c4a4062
--- /dev/null
+++ b/bin/sh/tests/errors/option-error.0
@@ -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/bin/sh/tests/errors/redirection-error.0 b/bin/sh/tests/errors/redirection-error.0
new file mode 100644
index 000000000000..cb8c0b113c13
--- /dev/null
+++ b/bin/sh/tests/errors/redirection-error.0
@@ -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/bin/sh/tests/errors/redirection-error2.2 b/bin/sh/tests/errors/redirection-error2.2
new file mode 100644
index 000000000000..32bccd8e784a
--- /dev/null
+++ b/bin/sh/tests/errors/redirection-error2.2
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+# sh should fail gracefully on this bad redirect
+${SH} -c 'echo 1 >&$a' 2>/dev/null
diff --git a/bin/sh/tests/errors/redirection-error3.0 b/bin/sh/tests/errors/redirection-error3.0
new file mode 100644
index 000000000000..8a07d037baec
--- /dev/null
+++ b/bin/sh/tests/errors/redirection-error3.0
@@ -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/bin/sh/tests/errors/redirection-error4.0 b/bin/sh/tests/errors/redirection-error4.0
new file mode 100644
index 000000000000..206097478e35
--- /dev/null
+++ b/bin/sh/tests/errors/redirection-error4.0
@@ -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/bin/sh/tests/errors/redirection-error5.0 b/bin/sh/tests/errors/redirection-error5.0
new file mode 100644
index 000000000000..1fcd47eebf96
--- /dev/null
+++ b/bin/sh/tests/errors/redirection-error5.0
@@ -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/bin/sh/tests/errors/redirection-error6.0 b/bin/sh/tests/errors/redirection-error6.0
new file mode 100644
index 000000000000..17d1109b390f
--- /dev/null
+++ b/bin/sh/tests/errors/redirection-error6.0
@@ -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/bin/sh/tests/errors/redirection-error7.0 b/bin/sh/tests/errors/redirection-error7.0
new file mode 100644
index 000000000000..5b20f04beb83
--- /dev/null
+++ b/bin/sh/tests/errors/redirection-error7.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+! dummy=$(
+ exec 3>&1 >&2 2>&3
+ ulimit -n 9
+ exec 9<.
+) && [ -n "$dummy" ]
diff --git a/bin/sh/tests/errors/redirection-error8.0 b/bin/sh/tests/errors/redirection-error8.0
new file mode 100644
index 000000000000..91595457831e
--- /dev/null
+++ b/bin/sh/tests/errors/redirection-error8.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+$SH -c '{ { :; } </var/empty/x; } 2>/dev/null || kill -INT $$; echo continued'
+r=$?
+[ "$r" -gt 128 ] && [ "$(kill -l "$r")" = INT ]
diff --git a/bin/sh/tests/errors/write-error1.0 b/bin/sh/tests/errors/write-error1.0
new file mode 100644
index 000000000000..fcb52e74178c
--- /dev/null
+++ b/bin/sh/tests/errors/write-error1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+! echo >&- 2>/dev/null
diff --git a/bin/sh/tests/execution/Makefile b/bin/sh/tests/execution/Makefile
new file mode 100644
index 000000000000..1f92c1a835f7
--- /dev/null
+++ b/bin/sh/tests/execution/Makefile
@@ -0,0 +1,57 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH= functional_test
+
+${PACKAGE}FILES+= bg1.0
+${PACKAGE}FILES+= bg2.0
+${PACKAGE}FILES+= bg3.0
+${PACKAGE}FILES+= bg4.0
+${PACKAGE}FILES+= bg5.0
+${PACKAGE}FILES+= bg6.0 bg6.0.stdout
+${PACKAGE}FILES+= bg7.0
+${PACKAGE}FILES+= bg8.0
+${PACKAGE}FILES+= bg9.0
+${PACKAGE}FILES+= bg10.0 bg10.0.stdout
+${PACKAGE}FILES+= fork1.0
+${PACKAGE}FILES+= fork2.0
+${PACKAGE}FILES+= fork3.0
+${PACKAGE}FILES+= func1.0
+${PACKAGE}FILES+= func2.0
+${PACKAGE}FILES+= func3.0
+${PACKAGE}FILES+= hash1.0
+${PACKAGE}FILES+= int-cmd1.0
+${PACKAGE}FILES+= killed1.0
+${PACKAGE}FILES+= killed2.0
+${PACKAGE}FILES+= not1.0
+${PACKAGE}FILES+= not2.0
+${PACKAGE}FILES+= path1.0
+${PACKAGE}FILES+= redir1.0
+${PACKAGE}FILES+= redir2.0
+${PACKAGE}FILES+= redir3.0
+${PACKAGE}FILES+= redir4.0
+${PACKAGE}FILES+= redir5.0
+${PACKAGE}FILES+= redir6.0
+${PACKAGE}FILES+= redir7.0
+${PACKAGE}FILES+= set-C1.0
+${PACKAGE}FILES+= set-n1.0
+${PACKAGE}FILES+= set-n2.0
+${PACKAGE}FILES+= set-n3.0
+${PACKAGE}FILES+= set-n4.0
+${PACKAGE}FILES+= set-x1.0
+${PACKAGE}FILES+= set-x2.0
+${PACKAGE}FILES+= set-x3.0
+${PACKAGE}FILES+= set-x4.0
+${PACKAGE}FILES+= shellproc1.0
+${PACKAGE}FILES+= subshell1.0 subshell1.0.stdout
+${PACKAGE}FILES+= subshell2.0
+${PACKAGE}FILES+= subshell3.0
+${PACKAGE}FILES+= subshell4.0
+${PACKAGE}FILES+= unknown1.0
+${PACKAGE}FILES+= var-assign1.0
+
+.include <bsd.test.mk>
diff --git a/bin/sh/tests/execution/Makefile.depend b/bin/sh/tests/execution/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/bin/sh/tests/execution/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/bin/sh/tests/execution/bg1.0 b/bin/sh/tests/execution/bg1.0
new file mode 100644
index 000000000000..edb92ae2ddd0
--- /dev/null
+++ b/bin/sh/tests/execution/bg1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+: `false` &
diff --git a/bin/sh/tests/execution/bg10.0 b/bin/sh/tests/execution/bg10.0
new file mode 100644
index 000000000000..44a25dc23b2e
--- /dev/null
+++ b/bin/sh/tests/execution/bg10.0
@@ -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/bin/sh/tests/execution/bg10.0.stdout b/bin/sh/tests/execution/bg10.0.stdout
new file mode 100644
index 000000000000..7cfab5b05d62
--- /dev/null
+++ b/bin/sh/tests/execution/bg10.0.stdout
@@ -0,0 +1 @@
+yes
diff --git a/bin/sh/tests/execution/bg2.0 b/bin/sh/tests/execution/bg2.0
new file mode 100644
index 000000000000..2e2fbc53bd26
--- /dev/null
+++ b/bin/sh/tests/execution/bg2.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+f() { return 42; }
+f
+: | : &
diff --git a/bin/sh/tests/execution/bg3.0 b/bin/sh/tests/execution/bg3.0
new file mode 100644
index 000000000000..359fc6f476e6
--- /dev/null
+++ b/bin/sh/tests/execution/bg3.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+f() { return 42; }
+f
+(:) &
diff --git a/bin/sh/tests/execution/bg4.0 b/bin/sh/tests/execution/bg4.0
new file mode 100644
index 000000000000..25e4f4e34fa5
--- /dev/null
+++ b/bin/sh/tests/execution/bg4.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+x=''
+: ${x:=1} &
+wait
+exit ${x:-0}
diff --git a/bin/sh/tests/execution/bg5.0 b/bin/sh/tests/execution/bg5.0
new file mode 100644
index 000000000000..cc9ceaa41d4e
--- /dev/null
+++ b/bin/sh/tests/execution/bg5.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+# A background command has an implicit </dev/null redirection.
+
+echo bad | ${SH} -c '{ cat & wait; }'
diff --git a/bin/sh/tests/execution/bg6.0 b/bin/sh/tests/execution/bg6.0
new file mode 100644
index 000000000000..b0faf9e73708
--- /dev/null
+++ b/bin/sh/tests/execution/bg6.0
@@ -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/bin/sh/tests/execution/bg6.0.stdout b/bin/sh/tests/execution/bg6.0.stdout
new file mode 100644
index 000000000000..7cfab5b05d62
--- /dev/null
+++ b/bin/sh/tests/execution/bg6.0.stdout
@@ -0,0 +1 @@
+yes
diff --git a/bin/sh/tests/execution/bg7.0 b/bin/sh/tests/execution/bg7.0
new file mode 100644
index 000000000000..f771edc56cde
--- /dev/null
+++ b/bin/sh/tests/execution/bg7.0
@@ -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/bin/sh/tests/execution/bg8.0 b/bin/sh/tests/execution/bg8.0
new file mode 100644
index 000000000000..33667cb1922b
--- /dev/null
+++ b/bin/sh/tests/execution/bg8.0
@@ -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/bin/sh/tests/execution/bg9.0 b/bin/sh/tests/execution/bg9.0
new file mode 100644
index 000000000000..64fde3e3e333
--- /dev/null
+++ b/bin/sh/tests/execution/bg9.0
@@ -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/bin/sh/tests/execution/fork1.0 b/bin/sh/tests/execution/fork1.0
new file mode 100644
index 000000000000..2eeac79ae8c3
--- /dev/null
+++ b/bin/sh/tests/execution/fork1.0
@@ -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/bin/sh/tests/execution/fork2.0 b/bin/sh/tests/execution/fork2.0
new file mode 100644
index 000000000000..62a25379123c
--- /dev/null
+++ b/bin/sh/tests/execution/fork2.0
@@ -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/bin/sh/tests/execution/fork3.0 b/bin/sh/tests/execution/fork3.0
new file mode 100644
index 000000000000..3cb678c2d08a
--- /dev/null
+++ b/bin/sh/tests/execution/fork3.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+result=$(${SH} -c 'f() { ps -p $$ -o comm=; }; f')
+test "$result" = "ps"
diff --git a/bin/sh/tests/execution/func1.0 b/bin/sh/tests/execution/func1.0
new file mode 100644
index 000000000000..29fcc077b592
--- /dev/null
+++ b/bin/sh/tests/execution/func1.0
@@ -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/bin/sh/tests/execution/func2.0 b/bin/sh/tests/execution/func2.0
new file mode 100644
index 000000000000..9830b5e8a7cf
--- /dev/null
+++ b/bin/sh/tests/execution/func2.0
@@ -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/bin/sh/tests/execution/func3.0 b/bin/sh/tests/execution/func3.0
new file mode 100644
index 000000000000..e0ed581f85ef
--- /dev/null
+++ b/bin/sh/tests/execution/func3.0
@@ -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/bin/sh/tests/execution/hash1.0 b/bin/sh/tests/execution/hash1.0
new file mode 100644
index 000000000000..a645c2aef735
--- /dev/null
+++ b/bin/sh/tests/execution/hash1.0
@@ -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/bin/sh/tests/execution/int-cmd1.0 b/bin/sh/tests/execution/int-cmd1.0
new file mode 100644
index 000000000000..a1f097b774d6
--- /dev/null
+++ b/bin/sh/tests/execution/int-cmd1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+! echo echo bad | $SH -ic 'fi' 2>/dev/null
diff --git a/bin/sh/tests/execution/killed1.0 b/bin/sh/tests/execution/killed1.0
new file mode 100644
index 000000000000..41d3e055723e
--- /dev/null
+++ b/bin/sh/tests/execution/killed1.0
@@ -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/bin/sh/tests/execution/killed2.0 b/bin/sh/tests/execution/killed2.0
new file mode 100644
index 000000000000..7ff3fe2900d1
--- /dev/null
+++ b/bin/sh/tests/execution/killed2.0
@@ -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/bin/sh/tests/execution/not1.0 b/bin/sh/tests/execution/not1.0
new file mode 100644
index 000000000000..12c6265a9282
--- /dev/null
+++ b/bin/sh/tests/execution/not1.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+f() { ! return $1; }
+f 0 && ! f 1
diff --git a/bin/sh/tests/execution/not2.0 b/bin/sh/tests/execution/not2.0
new file mode 100644
index 000000000000..1b128d096716
--- /dev/null
+++ b/bin/sh/tests/execution/not2.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+while :; do
+ ! break
+ exit 3
+done
diff --git a/bin/sh/tests/execution/path1.0 b/bin/sh/tests/execution/path1.0
new file mode 100644
index 000000000000..50829d629a4d
--- /dev/null
+++ b/bin/sh/tests/execution/path1.0
@@ -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/bin/sh/tests/execution/redir1.0 b/bin/sh/tests/execution/redir1.0
new file mode 100644
index 000000000000..dd0011f064b0
--- /dev/null
+++ b/bin/sh/tests/execution/redir1.0
@@ -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/bin/sh/tests/execution/redir2.0 b/bin/sh/tests/execution/redir2.0
new file mode 100644
index 000000000000..1588105f599a
--- /dev/null
+++ b/bin/sh/tests/execution/redir2.0
@@ -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/bin/sh/tests/execution/redir3.0 b/bin/sh/tests/execution/redir3.0
new file mode 100644
index 000000000000..d68e4504ed3d
--- /dev/null
+++ b/bin/sh/tests/execution/redir3.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+3>&- 3>&-
diff --git a/bin/sh/tests/execution/redir4.0 b/bin/sh/tests/execution/redir4.0
new file mode 100644
index 000000000000..57054c17c45f
--- /dev/null
+++ b/bin/sh/tests/execution/redir4.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+{ echo bad 0>&3; } 2>/dev/null 3>/dev/null 3>&-
+exit 0
diff --git a/bin/sh/tests/execution/redir5.0 b/bin/sh/tests/execution/redir5.0
new file mode 100644
index 000000000000..707ca68f737a
--- /dev/null
+++ b/bin/sh/tests/execution/redir5.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+{ (echo bad) >/dev/null; } </dev/null
diff --git a/bin/sh/tests/execution/redir6.0 b/bin/sh/tests/execution/redir6.0
new file mode 100644
index 000000000000..4e3ac0cae055
--- /dev/null
+++ b/bin/sh/tests/execution/redir6.0
@@ -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/bin/sh/tests/execution/redir7.0 b/bin/sh/tests/execution/redir7.0
new file mode 100644
index 000000000000..2487bcf2fc0d
--- /dev/null
+++ b/bin/sh/tests/execution/redir7.0
@@ -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/bin/sh/tests/execution/set-C1.0 b/bin/sh/tests/execution/set-C1.0
new file mode 100644
index 000000000000..7877a33989b7
--- /dev/null
+++ b/bin/sh/tests/execution/set-C1.0
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+T=$(mktemp -d "${TMPDIR:-/tmp}/sh-test.XXXXXXXX") || exit
+trap 'rm -rf "$T"' 0
+
+set -C
+echo . >"$T/a" &&
+[ -s "$T/a" ] &&
+{ ! true >"$T/a"; } 2>/dev/null &&
+[ -s "$T/a" ] &&
+ln -s /dev/null "$T/b" &&
+true >"$T/b"
diff --git a/bin/sh/tests/execution/set-n1.0 b/bin/sh/tests/execution/set-n1.0
new file mode 100644
index 000000000000..14c9b9396ada
--- /dev/null
+++ b/bin/sh/tests/execution/set-n1.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+v=$( ($SH -n <<'EOF'
+for
+EOF
+) 2>&1 >/dev/null)
+[ $? -ne 0 ] && [ -n "$v" ]
diff --git a/bin/sh/tests/execution/set-n2.0 b/bin/sh/tests/execution/set-n2.0
new file mode 100644
index 000000000000..c7f31629f474
--- /dev/null
+++ b/bin/sh/tests/execution/set-n2.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+$SH -n <<'EOF'
+echo bad
+EOF
diff --git a/bin/sh/tests/execution/set-n3.0 b/bin/sh/tests/execution/set-n3.0
new file mode 100644
index 000000000000..24a9159fdeb9
--- /dev/null
+++ b/bin/sh/tests/execution/set-n3.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+v=$( ($SH -nc 'for') 2>&1 >/dev/null)
+[ $? -ne 0 ] && [ -n "$v" ]
diff --git a/bin/sh/tests/execution/set-n4.0 b/bin/sh/tests/execution/set-n4.0
new file mode 100644
index 000000000000..36985084b26b
--- /dev/null
+++ b/bin/sh/tests/execution/set-n4.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+$SH -nc 'echo bad'
diff --git a/bin/sh/tests/execution/set-x1.0 b/bin/sh/tests/execution/set-x1.0
new file mode 100644
index 000000000000..7fe1dbf1076b
--- /dev/null
+++ b/bin/sh/tests/execution/set-x1.0
@@ -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/bin/sh/tests/execution/set-x2.0 b/bin/sh/tests/execution/set-x2.0
new file mode 100644
index 000000000000..56d54e38d251
--- /dev/null
+++ b/bin/sh/tests/execution/set-x2.0
@@ -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/bin/sh/tests/execution/set-x3.0 b/bin/sh/tests/execution/set-x3.0
new file mode 100644
index 000000000000..1ca57aca6baa
--- /dev/null
+++ b/bin/sh/tests/execution/set-x3.0
@@ -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/bin/sh/tests/execution/set-x4.0 b/bin/sh/tests/execution/set-x4.0
new file mode 100644
index 000000000000..0904766ccdd1
--- /dev/null
+++ b/bin/sh/tests/execution/set-x4.0
@@ -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/bin/sh/tests/execution/shellproc1.0 b/bin/sh/tests/execution/shellproc1.0
new file mode 100644
index 000000000000..1326bc27f1c1
--- /dev/null
+++ b/bin/sh/tests/execution/shellproc1.0
@@ -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/bin/sh/tests/execution/subshell1.0 b/bin/sh/tests/execution/subshell1.0
new file mode 100644
index 000000000000..347806ed45ac
--- /dev/null
+++ b/bin/sh/tests/execution/subshell1.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+(eval "cd /
+v=$(printf %0100000d 1)
+echo \${#v}")
+echo end
diff --git a/bin/sh/tests/execution/subshell1.0.stdout b/bin/sh/tests/execution/subshell1.0.stdout
new file mode 100644
index 000000000000..8c71af3cd79f
--- /dev/null
+++ b/bin/sh/tests/execution/subshell1.0.stdout
@@ -0,0 +1,2 @@
+100000
+end
diff --git a/bin/sh/tests/execution/subshell2.0 b/bin/sh/tests/execution/subshell2.0
new file mode 100644
index 000000000000..32164495c9ca
--- /dev/null
+++ b/bin/sh/tests/execution/subshell2.0
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+f() {
+ x=2
+}
+(
+ x=1
+ f
+ [ "$x" = 2 ]
+)
diff --git a/bin/sh/tests/execution/subshell3.0 b/bin/sh/tests/execution/subshell3.0
new file mode 100644
index 000000000000..9a87acb15e1c
--- /dev/null
+++ b/bin/sh/tests/execution/subshell3.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+(false; exit) && exit 3
+exit 0
diff --git a/bin/sh/tests/execution/subshell4.0 b/bin/sh/tests/execution/subshell4.0
new file mode 100644
index 000000000000..b39edb12eb2c
--- /dev/null
+++ b/bin/sh/tests/execution/subshell4.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+(eval "set v=1"; false) && echo bad; :
diff --git a/bin/sh/tests/execution/unknown1.0 b/bin/sh/tests/execution/unknown1.0
new file mode 100644
index 000000000000..45f541e6eeea
--- /dev/null
+++ b/bin/sh/tests/execution/unknown1.0
@@ -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/bin/sh/tests/execution/var-assign1.0 b/bin/sh/tests/execution/var-assign1.0
new file mode 100644
index 000000000000..26e54249c1cc
--- /dev/null
+++ b/bin/sh/tests/execution/var-assign1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+[ "$(HOME=/etc HOME=/ cd && pwd)" = / ]
diff --git a/bin/sh/tests/expansion/Makefile b/bin/sh/tests/expansion/Makefile
new file mode 100644
index 000000000000..f3cfc22b17c6
--- /dev/null
+++ b/bin/sh/tests/expansion/Makefile
@@ -0,0 +1,105 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH= functional_test
+
+${PACKAGE}FILES+= arith1.0
+${PACKAGE}FILES+= arith2.0
+${PACKAGE}FILES+= arith3.0
+${PACKAGE}FILES+= arith4.0
+${PACKAGE}FILES+= arith5.0
+${PACKAGE}FILES+= arith6.0
+${PACKAGE}FILES+= arith7.0
+${PACKAGE}FILES+= arith8.0
+${PACKAGE}FILES+= arith9.0
+${PACKAGE}FILES+= arith10.0
+${PACKAGE}FILES+= arith11.0
+${PACKAGE}FILES+= arith12.0
+${PACKAGE}FILES+= arith13.0
+${PACKAGE}FILES+= arith14.0
+${PACKAGE}FILES+= assign1.0
+${PACKAGE}FILES+= cmdsubst1.0
+${PACKAGE}FILES+= cmdsubst2.0
+${PACKAGE}FILES+= cmdsubst3.0
+${PACKAGE}FILES+= cmdsubst4.0
+${PACKAGE}FILES+= cmdsubst5.0
+${PACKAGE}FILES+= cmdsubst6.0
+${PACKAGE}FILES+= cmdsubst7.0
+${PACKAGE}FILES+= cmdsubst8.0
+${PACKAGE}FILES+= cmdsubst9.0
+${PACKAGE}FILES+= cmdsubst10.0
+${PACKAGE}FILES+= cmdsubst11.0
+${PACKAGE}FILES+= cmdsubst12.0
+${PACKAGE}FILES+= cmdsubst13.0
+${PACKAGE}FILES+= cmdsubst14.0
+${PACKAGE}FILES+= cmdsubst15.0
+${PACKAGE}FILES+= cmdsubst16.0
+${PACKAGE}FILES+= cmdsubst17.0
+${PACKAGE}FILES+= cmdsubst18.0
+${PACKAGE}FILES+= cmdsubst19.0
+${PACKAGE}FILES+= cmdsubst20.0
+${PACKAGE}FILES+= cmdsubst21.0
+${PACKAGE}FILES+= cmdsubst22.0
+${PACKAGE}FILES+= cmdsubst23.0
+${PACKAGE}FILES+= cmdsubst24.0
+${PACKAGE}FILES+= cmdsubst25.0
+${PACKAGE}FILES+= cmdsubst26.0
+${PACKAGE}FILES+= export1.0
+${PACKAGE}FILES+= export2.0
+${PACKAGE}FILES+= export3.0
+${PACKAGE}FILES+= heredoc1.0
+${PACKAGE}FILES+= heredoc2.0
+${PACKAGE}FILES+= ifs1.0
+${PACKAGE}FILES+= ifs2.0
+${PACKAGE}FILES+= ifs3.0
+${PACKAGE}FILES+= ifs4.0
+${PACKAGE}FILES+= ifs5.0
+${PACKAGE}FILES+= ifs6.0
+${PACKAGE}FILES+= ifs7.0
+${PACKAGE}FILES+= length1.0
+${PACKAGE}FILES+= length2.0
+${PACKAGE}FILES+= length3.0
+${PACKAGE}FILES+= length4.0
+${PACKAGE}FILES+= length5.0
+${PACKAGE}FILES+= length6.0
+${PACKAGE}FILES+= length7.0
+${PACKAGE}FILES+= length8.0
+${PACKAGE}FILES+= local1.0
+${PACKAGE}FILES+= local2.0
+${PACKAGE}FILES+= pathname1.0
+${PACKAGE}FILES+= pathname2.0
+${PACKAGE}FILES+= pathname3.0
+${PACKAGE}FILES+= pathname4.0
+${PACKAGE}FILES+= pathname5.0
+${PACKAGE}FILES+= pathname6.0
+${PACKAGE}FILES+= plus-minus1.0
+${PACKAGE}FILES+= plus-minus2.0
+${PACKAGE}FILES+= plus-minus3.0
+${PACKAGE}FILES+= plus-minus4.0
+${PACKAGE}FILES+= plus-minus5.0
+${PACKAGE}FILES+= plus-minus6.0
+${PACKAGE}FILES+= plus-minus7.0
+${PACKAGE}FILES+= plus-minus8.0
+${PACKAGE}FILES+= question1.0
+${PACKAGE}FILES+= readonly1.0
+${PACKAGE}FILES+= redir1.0
+${PACKAGE}FILES+= set-u1.0
+${PACKAGE}FILES+= set-u2.0
+${PACKAGE}FILES+= set-u3.0
+${PACKAGE}FILES+= tilde1.0
+${PACKAGE}FILES+= tilde2.0
+${PACKAGE}FILES+= trim1.0
+${PACKAGE}FILES+= trim2.0
+${PACKAGE}FILES+= trim3.0
+${PACKAGE}FILES+= trim4.0
+${PACKAGE}FILES+= trim5.0
+${PACKAGE}FILES+= trim6.0
+${PACKAGE}FILES+= trim7.0
+${PACKAGE}FILES+= trim8.0
+${PACKAGE}FILES+= trim9.0
+
+.include <bsd.test.mk>
diff --git a/bin/sh/tests/expansion/Makefile.depend b/bin/sh/tests/expansion/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/bin/sh/tests/expansion/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/bin/sh/tests/expansion/arith1.0 b/bin/sh/tests/expansion/arith1.0
new file mode 100644
index 000000000000..118ba2265687
--- /dev/null
+++ b/bin/sh/tests/expansion/arith1.0
@@ -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/bin/sh/tests/expansion/arith10.0 b/bin/sh/tests/expansion/arith10.0
new file mode 100644
index 000000000000..1aaf6194fbaf
--- /dev/null
+++ b/bin/sh/tests/expansion/arith10.0
@@ -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/bin/sh/tests/expansion/arith11.0 b/bin/sh/tests/expansion/arith11.0
new file mode 100644
index 000000000000..6bc73697ffab
--- /dev/null
+++ b/bin/sh/tests/expansion/arith11.0
@@ -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/bin/sh/tests/expansion/arith12.0 b/bin/sh/tests/expansion/arith12.0
new file mode 100644
index 000000000000..cb7da3b2e21a
--- /dev/null
+++ b/bin/sh/tests/expansion/arith12.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+_x=4 y_=5 z_z=6
+[ "$((_x*100+y_*10+z_z))" = 456 ]
diff --git a/bin/sh/tests/expansion/arith13.0 b/bin/sh/tests/expansion/arith13.0
new file mode 100644
index 000000000000..207e4881935b
--- /dev/null
+++ b/bin/sh/tests/expansion/arith13.0
@@ -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/bin/sh/tests/expansion/arith14.0 b/bin/sh/tests/expansion/arith14.0
new file mode 100644
index 000000000000..836904335ef3
--- /dev/null
+++ b/bin/sh/tests/expansion/arith14.0
@@ -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/bin/sh/tests/expansion/arith2.0 b/bin/sh/tests/expansion/arith2.0
new file mode 100644
index 000000000000..95b48a06aae6
--- /dev/null
+++ b/bin/sh/tests/expansion/arith2.0
@@ -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/bin/sh/tests/expansion/arith3.0 b/bin/sh/tests/expansion/arith3.0
new file mode 100644
index 000000000000..b69159d9b5b8
--- /dev/null
+++ b/bin/sh/tests/expansion/arith3.0
@@ -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/bin/sh/tests/expansion/arith4.0 b/bin/sh/tests/expansion/arith4.0
new file mode 100644
index 000000000000..610dad89e4dc
--- /dev/null
+++ b/bin/sh/tests/expansion/arith4.0
@@ -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/bin/sh/tests/expansion/arith5.0 b/bin/sh/tests/expansion/arith5.0
new file mode 100644
index 000000000000..d0f23312f9ca
--- /dev/null
+++ b/bin/sh/tests/expansion/arith5.0
@@ -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/bin/sh/tests/expansion/arith6.0 b/bin/sh/tests/expansion/arith6.0
new file mode 100644
index 000000000000..fc4589c1bac5
--- /dev/null
+++ b/bin/sh/tests/expansion/arith6.0
@@ -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/bin/sh/tests/expansion/arith7.0 b/bin/sh/tests/expansion/arith7.0
new file mode 100644
index 000000000000..5aada2b86586
--- /dev/null
+++ b/bin/sh/tests/expansion/arith7.0
@@ -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/bin/sh/tests/expansion/arith8.0 b/bin/sh/tests/expansion/arith8.0
new file mode 100644
index 000000000000..2d03e503387d
--- /dev/null
+++ b/bin/sh/tests/expansion/arith8.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+v=$( (eval ': $((08))') 2>&1 >/dev/null)
+[ $? -ne 0 ] && [ -n "$v" ]
diff --git a/bin/sh/tests/expansion/arith9.0 b/bin/sh/tests/expansion/arith9.0
new file mode 100644
index 000000000000..cc8b597d4755
--- /dev/null
+++ b/bin/sh/tests/expansion/arith9.0
@@ -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/bin/sh/tests/expansion/assign1.0 b/bin/sh/tests/expansion/assign1.0
new file mode 100644
index 000000000000..d4fa7727f593
--- /dev/null
+++ b/bin/sh/tests/expansion/assign1.0
@@ -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/bin/sh/tests/expansion/cmdsubst1.0 b/bin/sh/tests/expansion/cmdsubst1.0
new file mode 100644
index 000000000000..515c7da9aca1
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst1.0
@@ -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/bin/sh/tests/expansion/cmdsubst10.0 b/bin/sh/tests/expansion/cmdsubst10.0
new file mode 100644
index 000000000000..7cf17a3e8fe1
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst10.0
@@ -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/bin/sh/tests/expansion/cmdsubst11.0 b/bin/sh/tests/expansion/cmdsubst11.0
new file mode 100644
index 000000000000..f1af547421f9
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst11.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+# Not required by POSIX but useful for efficiency.
+
+[ $$ = $(eval '${SH} -c echo\ \$PPID') ]
diff --git a/bin/sh/tests/expansion/cmdsubst12.0 b/bin/sh/tests/expansion/cmdsubst12.0
new file mode 100644
index 000000000000..50394dbb00ca
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst12.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+f() {
+ echo x$(printf foo >&2)y
+}
+[ "$(f 2>&1)" = "fooxy" ]
diff --git a/bin/sh/tests/expansion/cmdsubst13.0 b/bin/sh/tests/expansion/cmdsubst13.0
new file mode 100644
index 000000000000..7fdc5b217f19
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst13.0
@@ -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/bin/sh/tests/expansion/cmdsubst14.0 b/bin/sh/tests/expansion/cmdsubst14.0
new file mode 100644
index 000000000000..bdbbb823e2b2
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst14.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+! v=`false
+
+`
diff --git a/bin/sh/tests/expansion/cmdsubst15.0 b/bin/sh/tests/expansion/cmdsubst15.0
new file mode 100644
index 000000000000..31d85d497e23
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst15.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+! v=`false;
+
+`
diff --git a/bin/sh/tests/expansion/cmdsubst16.0 b/bin/sh/tests/expansion/cmdsubst16.0
new file mode 100644
index 000000000000..71df562298e5
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst16.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+f() { return 3; }
+f
+[ `echo $?` = 3 ]
diff --git a/bin/sh/tests/expansion/cmdsubst17.0 b/bin/sh/tests/expansion/cmdsubst17.0
new file mode 100644
index 000000000000..8c29e8318327
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst17.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+f() { return 3; }
+f
+[ `echo $?; :` = 3 ]
diff --git a/bin/sh/tests/expansion/cmdsubst18.0 b/bin/sh/tests/expansion/cmdsubst18.0
new file mode 100644
index 000000000000..a9791b3acae6
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst18.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+x=X
+unset n
+r=${x+$(echo a)}${x-$(echo b)}${n+$(echo c)}${n-$(echo d)}$(echo e)
+[ "$r" = aXde ]
diff --git a/bin/sh/tests/expansion/cmdsubst19.0 b/bin/sh/tests/expansion/cmdsubst19.0
new file mode 100644
index 000000000000..ae6619a775f7
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst19.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+b=200 c=30 d=5 x=4
+r=$(echo a)$(($(echo b) + ${x+$(echo c)} + ${x-$(echo d)}))$(echo e)
+[ "$r" = a234e ]
diff --git a/bin/sh/tests/expansion/cmdsubst2.0 b/bin/sh/tests/expansion/cmdsubst2.0
new file mode 100644
index 000000000000..b86776ed24b0
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst2.0
@@ -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/bin/sh/tests/expansion/cmdsubst20.0 b/bin/sh/tests/expansion/cmdsubst20.0
new file mode 100644
index 000000000000..33932487b89f
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst20.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+set -T
+trapped=''
+trap "trapped=x$trapped" USR1
+[ "x$(kill -USR1 $$)y" = xy ] && [ "$trapped" = x ]
diff --git a/bin/sh/tests/expansion/cmdsubst21.0 b/bin/sh/tests/expansion/cmdsubst21.0
new file mode 100644
index 000000000000..87ff6a9db4ad
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst21.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+set -T
+trapped=''
+trap "trapped=x$trapped" TERM
+[ "x$($SH -c "kill $$")y" = xy ] && [ "$trapped" = x ]
diff --git a/bin/sh/tests/expansion/cmdsubst22.0 b/bin/sh/tests/expansion/cmdsubst22.0
new file mode 100644
index 000000000000..97c6c98dff87
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst22.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+set -T
+trapped=''
+trap "trapped=x$trapped" TERM
+[ "x$(:; kill $$)y" = xy ] && [ "$trapped" = x ]
diff --git a/bin/sh/tests/expansion/cmdsubst23.0 b/bin/sh/tests/expansion/cmdsubst23.0
new file mode 100644
index 000000000000..cde86981f461
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst23.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+unset n
+x=abcd
+[ "X${n#$(echo a)}X${x#$(echo ab)}X$(echo abc)X" = XXcdXabcX ]
diff --git a/bin/sh/tests/expansion/cmdsubst24.0 b/bin/sh/tests/expansion/cmdsubst24.0
new file mode 100644
index 000000000000..4b34247d6805
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst24.0
@@ -0,0 +1,24 @@
+# $FreeBSD$
+# POSIX leaves the effect of NUL bytes in command substitution output
+# unspecified but we have always discarded them.
+
+failures=0
+
+check() {
+ if [ "$2" != "$3" ]; then
+ printf "Failed at line %s: got \"%s\" expected \"%s\"\n" "$1" "$2" "$3"
+ : $((failures += 1))
+ fi
+}
+
+fmt='\0a\0 \0b\0c d\0'
+assign_builtin=$(printf "$fmt")
+check "$LINENO" "$assign_builtin" "a bc d"
+assign_pipeline=$(printf "$fmt" | cat)
+check "$LINENO" "$assign_pipeline" "a bc d"
+set -- $(printf "$fmt") $(printf "$fmt" | cat) "$(printf "$fmt")" "$(printf "$fmt" | cat)"
+IFS=@
+splits="$*"
+check "$LINENO" "$splits" "a@bc@d@a@bc@d@a bc d@a bc d"
+
+[ "$failures" = 0 ]
diff --git a/bin/sh/tests/expansion/cmdsubst25.0 b/bin/sh/tests/expansion/cmdsubst25.0
new file mode 100644
index 000000000000..83cca7d0b97e
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst25.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+IFS=' '
+set -- `printf '\n '`
+IFS=:
+[ "$*" = '
+' ]
diff --git a/bin/sh/tests/expansion/cmdsubst26.0 b/bin/sh/tests/expansion/cmdsubst26.0
new file mode 100644
index 000000000000..68a624b12471
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst26.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+nl='
+'
+v=$nl`printf '\n'`
+[ "$v" = "$nl" ]
diff --git a/bin/sh/tests/expansion/cmdsubst3.0 b/bin/sh/tests/expansion/cmdsubst3.0
new file mode 100644
index 000000000000..abb6b225a257
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst3.0
@@ -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/bin/sh/tests/expansion/cmdsubst4.0 b/bin/sh/tests/expansion/cmdsubst4.0
new file mode 100644
index 000000000000..ee1ce73e7e38
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst4.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+exec 2>/dev/null
+! y=$(: </var/empty/nonexistent)
diff --git a/bin/sh/tests/expansion/cmdsubst5.0 b/bin/sh/tests/expansion/cmdsubst5.0
new file mode 100644
index 000000000000..afca3719e8b0
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst5.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+unset v
+exec 2>/dev/null
+! y=$(: ${v?})
diff --git a/bin/sh/tests/expansion/cmdsubst6.0 b/bin/sh/tests/expansion/cmdsubst6.0
new file mode 100644
index 000000000000..6586f330db5b
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst6.0
@@ -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/bin/sh/tests/expansion/cmdsubst7.0 b/bin/sh/tests/expansion/cmdsubst7.0
new file mode 100644
index 000000000000..dbd1aec5f9da
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst7.0
@@ -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/bin/sh/tests/expansion/cmdsubst8.0 b/bin/sh/tests/expansion/cmdsubst8.0
new file mode 100644
index 000000000000..52adaea33b93
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst8.0
@@ -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/bin/sh/tests/expansion/cmdsubst9.0 b/bin/sh/tests/expansion/cmdsubst9.0
new file mode 100644
index 000000000000..0b1f81f3537a
--- /dev/null
+++ b/bin/sh/tests/expansion/cmdsubst9.0
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+set -e
+
+cd /
+dummy=$(cd /bin)
+[ "$(pwd)" = / ]
+
+v=1
+dummy=$(eval v=2)
+[ "$v" = 1 ]
diff --git a/bin/sh/tests/expansion/export1.0 b/bin/sh/tests/expansion/export1.0
new file mode 100644
index 000000000000..4ee3ef4ffa41
--- /dev/null
+++ b/bin/sh/tests/expansion/export1.0
@@ -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/bin/sh/tests/expansion/export2.0 b/bin/sh/tests/expansion/export2.0
new file mode 100644
index 000000000000..57f64e7f7f7b
--- /dev/null
+++ b/bin/sh/tests/expansion/export2.0
@@ -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/bin/sh/tests/expansion/export3.0 b/bin/sh/tests/expansion/export3.0
new file mode 100644
index 000000000000..a1a0e6658a08
--- /dev/null
+++ b/bin/sh/tests/expansion/export3.0
@@ -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/bin/sh/tests/expansion/heredoc1.0 b/bin/sh/tests/expansion/heredoc1.0
new file mode 100644
index 000000000000..a67b2da2e5f2
--- /dev/null
+++ b/bin/sh/tests/expansion/heredoc1.0
@@ -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/bin/sh/tests/expansion/heredoc2.0 b/bin/sh/tests/expansion/heredoc2.0
new file mode 100644
index 000000000000..255143296d4a
--- /dev/null
+++ b/bin/sh/tests/expansion/heredoc2.0
@@ -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/bin/sh/tests/expansion/ifs1.0 b/bin/sh/tests/expansion/ifs1.0
new file mode 100644
index 000000000000..e7f53c77a5f9
--- /dev/null
+++ b/bin/sh/tests/expansion/ifs1.0
@@ -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/bin/sh/tests/expansion/ifs2.0 b/bin/sh/tests/expansion/ifs2.0
new file mode 100644
index 000000000000..e91b86707183
--- /dev/null
+++ b/bin/sh/tests/expansion/ifs2.0
@@ -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/bin/sh/tests/expansion/ifs3.0 b/bin/sh/tests/expansion/ifs3.0
new file mode 100644
index 000000000000..0569b5729cae
--- /dev/null
+++ b/bin/sh/tests/expansion/ifs3.0
@@ -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/bin/sh/tests/expansion/ifs4.0 b/bin/sh/tests/expansion/ifs4.0
new file mode 100644
index 000000000000..5b896a25413b
--- /dev/null
+++ b/bin/sh/tests/expansion/ifs4.0
@@ -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/bin/sh/tests/expansion/ifs5.0 b/bin/sh/tests/expansion/ifs5.0
new file mode 100644
index 000000000000..ab0e64662fdf
--- /dev/null
+++ b/bin/sh/tests/expansion/ifs5.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+set -- $(echo a b c d)
+[ "$#" = 4 ]
diff --git a/bin/sh/tests/expansion/ifs6.0 b/bin/sh/tests/expansion/ifs6.0
new file mode 100644
index 000000000000..be7794563085
--- /dev/null
+++ b/bin/sh/tests/expansion/ifs6.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+IFS=': '
+x=': :'
+set -- $x
+[ "$#|$1|$2|$3" = "2|||" ]
diff --git a/bin/sh/tests/expansion/ifs7.0 b/bin/sh/tests/expansion/ifs7.0
new file mode 100644
index 000000000000..0cc08348c04a
--- /dev/null
+++ b/bin/sh/tests/expansion/ifs7.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+IFS=2
+set -- $((123))
+[ "$#|$1|$2|$3" = "2|1|3|" ]
diff --git a/bin/sh/tests/expansion/length1.0 b/bin/sh/tests/expansion/length1.0
new file mode 100644
index 000000000000..2aaebf9048a2
--- /dev/null
+++ b/bin/sh/tests/expansion/length1.0
@@ -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/bin/sh/tests/expansion/length2.0 b/bin/sh/tests/expansion/length2.0
new file mode 100644
index 000000000000..d749b51f4b20
--- /dev/null
+++ b/bin/sh/tests/expansion/length2.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+v=$-
+[ "${#-}" = "${#v}" ] || echo '${#-} wrong'
diff --git a/bin/sh/tests/expansion/length3.0 b/bin/sh/tests/expansion/length3.0
new file mode 100644
index 000000000000..2093eed8bbd3
--- /dev/null
+++ b/bin/sh/tests/expansion/length3.0
@@ -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/bin/sh/tests/expansion/length4.0 b/bin/sh/tests/expansion/length4.0
new file mode 100644
index 000000000000..5348be511379
--- /dev/null
+++ b/bin/sh/tests/expansion/length4.0
@@ -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/bin/sh/tests/expansion/length5.0 b/bin/sh/tests/expansion/length5.0
new file mode 100644
index 000000000000..322ca162b0b6
--- /dev/null
+++ b/bin/sh/tests/expansion/length5.0
@@ -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/bin/sh/tests/expansion/length6.0 b/bin/sh/tests/expansion/length6.0
new file mode 100644
index 000000000000..6b78309f6b81
--- /dev/null
+++ b/bin/sh/tests/expansion/length6.0
@@ -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/bin/sh/tests/expansion/length7.0 b/bin/sh/tests/expansion/length7.0
new file mode 100644
index 000000000000..b79b11616c15
--- /dev/null
+++ b/bin/sh/tests/expansion/length7.0
@@ -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/bin/sh/tests/expansion/length8.0 b/bin/sh/tests/expansion/length8.0
new file mode 100644
index 000000000000..3cc6c15e7ece
--- /dev/null
+++ b/bin/sh/tests/expansion/length8.0
@@ -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/bin/sh/tests/expansion/local1.0 b/bin/sh/tests/expansion/local1.0
new file mode 100644
index 000000000000..34778351f1f3
--- /dev/null
+++ b/bin/sh/tests/expansion/local1.0
@@ -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/bin/sh/tests/expansion/local2.0 b/bin/sh/tests/expansion/local2.0
new file mode 100644
index 000000000000..19842900e0c1
--- /dev/null
+++ b/bin/sh/tests/expansion/local2.0
@@ -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/bin/sh/tests/expansion/pathname1.0 b/bin/sh/tests/expansion/pathname1.0
new file mode 100644
index 000000000000..a4bb0938837f
--- /dev/null
+++ b/bin/sh/tests/expansion/pathname1.0
@@ -0,0 +1,65 @@
+# $FreeBSD$
+
+unset LC_ALL
+LC_COLLATE=C
+export LC_COLLATE
+
+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/bin/sh/tests/expansion/pathname2.0 b/bin/sh/tests/expansion/pathname2.0
new file mode 100644
index 000000000000..5643cf907c87
--- /dev/null
+++ b/bin/sh/tests/expansion/pathname2.0
@@ -0,0 +1,35 @@
+# $FreeBSD$
+
+unset LC_ALL
+LC_COLLATE=C
+export LC_COLLATE
+
+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/bin/sh/tests/expansion/pathname3.0 b/bin/sh/tests/expansion/pathname3.0
new file mode 100644
index 000000000000..d1672e057ec5
--- /dev/null
+++ b/bin/sh/tests/expansion/pathname3.0
@@ -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/bin/sh/tests/expansion/pathname4.0 b/bin/sh/tests/expansion/pathname4.0
new file mode 100644
index 000000000000..18269c4318e6
--- /dev/null
+++ b/bin/sh/tests/expansion/pathname4.0
@@ -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/bin/sh/tests/expansion/pathname5.0 b/bin/sh/tests/expansion/pathname5.0
new file mode 100644
index 000000000000..bc278124de4f
--- /dev/null
+++ b/bin/sh/tests/expansion/pathname5.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+[ `echo '/[e]tc'` = /etc ]
diff --git a/bin/sh/tests/expansion/pathname6.0 b/bin/sh/tests/expansion/pathname6.0
new file mode 100644
index 000000000000..dc425ce64950
--- /dev/null
+++ b/bin/sh/tests/expansion/pathname6.0
@@ -0,0 +1,29 @@
+# $FreeBSD$
+
+unset LC_ALL
+LC_COLLATE=en_US.US-ASCII
+export LC_COLLATE
+
+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
+
+touch A B a b
+
+check '*' 'a A b B'
+
+exit $((failures != 0))
diff --git a/bin/sh/tests/expansion/plus-minus1.0 b/bin/sh/tests/expansion/plus-minus1.0
new file mode 100644
index 000000000000..9a6a53a2414a
--- /dev/null
+++ b/bin/sh/tests/expansion/plus-minus1.0
@@ -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/bin/sh/tests/expansion/plus-minus2.0 b/bin/sh/tests/expansion/plus-minus2.0
new file mode 100644
index 000000000000..f5a87525ae37
--- /dev/null
+++ b/bin/sh/tests/expansion/plus-minus2.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+e=
+test "${e:-\}}" = '}'
diff --git a/bin/sh/tests/expansion/plus-minus3.0 b/bin/sh/tests/expansion/plus-minus3.0
new file mode 100644
index 000000000000..49fcdc2f8f56
--- /dev/null
+++ b/bin/sh/tests/expansion/plus-minus3.0
@@ -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/bin/sh/tests/expansion/plus-minus4.0 b/bin/sh/tests/expansion/plus-minus4.0
new file mode 100644
index 000000000000..66dea3851efc
--- /dev/null
+++ b/bin/sh/tests/expansion/plus-minus4.0
@@ -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/bin/sh/tests/expansion/plus-minus5.0 b/bin/sh/tests/expansion/plus-minus5.0
new file mode 100644
index 000000000000..0b25e53cf82a
--- /dev/null
+++ b/bin/sh/tests/expansion/plus-minus5.0
@@ -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/bin/sh/tests/expansion/plus-minus6.0 b/bin/sh/tests/expansion/plus-minus6.0
new file mode 100644
index 000000000000..bc6680526823
--- /dev/null
+++ b/bin/sh/tests/expansion/plus-minus6.0
@@ -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/bin/sh/tests/expansion/plus-minus7.0 b/bin/sh/tests/expansion/plus-minus7.0
new file mode 100644
index 000000000000..9e81f58a0a99
--- /dev/null
+++ b/bin/sh/tests/expansion/plus-minus7.0
@@ -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/bin/sh/tests/expansion/plus-minus8.0 b/bin/sh/tests/expansion/plus-minus8.0
new file mode 100644
index 000000000000..beba009b0660
--- /dev/null
+++ b/bin/sh/tests/expansion/plus-minus8.0
@@ -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/bin/sh/tests/expansion/question1.0 b/bin/sh/tests/expansion/question1.0
new file mode 100644
index 000000000000..663c68d2046d
--- /dev/null
+++ b/bin/sh/tests/expansion/question1.0
@@ -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/bin/sh/tests/expansion/readonly1.0 b/bin/sh/tests/expansion/readonly1.0
new file mode 100644
index 000000000000..5ad0e143f81f
--- /dev/null
+++ b/bin/sh/tests/expansion/readonly1.0
@@ -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/bin/sh/tests/expansion/redir1.0 b/bin/sh/tests/expansion/redir1.0
new file mode 100644
index 000000000000..aa13e1561b68
--- /dev/null
+++ b/bin/sh/tests/expansion/redir1.0
@@ -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/bin/sh/tests/expansion/set-u1.0 b/bin/sh/tests/expansion/set-u1.0
new file mode 100644
index 000000000000..763eb7dcfc8f
--- /dev/null
+++ b/bin/sh/tests/expansion/set-u1.0
@@ -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/bin/sh/tests/expansion/set-u2.0 b/bin/sh/tests/expansion/set-u2.0
new file mode 100644
index 000000000000..f81aa62cb6ba
--- /dev/null
+++ b/bin/sh/tests/expansion/set-u2.0
@@ -0,0 +1,12 @@
+# $FreeBSD$
+
+set -u
+: $* $@ "$@" "$*"
+set -- x
+: $* $@ "$@" "$*"
+shift $#
+: $* $@ "$@" "$*"
+set -- y
+set --
+: $* $@ "$@" "$*"
+exit 0
diff --git a/bin/sh/tests/expansion/set-u3.0 b/bin/sh/tests/expansion/set-u3.0
new file mode 100644
index 000000000000..7f199b42c796
--- /dev/null
+++ b/bin/sh/tests/expansion/set-u3.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+set -u
+unset x
+v=$( (eval ': $((x))') 2>&1 >/dev/null)
+[ $? -ne 0 ] && [ -n "$v" ]
diff --git a/bin/sh/tests/expansion/tilde1.0 b/bin/sh/tests/expansion/tilde1.0
new file mode 100644
index 000000000000..7d8581bbc8ee
--- /dev/null
+++ b/bin/sh/tests/expansion/tilde1.0
@@ -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/bin/sh/tests/expansion/tilde2.0 b/bin/sh/tests/expansion/tilde2.0
new file mode 100644
index 000000000000..4f8ed9b491c7
--- /dev/null
+++ b/bin/sh/tests/expansion/tilde2.0
@@ -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/bin/sh/tests/expansion/trim1.0 b/bin/sh/tests/expansion/trim1.0
new file mode 100644
index 000000000000..b548e521792a
--- /dev/null
+++ b/bin/sh/tests/expansion/trim1.0
@@ -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/bin/sh/tests/expansion/trim2.0 b/bin/sh/tests/expansion/trim2.0
new file mode 100644
index 000000000000..619ef651cce6
--- /dev/null
+++ b/bin/sh/tests/expansion/trim2.0
@@ -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/bin/sh/tests/expansion/trim3.0 b/bin/sh/tests/expansion/trim3.0
new file mode 100644
index 000000000000..b89a04140c37
--- /dev/null
+++ b/bin/sh/tests/expansion/trim3.0
@@ -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/bin/sh/tests/expansion/trim4.0 b/bin/sh/tests/expansion/trim4.0
new file mode 100644
index 000000000000..1000bd3d0245
--- /dev/null
+++ b/bin/sh/tests/expansion/trim4.0
@@ -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/bin/sh/tests/expansion/trim5.0 b/bin/sh/tests/expansion/trim5.0
new file mode 100644
index 000000000000..937ec9a708e0
--- /dev/null
+++ b/bin/sh/tests/expansion/trim5.0
@@ -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/bin/sh/tests/expansion/trim6.0 b/bin/sh/tests/expansion/trim6.0
new file mode 100644
index 000000000000..3f753c4113f6
--- /dev/null
+++ b/bin/sh/tests/expansion/trim6.0
@@ -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/bin/sh/tests/expansion/trim7.0 b/bin/sh/tests/expansion/trim7.0
new file mode 100644
index 000000000000..352bdea920bf
--- /dev/null
+++ b/bin/sh/tests/expansion/trim7.0
@@ -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/bin/sh/tests/expansion/trim8.0 b/bin/sh/tests/expansion/trim8.0
new file mode 100644
index 000000000000..f7272f371dce
--- /dev/null
+++ b/bin/sh/tests/expansion/trim8.0
@@ -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/bin/sh/tests/expansion/trim9.0 b/bin/sh/tests/expansion/trim9.0
new file mode 100644
index 000000000000..47c825a52c3a
--- /dev/null
+++ b/bin/sh/tests/expansion/trim9.0
@@ -0,0 +1,61 @@
+# $FreeBSD$
+
+# POSIX does not specify these but they occasionally occur in the wild.
+# This just serves to keep working what currently works.
+
+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 'shift $#; set -- "${*#Q}"' '1|'
+testcase 'shift $#; set -- "${*##Q}"' '1|'
+testcase 'shift $#; set -- "${*%Q}"' '1|'
+testcase 'shift $#; set -- "${*%%Q}"' '1|'
+testcase 'set -- Q R; set -- "${*#Q}"' '1| R'
+testcase 'set -- Q R; set -- "${*##Q}"' '1| R'
+testcase 'set -- Q R; set -- "${*%R}"' '1|Q '
+testcase 'set -- Q R; set -- "${*%%R}"' '1|Q '
+testcase 'set -- Q R; set -- "${*#S}"' '1|Q R'
+testcase 'set -- Q R; set -- "${*##S}"' '1|Q R'
+testcase 'set -- Q R; set -- "${*%S}"' '1|Q R'
+testcase 'set -- Q R; set -- "${*%%S}"' '1|Q R'
+testcase 'set -- Q R; set -- ${*#Q}' '1|R'
+testcase 'set -- Q R; set -- ${*##Q}' '1|R'
+testcase 'set -- Q R; set -- ${*%R}' '1|Q'
+testcase 'set -- Q R; set -- ${*%%R}' '1|Q'
+testcase 'set -- Q R; set -- ${*#S}' '2|Q|R'
+testcase 'set -- Q R; set -- ${*##S}' '2|Q|R'
+testcase 'set -- Q R; set -- ${*%S}' '2|Q|R'
+testcase 'set -- Q R; set -- ${*%%S}' '2|Q|R'
+testcase 'set -- Q R; set -- ${@#Q}' '1|R'
+testcase 'set -- Q R; set -- ${@##Q}' '1|R'
+testcase 'set -- Q R; set -- ${@%R}' '1|Q'
+testcase 'set -- Q R; set -- ${@%%R}' '1|Q'
+testcase 'set -- Q R; set -- ${@#S}' '2|Q|R'
+testcase 'set -- Q R; set -- ${@##S}' '2|Q|R'
+testcase 'set -- Q R; set -- ${@%S}' '2|Q|R'
+testcase 'set -- Q R; set -- ${@%%S}' '2|Q|R'
+testcase 'set -- Q R; set -- "${@#Q}"' '2||R'
+testcase 'set -- Q R; set -- "${@%R}"' '2|Q|'
+testcase 'set -- Q R; set -- "${@%%R}"' '2|Q|'
+testcase 'set -- Q R; set -- "${@#S}"' '2|Q|R'
+testcase 'set -- Q R; set -- "${@##S}"' '2|Q|R'
+testcase 'set -- Q R; set -- "${@%S}"' '2|Q|R'
+testcase 'set -- Q R; set -- "${@%%S}"' '2|Q|R'
+
+test "x$failures" = x
diff --git a/bin/sh/tests/functional_test.sh b/bin/sh/tests/functional_test.sh
new file mode 100755
index 000000000000..698053885adc
--- /dev/null
+++ b/bin/sh/tests/functional_test.sh
@@ -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/bin/sh/tests/invocation/Makefile b/bin/sh/tests/invocation/Makefile
new file mode 100644
index 000000000000..b0bf97be9e69
--- /dev/null
+++ b/bin/sh/tests/invocation/Makefile
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH= functional_test
+
+${PACKAGE}FILES+= sh-ac1.0
+${PACKAGE}FILES+= sh-c-missing1.0
+${PACKAGE}FILES+= sh-c1.0
+${PACKAGE}FILES+= sh-ca1.0
+${PACKAGE}FILES+= sh-fca1.0
+
+.include <bsd.test.mk>
diff --git a/bin/sh/tests/invocation/sh-ac1.0 b/bin/sh/tests/invocation/sh-ac1.0
new file mode 100644
index 000000000000..0e0818b8a2c4
--- /dev/null
+++ b/bin/sh/tests/invocation/sh-ac1.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+# Test that attached options before c are processed
+
+case `${SH} -ac 'echo $-:$0' moo` in
+*a*:moo) true ;;
+*) false ;;
+esac
diff --git a/bin/sh/tests/invocation/sh-c-missing1.0 b/bin/sh/tests/invocation/sh-c-missing1.0
new file mode 100644
index 000000000000..6089a6df3eb1
--- /dev/null
+++ b/bin/sh/tests/invocation/sh-c-missing1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+! echo echo bad | ${SH} -c 2>/dev/null
diff --git a/bin/sh/tests/invocation/sh-c1.0 b/bin/sh/tests/invocation/sh-c1.0
new file mode 100644
index 000000000000..6bda19837b90
--- /dev/null
+++ b/bin/sh/tests/invocation/sh-c1.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+# Test that -c executes command_string with the given name and arg
+
+${SH} -c 'echo $0 $@' moo foo | grep -qx -- "moo foo"
diff --git a/bin/sh/tests/invocation/sh-ca1.0 b/bin/sh/tests/invocation/sh-ca1.0
new file mode 100644
index 000000000000..188b6453db2a
--- /dev/null
+++ b/bin/sh/tests/invocation/sh-ca1.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+# Test that attached options after c are processed
+
+case `${SH} -ca 'echo $-:$0' moo` in
+*a*:moo) true ;;
+*) false ;;
+esac
diff --git a/bin/sh/tests/invocation/sh-fca1.0 b/bin/sh/tests/invocation/sh-fca1.0
new file mode 100644
index 000000000000..c010a5ebacdf
--- /dev/null
+++ b/bin/sh/tests/invocation/sh-fca1.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+# Test that attached options before and after c are processed
+
+case `${SH} -fca 'echo $-:$-:$0:$@' foo -bar` in
+*f*:*a*:foo:-bar) true ;;
+*) false ;;
+esac
diff --git a/bin/sh/tests/parameters/Makefile b/bin/sh/tests/parameters/Makefile
new file mode 100644
index 000000000000..939bd199a72b
--- /dev/null
+++ b/bin/sh/tests/parameters/Makefile
@@ -0,0 +1,29 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH= functional_test
+
+${PACKAGE}FILES+= env1.0
+${PACKAGE}FILES+= exitstatus1.0
+${PACKAGE}FILES+= ifs1.0
+${PACKAGE}FILES+= mail1.0
+${PACKAGE}FILES+= mail2.0
+${PACKAGE}FILES+= optind1.0
+${PACKAGE}FILES+= optind2.0
+${PACKAGE}FILES+= positional1.0
+${PACKAGE}FILES+= positional2.0
+${PACKAGE}FILES+= positional3.0
+${PACKAGE}FILES+= positional4.0
+${PACKAGE}FILES+= positional5.0
+${PACKAGE}FILES+= positional6.0
+${PACKAGE}FILES+= positional7.0
+${PACKAGE}FILES+= positional8.0
+${PACKAGE}FILES+= positional9.0
+${PACKAGE}FILES+= pwd1.0
+${PACKAGE}FILES+= pwd2.0
+
+.include <bsd.test.mk>
diff --git a/bin/sh/tests/parameters/Makefile.depend b/bin/sh/tests/parameters/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/bin/sh/tests/parameters/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/bin/sh/tests/parameters/env1.0 b/bin/sh/tests/parameters/env1.0
new file mode 100644
index 000000000000..c0d4a2cc9141
--- /dev/null
+++ b/bin/sh/tests/parameters/env1.0
@@ -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/bin/sh/tests/parameters/exitstatus1.0 b/bin/sh/tests/parameters/exitstatus1.0
new file mode 100644
index 000000000000..696823d58043
--- /dev/null
+++ b/bin/sh/tests/parameters/exitstatus1.0
@@ -0,0 +1,9 @@
+# $FreeBSD$
+f() {
+ [ $? = $1 ] || exit 1
+}
+
+true
+f 0
+false
+f 1
diff --git a/bin/sh/tests/parameters/ifs1.0 b/bin/sh/tests/parameters/ifs1.0
new file mode 100644
index 000000000000..b93d99a8a8c7
--- /dev/null
+++ b/bin/sh/tests/parameters/ifs1.0
@@ -0,0 +1,10 @@
+# $FreeBSD$
+
+env IFS=_ ${SH} -c '
+rc=2
+nosuchtool_function() {
+ rc=0
+}
+v=nosuchtool_function
+$v && exit "$rc"
+'
diff --git a/bin/sh/tests/parameters/mail1.0 b/bin/sh/tests/parameters/mail1.0
new file mode 100644
index 000000000000..5791a5accc7c
--- /dev/null
+++ b/bin/sh/tests/parameters/mail1.0
@@ -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/bin/sh/tests/parameters/mail2.0 b/bin/sh/tests/parameters/mail2.0
new file mode 100644
index 000000000000..343c99de9b27
--- /dev/null
+++ b/bin/sh/tests/parameters/mail2.0
@@ -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/bin/sh/tests/parameters/optind1.0 b/bin/sh/tests/parameters/optind1.0
new file mode 100644
index 000000000000..33e0288e861f
--- /dev/null
+++ b/bin/sh/tests/parameters/optind1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+unset OPTIND && [ -z "$OPTIND" ]
diff --git a/bin/sh/tests/parameters/optind2.0 b/bin/sh/tests/parameters/optind2.0
new file mode 100644
index 000000000000..a7689f6841a5
--- /dev/null
+++ b/bin/sh/tests/parameters/optind2.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+[ "$(OPTIND=42 ${SH} -c 'printf %s "$OPTIND"')" = 1 ]
diff --git a/bin/sh/tests/parameters/positional1.0 b/bin/sh/tests/parameters/positional1.0
new file mode 100644
index 000000000000..67d19516a5d9
--- /dev/null
+++ b/bin/sh/tests/parameters/positional1.0
@@ -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/bin/sh/tests/parameters/positional2.0 b/bin/sh/tests/parameters/positional2.0
new file mode 100644
index 000000000000..fcec2a4b676e
--- /dev/null
+++ b/bin/sh/tests/parameters/positional2.0
@@ -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/bin/sh/tests/parameters/positional3.0 b/bin/sh/tests/parameters/positional3.0
new file mode 100644
index 000000000000..1200469b4e12
--- /dev/null
+++ b/bin/sh/tests/parameters/positional3.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+r=$(${SH} -c 'echo ${01:+yes}${010:+yes}' '' a '' '' '' '' '' '' '' '' b)
+[ "$r" = yesyes ]
diff --git a/bin/sh/tests/parameters/positional4.0 b/bin/sh/tests/parameters/positional4.0
new file mode 100644
index 000000000000..c1c380c0feac
--- /dev/null
+++ b/bin/sh/tests/parameters/positional4.0
@@ -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/bin/sh/tests/parameters/positional5.0 b/bin/sh/tests/parameters/positional5.0
new file mode 100644
index 000000000000..eeaaba5321ff
--- /dev/null
+++ b/bin/sh/tests/parameters/positional5.0
@@ -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/bin/sh/tests/parameters/positional6.0 b/bin/sh/tests/parameters/positional6.0
new file mode 100644
index 000000000000..1410668b8a9f
--- /dev/null
+++ b/bin/sh/tests/parameters/positional6.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+IFS=?
+set p r
+v=pqrs
+r=${v#"$*"}
+[ "$r" = pqrs ]
diff --git a/bin/sh/tests/parameters/positional7.0 b/bin/sh/tests/parameters/positional7.0
new file mode 100644
index 000000000000..f170ad343996
--- /dev/null
+++ b/bin/sh/tests/parameters/positional7.0
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+set -- / ''
+IFS=*
+set -- "$*"
+IFS=:
+args="$*"
+[ "$#:$args" = "1:/*" ]
diff --git a/bin/sh/tests/parameters/positional8.0 b/bin/sh/tests/parameters/positional8.0
new file mode 100644
index 000000000000..4c4dbd5cf1a6
--- /dev/null
+++ b/bin/sh/tests/parameters/positional8.0
@@ -0,0 +1,31 @@
+# $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 'shift $#; set -- ""$*' '1|'
+testcase 'shift $#; set -- $*""' '1|'
+testcase 'shift $#; set -- ""$@' '1|'
+testcase 'shift $#; set -- $@""' '1|'
+testcase 'shift $#; set -- """$*"' '1|'
+testcase 'shift $#; set -- "$*"""' '1|'
+testcase 'shift $#; set -- """$@"' '1|'
+testcase 'shift $#; set -- "$@"""' '1|'
+
+test "x$failures" = x
diff --git a/bin/sh/tests/parameters/positional9.0 b/bin/sh/tests/parameters/positional9.0
new file mode 100644
index 000000000000..8571bfaf6135
--- /dev/null
+++ b/bin/sh/tests/parameters/positional9.0
@@ -0,0 +1,18 @@
+# $FreeBSD$
+# Although POSIX leaves the result of expanding ${#@} and ${#*} unspecified,
+# make sure it is at least numeric.
+
+set -- bb cc ddd
+set -f
+lengths=${#*}${#@}"${#*}${#@}"$(echo ${#*}${#@}"${#*}${#@}")
+IFS=
+lengths=$lengths${#*}${#@}"${#*}${#@}"$(echo ${#*}${#@}"${#*}${#@}")
+case $lengths in
+*[!0-9]*)
+ printf 'bad: %s\n' "$lengths"
+ exit 3 ;;
+????????????????*) ;;
+*)
+ printf 'too short: %s\n' "$lengths"
+ exit 3 ;;
+esac
diff --git a/bin/sh/tests/parameters/pwd1.0 b/bin/sh/tests/parameters/pwd1.0
new file mode 100644
index 000000000000..0099379a9d3c
--- /dev/null
+++ b/bin/sh/tests/parameters/pwd1.0
@@ -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/bin/sh/tests/parameters/pwd2.0 b/bin/sh/tests/parameters/pwd2.0
new file mode 100644
index 000000000000..2297f8b753d4
--- /dev/null
+++ b/bin/sh/tests/parameters/pwd2.0
@@ -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/bin/sh/tests/parser/Makefile b/bin/sh/tests/parser/Makefile
new file mode 100644
index 000000000000..28816a926807
--- /dev/null
+++ b/bin/sh/tests/parser/Makefile
@@ -0,0 +1,88 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH= functional_test
+
+${PACKAGE}FILES+= alias1.0
+${PACKAGE}FILES+= alias2.0
+${PACKAGE}FILES+= alias3.0
+${PACKAGE}FILES+= alias4.0
+${PACKAGE}FILES+= alias5.0
+${PACKAGE}FILES+= alias6.0
+${PACKAGE}FILES+= alias7.0
+${PACKAGE}FILES+= alias8.0
+${PACKAGE}FILES+= alias9.0
+${PACKAGE}FILES+= alias10.0
+${PACKAGE}FILES+= alias11.0
+${PACKAGE}FILES+= alias12.0
+${PACKAGE}FILES+= alias13.0
+${PACKAGE}FILES+= alias14.0
+${PACKAGE}FILES+= alias15.0 alias15.0.stdout
+${PACKAGE}FILES+= alias16.0
+${PACKAGE}FILES+= alias17.0
+${PACKAGE}FILES+= alias18.0
+${PACKAGE}FILES+= and-pipe-not.0
+${PACKAGE}FILES+= case1.0
+${PACKAGE}FILES+= case2.0
+${PACKAGE}FILES+= comment1.0
+${PACKAGE}FILES+= comment2.42
+${PACKAGE}FILES+= dollar-quote1.0
+${PACKAGE}FILES+= dollar-quote2.0
+${PACKAGE}FILES+= dollar-quote3.0
+${PACKAGE}FILES+= dollar-quote4.0
+${PACKAGE}FILES+= dollar-quote5.0
+${PACKAGE}FILES+= dollar-quote6.0
+${PACKAGE}FILES+= dollar-quote7.0
+${PACKAGE}FILES+= dollar-quote8.0
+${PACKAGE}FILES+= dollar-quote9.0
+${PACKAGE}FILES+= dollar-quote10.0
+${PACKAGE}FILES+= dollar-quote11.0
+${PACKAGE}FILES+= dollar-quote12.0
+${PACKAGE}FILES+= dollar-quote13.0
+${PACKAGE}FILES+= empty-braces1.0
+${PACKAGE}FILES+= empty-cmd1.0
+${PACKAGE}FILES+= for1.0
+${PACKAGE}FILES+= for2.0
+${PACKAGE}FILES+= func1.0
+${PACKAGE}FILES+= func2.0
+${PACKAGE}FILES+= func3.0
+${PACKAGE}FILES+= heredoc1.0
+${PACKAGE}FILES+= heredoc2.0
+${PACKAGE}FILES+= heredoc3.0
+${PACKAGE}FILES+= heredoc4.0
+${PACKAGE}FILES+= heredoc5.0
+${PACKAGE}FILES+= heredoc6.0
+${PACKAGE}FILES+= heredoc7.0
+${PACKAGE}FILES+= heredoc8.0
+${PACKAGE}FILES+= heredoc9.0
+${PACKAGE}FILES+= heredoc10.0
+${PACKAGE}FILES+= heredoc11.0
+${PACKAGE}FILES+= heredoc12.0
+${PACKAGE}FILES+= heredoc13.0
+${PACKAGE}FILES+= line-cont1.0
+${PACKAGE}FILES+= line-cont2.0
+${PACKAGE}FILES+= line-cont3.0
+${PACKAGE}FILES+= line-cont4.0
+${PACKAGE}FILES+= line-cont5.0
+${PACKAGE}FILES+= line-cont6.0
+${PACKAGE}FILES+= line-cont7.0
+${PACKAGE}FILES+= line-cont8.0
+${PACKAGE}FILES+= line-cont9.0
+${PACKAGE}FILES+= line-cont10.0
+${PACKAGE}FILES+= line-cont11.0
+${PACKAGE}FILES+= no-space1.0
+${PACKAGE}FILES+= no-space2.0
+${PACKAGE}FILES+= nul1.0
+${PACKAGE}FILES+= only-redir1.0
+${PACKAGE}FILES+= only-redir2.0
+${PACKAGE}FILES+= only-redir3.0
+${PACKAGE}FILES+= only-redir4.0
+${PACKAGE}FILES+= pipe-not1.0
+${PACKAGE}FILES+= set-v1.0 set-v1.0.stderr
+${PACKAGE}FILES+= var-assign1.0
+
+.include <bsd.test.mk>
diff --git a/bin/sh/tests/parser/Makefile.depend b/bin/sh/tests/parser/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/bin/sh/tests/parser/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/bin/sh/tests/parser/alias1.0 b/bin/sh/tests/parser/alias1.0
new file mode 100644
index 000000000000..75dd9ab9b8f1
--- /dev/null
+++ b/bin/sh/tests/parser/alias1.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+alias alias0=exit
+eval 'alias0 0'
+exit 1
diff --git a/bin/sh/tests/parser/alias10.0 b/bin/sh/tests/parser/alias10.0
new file mode 100644
index 000000000000..30d99f49bbf4
--- /dev/null
+++ b/bin/sh/tests/parser/alias10.0
@@ -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/bin/sh/tests/parser/alias11.0 b/bin/sh/tests/parser/alias11.0
new file mode 100644
index 000000000000..522264ff823f
--- /dev/null
+++ b/bin/sh/tests/parser/alias11.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+alias alias0=alias1
+alias alias1=exit
+eval 'alias0 0'
+exit 3
diff --git a/bin/sh/tests/parser/alias12.0 b/bin/sh/tests/parser/alias12.0
new file mode 100644
index 000000000000..2e4379155d4a
--- /dev/null
+++ b/bin/sh/tests/parser/alias12.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+unalias -a
+alias alias0=command
+alias true='echo bad'
+eval 'alias0 true'
diff --git a/bin/sh/tests/parser/alias13.0 b/bin/sh/tests/parser/alias13.0
new file mode 100644
index 000000000000..53b949dc23e9
--- /dev/null
+++ b/bin/sh/tests/parser/alias13.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+unalias -a
+alias command=command
+alias true='echo bad'
+eval 'command true'
diff --git a/bin/sh/tests/parser/alias14.0 b/bin/sh/tests/parser/alias14.0
new file mode 100644
index 000000000000..1b92fc07d5b2
--- /dev/null
+++ b/bin/sh/tests/parser/alias14.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+alias command='command '
+alias alias0=exit
+eval 'command alias0 0'
+exit 3
diff --git a/bin/sh/tests/parser/alias15.0 b/bin/sh/tests/parser/alias15.0
new file mode 100644
index 000000000000..f0fbadbb20e7
--- /dev/null
+++ b/bin/sh/tests/parser/alias15.0
@@ -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/bin/sh/tests/parser/alias15.0.stdout b/bin/sh/tests/parser/alias15.0.stdout
new file mode 100644
index 000000000000..6dd179c065a7
--- /dev/null
+++ b/bin/sh/tests/parser/alias15.0.stdout
@@ -0,0 +1,4 @@
+echo test1
+test1
+echo test2
+test2
diff --git a/bin/sh/tests/parser/alias16.0 b/bin/sh/tests/parser/alias16.0
new file mode 100644
index 000000000000..2df9c254e57d
--- /dev/null
+++ b/bin/sh/tests/parser/alias16.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+v=1
+alias a='unalias a
+v=2'
+eval a
+[ "$v" = 2 ]
diff --git a/bin/sh/tests/parser/alias17.0 b/bin/sh/tests/parser/alias17.0
new file mode 100644
index 000000000000..005eeb72dee4
--- /dev/null
+++ b/bin/sh/tests/parser/alias17.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+v=1
+alias a='unalias -a
+v=2'
+eval a
+[ "$v" = 2 ]
diff --git a/bin/sh/tests/parser/alias18.0 b/bin/sh/tests/parser/alias18.0
new file mode 100644
index 000000000000..74234fe72c05
--- /dev/null
+++ b/bin/sh/tests/parser/alias18.0
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+v=1
+alias a='alias a=v=2
+v=3
+a'
+eval a
+[ "$v" = 2 ]
diff --git a/bin/sh/tests/parser/alias2.0 b/bin/sh/tests/parser/alias2.0
new file mode 100644
index 000000000000..ae99b8a588c2
--- /dev/null
+++ b/bin/sh/tests/parser/alias2.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+alias alias0=exit
+x=alias0
+eval 'case $x in alias0) exit 0;; esac'
+exit 1
diff --git a/bin/sh/tests/parser/alias3.0 b/bin/sh/tests/parser/alias3.0
new file mode 100644
index 000000000000..e0721e2aaa01
--- /dev/null
+++ b/bin/sh/tests/parser/alias3.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+alias alias0=exit
+x=alias0
+eval 'case $x in "alias0") alias0 0;; esac'
+exit 1
diff --git a/bin/sh/tests/parser/alias4.0 b/bin/sh/tests/parser/alias4.0
new file mode 100644
index 000000000000..19332ed09056
--- /dev/null
+++ b/bin/sh/tests/parser/alias4.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+alias alias0=exit
+eval 'x=1 alias0 0'
+exit 1
diff --git a/bin/sh/tests/parser/alias5.0 b/bin/sh/tests/parser/alias5.0
new file mode 100644
index 000000000000..3d0205fd2332
--- /dev/null
+++ b/bin/sh/tests/parser/alias5.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+alias alias0=exit
+eval '</dev/null alias0 0'
+exit 1
diff --git a/bin/sh/tests/parser/alias6.0 b/bin/sh/tests/parser/alias6.0
new file mode 100644
index 000000000000..c723d08ab3eb
--- /dev/null
+++ b/bin/sh/tests/parser/alias6.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+alias alias0='| cat >/dev/null'
+
+eval '{ echo bad; } alias0'
+eval '(echo bad)alias0'
diff --git a/bin/sh/tests/parser/alias7.0 b/bin/sh/tests/parser/alias7.0
new file mode 100644
index 000000000000..b26f0dd067cd
--- /dev/null
+++ b/bin/sh/tests/parser/alias7.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+alias echo='echo a'
+[ "`eval echo b`" = "a b" ]
diff --git a/bin/sh/tests/parser/alias8.0 b/bin/sh/tests/parser/alias8.0
new file mode 100644
index 000000000000..7fc2f15f0931
--- /dev/null
+++ b/bin/sh/tests/parser/alias8.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+alias echo='echo'
+[ "`eval echo b`" = b ]
diff --git a/bin/sh/tests/parser/alias9.0 b/bin/sh/tests/parser/alias9.0
new file mode 100644
index 000000000000..6bd8808cc379
--- /dev/null
+++ b/bin/sh/tests/parser/alias9.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+alias alias0=:
+alias alias0=exit
+eval 'alias0 0'
+exit 1
diff --git a/bin/sh/tests/parser/and-pipe-not.0 b/bin/sh/tests/parser/and-pipe-not.0
new file mode 100644
index 000000000000..35b125c14767
--- /dev/null
+++ b/bin/sh/tests/parser/and-pipe-not.0
@@ -0,0 +1,2 @@
+# $FreeBSD$
+true && ! true | false
diff --git a/bin/sh/tests/parser/case1.0 b/bin/sh/tests/parser/case1.0
new file mode 100644
index 000000000000..49b4c45155fa
--- /dev/null
+++ b/bin/sh/tests/parser/case1.0
@@ -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/bin/sh/tests/parser/case2.0 b/bin/sh/tests/parser/case2.0
new file mode 100644
index 000000000000..14610e415c19
--- /dev/null
+++ b/bin/sh/tests/parser/case2.0
@@ -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/bin/sh/tests/parser/comment1.0 b/bin/sh/tests/parser/comment1.0
new file mode 100644
index 000000000000..21e7ade957bc
--- /dev/null
+++ b/bin/sh/tests/parser/comment1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+${SH} -c '#'
diff --git a/bin/sh/tests/parser/comment2.42 b/bin/sh/tests/parser/comment2.42
new file mode 100644
index 000000000000..196b73354493
--- /dev/null
+++ b/bin/sh/tests/parser/comment2.42
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+${SH} -c '#
+exit 42'
diff --git a/bin/sh/tests/parser/dollar-quote1.0 b/bin/sh/tests/parser/dollar-quote1.0
new file mode 100644
index 000000000000..12061417e108
--- /dev/null
+++ b/bin/sh/tests/parser/dollar-quote1.0
@@ -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/bin/sh/tests/parser/dollar-quote10.0 b/bin/sh/tests/parser/dollar-quote10.0
new file mode 100644
index 000000000000..ad166da23ffe
--- /dev/null
+++ b/bin/sh/tests/parser/dollar-quote10.0
@@ -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/bin/sh/tests/parser/dollar-quote11.0 b/bin/sh/tests/parser/dollar-quote11.0
new file mode 100644
index 000000000000..2e872abfe5b0
--- /dev/null
+++ b/bin/sh/tests/parser/dollar-quote11.0
@@ -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/bin/sh/tests/parser/dollar-quote12.0 b/bin/sh/tests/parser/dollar-quote12.0
new file mode 100644
index 000000000000..838e27cda706
--- /dev/null
+++ b/bin/sh/tests/parser/dollar-quote12.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+# \u without any digits at all remains invalid.
+# Our choice is a parse error.
+
+v=$( (eval ": \$'\u'") 2>&1 >/dev/null)
+[ $? -ne 0 ] && [ -n "$v" ]
diff --git a/bin/sh/tests/parser/dollar-quote13.0 b/bin/sh/tests/parser/dollar-quote13.0
new file mode 100644
index 000000000000..2247da7abbc9
--- /dev/null
+++ b/bin/sh/tests/parser/dollar-quote13.0
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+# This Unicode escape sequence that has never been in range should either
+# fail to expand or expand to a fallback.
+
+c=$(eval printf %s \$\'\\Uffffff41\' 2>/dev/null)
+r=$(($? != 0))
+[ "$r.$c" = '1.' ] || [ "$r.$c" = '0.?' ] || [ "$r.$c" = $'0.\u2222' ]
diff --git a/bin/sh/tests/parser/dollar-quote2.0 b/bin/sh/tests/parser/dollar-quote2.0
new file mode 100644
index 000000000000..4617ed8d9086
--- /dev/null
+++ b/bin/sh/tests/parser/dollar-quote2.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+# This depends on the ASCII character set.
+
+[ $'\e' = "$(printf "\033")" ]
diff --git a/bin/sh/tests/parser/dollar-quote3.0 b/bin/sh/tests/parser/dollar-quote3.0
new file mode 100644
index 000000000000..a7e68527791c
--- /dev/null
+++ b/bin/sh/tests/parser/dollar-quote3.0
@@ -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/bin/sh/tests/parser/dollar-quote4.0 b/bin/sh/tests/parser/dollar-quote4.0
new file mode 100644
index 000000000000..f620af5b12c2
--- /dev/null
+++ b/bin/sh/tests/parser/dollar-quote4.0
@@ -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/bin/sh/tests/parser/dollar-quote5.0 b/bin/sh/tests/parser/dollar-quote5.0
new file mode 100644
index 000000000000..c2c44ca620e5
--- /dev/null
+++ b/bin/sh/tests/parser/dollar-quote5.0
@@ -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/bin/sh/tests/parser/dollar-quote6.0 b/bin/sh/tests/parser/dollar-quote6.0
new file mode 100644
index 000000000000..a4b1e3f48729
--- /dev/null
+++ b/bin/sh/tests/parser/dollar-quote6.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+# This depends on the ASCII character set.
+
+[ $'\c\\' = $'\034' ]
diff --git a/bin/sh/tests/parser/dollar-quote7.0 b/bin/sh/tests/parser/dollar-quote7.0
new file mode 100644
index 000000000000..c866b1af68bb
--- /dev/null
+++ b/bin/sh/tests/parser/dollar-quote7.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+set -e
+
+[ $'\u0024\u0040\u0060' = '$@`' ]
+[ $'\U00000024\U00000040\U00000060' = '$@`' ]
diff --git a/bin/sh/tests/parser/dollar-quote8.0 b/bin/sh/tests/parser/dollar-quote8.0
new file mode 100644
index 000000000000..8f0b41a0a3f7
--- /dev/null
+++ b/bin/sh/tests/parser/dollar-quote8.0
@@ -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/bin/sh/tests/parser/dollar-quote9.0 b/bin/sh/tests/parser/dollar-quote9.0
new file mode 100644
index 000000000000..df64b7dfc0b1
--- /dev/null
+++ b/bin/sh/tests/parser/dollar-quote9.0
@@ -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/bin/sh/tests/parser/empty-braces1.0 b/bin/sh/tests/parser/empty-braces1.0
new file mode 100644
index 000000000000..5ab443c48d8a
--- /dev/null
+++ b/bin/sh/tests/parser/empty-braces1.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+# Unfortunately, some scripts depend on the extension of allowing an empty
+# pair of braces.
+
+{ } &
+wait $!
diff --git a/bin/sh/tests/parser/empty-cmd1.0 b/bin/sh/tests/parser/empty-cmd1.0
new file mode 100644
index 000000000000..f8b01e9c7997
--- /dev/null
+++ b/bin/sh/tests/parser/empty-cmd1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+! (eval ': || f()') 2>/dev/null
diff --git a/bin/sh/tests/parser/for1.0 b/bin/sh/tests/parser/for1.0
new file mode 100644
index 000000000000..eb7c881237fd
--- /dev/null
+++ b/bin/sh/tests/parser/for1.0
@@ -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/bin/sh/tests/parser/for2.0 b/bin/sh/tests/parser/for2.0
new file mode 100644
index 000000000000..54ebfc3d7193
--- /dev/null
+++ b/bin/sh/tests/parser/for2.0
@@ -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/bin/sh/tests/parser/func1.0 b/bin/sh/tests/parser/func1.0
new file mode 100644
index 000000000000..4e887b25f285
--- /dev/null
+++ b/bin/sh/tests/parser/func1.0
@@ -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/bin/sh/tests/parser/func2.0 b/bin/sh/tests/parser/func2.0
new file mode 100644
index 000000000000..5fd4dda8cca9
--- /dev/null
+++ b/bin/sh/tests/parser/func2.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+f() { return 42; }
+f() { return 3; } &
+f
+[ $? -eq 42 ]
diff --git a/bin/sh/tests/parser/func3.0 b/bin/sh/tests/parser/func3.0
new file mode 100644
index 000000000000..dcac7323ad3f
--- /dev/null
+++ b/bin/sh/tests/parser/func3.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+name=/var/empty/nosuch
+f() { true; } <$name
+name=/dev/null
+f
diff --git a/bin/sh/tests/parser/heredoc1.0 b/bin/sh/tests/parser/heredoc1.0
new file mode 100644
index 000000000000..5ce38977d7f5
--- /dev/null
+++ b/bin/sh/tests/parser/heredoc1.0
@@ -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/bin/sh/tests/parser/heredoc10.0 b/bin/sh/tests/parser/heredoc10.0
new file mode 100644
index 000000000000..27369a0b1b4c
--- /dev/null
+++ b/bin/sh/tests/parser/heredoc10.0
@@ -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/bin/sh/tests/parser/heredoc11.0 b/bin/sh/tests/parser/heredoc11.0
new file mode 100644
index 000000000000..5839e46b36ce
--- /dev/null
+++ b/bin/sh/tests/parser/heredoc11.0
@@ -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/bin/sh/tests/parser/heredoc12.0 b/bin/sh/tests/parser/heredoc12.0
new file mode 100644
index 000000000000..964838453237
--- /dev/null
+++ b/bin/sh/tests/parser/heredoc12.0
@@ -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/bin/sh/tests/parser/heredoc13.0 b/bin/sh/tests/parser/heredoc13.0
new file mode 100644
index 000000000000..225d4f08f492
--- /dev/null
+++ b/bin/sh/tests/parser/heredoc13.0
@@ -0,0 +1,21 @@
+# $FreeBSD$
+
+failures=0
+
+check() {
+ if ! eval "[ $* ]"; then
+ echo "Failed: $*"
+ : $((failures += 1))
+ fi
+}
+
+check '"$(cat <<""
+
+echo yes)" = "yes"'
+
+check '"$(cat <<""
+yes
+
+)" = "yes"'
+
+exit $((failures != 0))
diff --git a/bin/sh/tests/parser/heredoc2.0 b/bin/sh/tests/parser/heredoc2.0
new file mode 100644
index 000000000000..4bb85ad80c17
--- /dev/null
+++ b/bin/sh/tests/parser/heredoc2.0
@@ -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/bin/sh/tests/parser/heredoc3.0 b/bin/sh/tests/parser/heredoc3.0
new file mode 100644
index 000000000000..b250272f3319
--- /dev/null
+++ b/bin/sh/tests/parser/heredoc3.0
@@ -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/bin/sh/tests/parser/heredoc4.0 b/bin/sh/tests/parser/heredoc4.0
new file mode 100644
index 000000000000..fa3af5fd5a97
--- /dev/null
+++ b/bin/sh/tests/parser/heredoc4.0
@@ -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/bin/sh/tests/parser/heredoc5.0 b/bin/sh/tests/parser/heredoc5.0
new file mode 100644
index 000000000000..84b0eb2705d4
--- /dev/null
+++ b/bin/sh/tests/parser/heredoc5.0
@@ -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/bin/sh/tests/parser/heredoc6.0 b/bin/sh/tests/parser/heredoc6.0
new file mode 100644
index 000000000000..3a634de167a7
--- /dev/null
+++ b/bin/sh/tests/parser/heredoc6.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+
+r=
+! command eval ": <<EOF; )" 2>/dev/null; command eval : hi \${r:=0}
+exit ${r:-3}
diff --git a/bin/sh/tests/parser/heredoc7.0 b/bin/sh/tests/parser/heredoc7.0
new file mode 100644
index 000000000000..a15010648780
--- /dev/null
+++ b/bin/sh/tests/parser/heredoc7.0
@@ -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/bin/sh/tests/parser/heredoc8.0 b/bin/sh/tests/parser/heredoc8.0
new file mode 100644
index 000000000000..598358a0494b
--- /dev/null
+++ b/bin/sh/tests/parser/heredoc8.0
@@ -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/bin/sh/tests/parser/heredoc9.0 b/bin/sh/tests/parser/heredoc9.0
new file mode 100644
index 000000000000..125a542ab717
--- /dev/null
+++ b/bin/sh/tests/parser/heredoc9.0
@@ -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/bin/sh/tests/parser/line-cont1.0 b/bin/sh/tests/parser/line-cont1.0
new file mode 100644
index 000000000000..7ef5eba82b1e
--- /dev/null
+++ b/bin/sh/tests/parser/line-cont1.0
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+i\
+f
+t\
+r\
+u\
+e
+t\
+h\
+e\
+n
+:
+\
+f\
+i
diff --git a/bin/sh/tests/parser/line-cont10.0 b/bin/sh/tests/parser/line-cont10.0
new file mode 100644
index 000000000000..1e74108757a0
--- /dev/null
+++ b/bin/sh/tests/parser/line-cont10.0
@@ -0,0 +1,18 @@
+# $FreeBSD$
+
+v=XaaaXbbbX
+[ "${v\
+#\
+*\
+a}.${v\
+#\
+#\
+*\
+a}.${v\
+%\
+b\
+*}.${v\
+%\
+%\
+b\
+*}" = aaXbbbX.XbbbX.XaaaXbb.XaaaX ]
diff --git a/bin/sh/tests/parser/line-cont11.0 b/bin/sh/tests/parser/line-cont11.0
new file mode 100644
index 000000000000..22e49758dbc6
--- /dev/null
+++ b/bin/sh/tests/parser/line-cont11.0
@@ -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/bin/sh/tests/parser/line-cont2.0 b/bin/sh/tests/parser/line-cont2.0
new file mode 100644
index 000000000000..9a293faf6bc2
--- /dev/null
+++ b/bin/sh/tests/parser/line-cont2.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+
+[ "a\
+b" = ab ]
diff --git a/bin/sh/tests/parser/line-cont3.0 b/bin/sh/tests/parser/line-cont3.0
new file mode 100644
index 000000000000..09d3aa8bba83
--- /dev/null
+++ b/bin/sh/tests/parser/line-cont3.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+v=`printf %s 'a\
+b'`
+w="`printf %s 'c\
+d'`"
+[ "$v$w" = abcd ]
diff --git a/bin/sh/tests/parser/line-cont4.0 b/bin/sh/tests/parser/line-cont4.0
new file mode 100644
index 000000000000..5803276f2b3b
--- /dev/null
+++ b/bin/sh/tests/parser/line-cont4.0
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+v=abcd
+[ "$\
+v.$\
+{v}.${\
+v}.${v\
+}" = abcd.abcd.abcd.abcd ]
diff --git a/bin/sh/tests/parser/line-cont5.0 b/bin/sh/tests/parser/line-cont5.0
new file mode 100644
index 000000000000..a7aa02688310
--- /dev/null
+++ b/bin/sh/tests/parser/line-cont5.0
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+bad=1
+case x in
+x\
+) ;\
+; *) exit 7
+esac &\
+& bad= &\
+& : >\
+>/dev/null
+
+false |\
+| [ -z "$bad" ]
diff --git a/bin/sh/tests/parser/line-cont6.0 b/bin/sh/tests/parser/line-cont6.0
new file mode 100644
index 000000000000..b12125b929e5
--- /dev/null
+++ b/bin/sh/tests/parser/line-cont6.0
@@ -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/bin/sh/tests/parser/line-cont7.0 b/bin/sh/tests/parser/line-cont7.0
new file mode 100644
index 000000000000..27f8aec9515c
--- /dev/null
+++ b/bin/sh/tests/parser/line-cont7.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+[ "$(\
+(
+1\
++ 1)\
+)" = 2 ]
diff --git a/bin/sh/tests/parser/line-cont8.0 b/bin/sh/tests/parser/line-cont8.0
new file mode 100644
index 000000000000..88667760b47b
--- /dev/null
+++ b/bin/sh/tests/parser/line-cont8.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+set -- a b c d e f g h i j
+[ "${1\
+0\
+}" = j ]
diff --git a/bin/sh/tests/parser/line-cont9.0 b/bin/sh/tests/parser/line-cont9.0
new file mode 100644
index 000000000000..4e73c8f04abc
--- /dev/null
+++ b/bin/sh/tests/parser/line-cont9.0
@@ -0,0 +1,6 @@
+# $FreeBSD$
+
+[ "${$\
+:\
++\
+xyz}" = xyz ]
diff --git a/bin/sh/tests/parser/no-space1.0 b/bin/sh/tests/parser/no-space1.0
new file mode 100644
index 000000000000..6df9f6395ff6
--- /dev/null
+++ b/bin/sh/tests/parser/no-space1.0
@@ -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/bin/sh/tests/parser/no-space2.0 b/bin/sh/tests/parser/no-space2.0
new file mode 100644
index 000000000000..4e8447b11e4c
--- /dev/null
+++ b/bin/sh/tests/parser/no-space2.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+# This conflicts with ksh extended patterns but occurs in the wild.
+
+set -e
+
+!(false)
diff --git a/bin/sh/tests/parser/nul1.0 b/bin/sh/tests/parser/nul1.0
new file mode 100644
index 000000000000..49c5ab1b0cfb
--- /dev/null
+++ b/bin/sh/tests/parser/nul1.0
@@ -0,0 +1,12 @@
+# $FreeBSD$
+# Although POSIX does not specify the effect of NUL bytes in scripts,
+# we ignore them.
+
+{
+ printf 'v=%03000d\0%02000d' 7 2
+ dd if=/dev/zero bs=1000 count=1 status=none
+ printf '1 w=%03000d%02000d1\0\n' 7 2
+ printf '\0l\0v\0=\0$\0{\0#\0v\0}\n'
+ printf '\0l\0w\0=\0\0$\0{\0#\0w}\0\0\0\n'
+ printf '[ "$lv.$lw.$v" = "5001.5001.$w" ]\n'
+} | ${SH}
diff --git a/bin/sh/tests/parser/only-redir1.0 b/bin/sh/tests/parser/only-redir1.0
new file mode 100644
index 000000000000..46076c882a5e
--- /dev/null
+++ b/bin/sh/tests/parser/only-redir1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+</dev/null &
+wait $!
diff --git a/bin/sh/tests/parser/only-redir2.0 b/bin/sh/tests/parser/only-redir2.0
new file mode 100644
index 000000000000..b9e9501c2027
--- /dev/null
+++ b/bin/sh/tests/parser/only-redir2.0
@@ -0,0 +1,2 @@
+# $FreeBSD$
+</dev/null | :
diff --git a/bin/sh/tests/parser/only-redir3.0 b/bin/sh/tests/parser/only-redir3.0
new file mode 100644
index 000000000000..128a48391ded
--- /dev/null
+++ b/bin/sh/tests/parser/only-redir3.0
@@ -0,0 +1,2 @@
+# $FreeBSD$
+case x in x) </dev/null ;; esac
diff --git a/bin/sh/tests/parser/only-redir4.0 b/bin/sh/tests/parser/only-redir4.0
new file mode 100644
index 000000000000..d804e128debe
--- /dev/null
+++ b/bin/sh/tests/parser/only-redir4.0
@@ -0,0 +1,2 @@
+# $FreeBSD$
+case x in x) </dev/null ;& esac
diff --git a/bin/sh/tests/parser/pipe-not1.0 b/bin/sh/tests/parser/pipe-not1.0
new file mode 100644
index 000000000000..9842ff0afd05
--- /dev/null
+++ b/bin/sh/tests/parser/pipe-not1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+
+: | ! : | false
diff --git a/bin/sh/tests/parser/set-v1.0 b/bin/sh/tests/parser/set-v1.0
new file mode 100644
index 000000000000..687cb8317557
--- /dev/null
+++ b/bin/sh/tests/parser/set-v1.0
@@ -0,0 +1,8 @@
+# $FreeBSD$
+
+${SH} <<\EOF
+echo one >&2
+set -v
+echo two >&2
+echo three >&2
+EOF
diff --git a/bin/sh/tests/parser/set-v1.0.stderr b/bin/sh/tests/parser/set-v1.0.stderr
new file mode 100644
index 000000000000..d904fa5ffdb2
--- /dev/null
+++ b/bin/sh/tests/parser/set-v1.0.stderr
@@ -0,0 +1,5 @@
+one
+echo two >&2
+two
+echo three >&2
+three
diff --git a/bin/sh/tests/parser/var-assign1.0 b/bin/sh/tests/parser/var-assign1.0
new file mode 100644
index 000000000000..1fd3b26f8a26
--- /dev/null
+++ b/bin/sh/tests/parser/var-assign1.0
@@ -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/bin/sh/tests/set-e/Makefile b/bin/sh/tests/set-e/Makefile
new file mode 100644
index 000000000000..211fc95d326a
--- /dev/null
+++ b/bin/sh/tests/set-e/Makefile
@@ -0,0 +1,46 @@
+# $FreeBSD$
+
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/bin/sh/${.CURDIR:T}
+
+.PATH: ${.CURDIR:H}
+ATF_TESTS_SH= functional_test
+
+${PACKAGE}FILES+= and1.0
+${PACKAGE}FILES+= and2.1
+${PACKAGE}FILES+= and3.0
+${PACKAGE}FILES+= and4.0
+${PACKAGE}FILES+= background1.0
+${PACKAGE}FILES+= cmd1.0
+${PACKAGE}FILES+= cmd2.1
+${PACKAGE}FILES+= elif1.0
+${PACKAGE}FILES+= elif2.0
+${PACKAGE}FILES+= eval1.0
+${PACKAGE}FILES+= eval2.1
+${PACKAGE}FILES+= for1.0
+${PACKAGE}FILES+= func1.0
+${PACKAGE}FILES+= func2.1
+${PACKAGE}FILES+= if1.0
+${PACKAGE}FILES+= if2.0
+${PACKAGE}FILES+= if3.0
+${PACKAGE}FILES+= not1.0
+${PACKAGE}FILES+= not2.0
+${PACKAGE}FILES+= or1.0
+${PACKAGE}FILES+= or2.0
+${PACKAGE}FILES+= or3.1
+${PACKAGE}FILES+= pipe1.1
+${PACKAGE}FILES+= pipe2.0
+${PACKAGE}FILES+= return1.0
+${PACKAGE}FILES+= semi1.1
+${PACKAGE}FILES+= semi2.1
+${PACKAGE}FILES+= subshell1.0
+${PACKAGE}FILES+= subshell2.1
+${PACKAGE}FILES+= until1.0
+${PACKAGE}FILES+= until2.0
+${PACKAGE}FILES+= until3.0
+${PACKAGE}FILES+= while1.0
+${PACKAGE}FILES+= while2.0
+${PACKAGE}FILES+= while3.0
+
+.include <bsd.test.mk>
diff --git a/bin/sh/tests/set-e/Makefile.depend b/bin/sh/tests/set-e/Makefile.depend
new file mode 100644
index 000000000000..f80275d86ab1
--- /dev/null
+++ b/bin/sh/tests/set-e/Makefile.depend
@@ -0,0 +1,11 @@
+# $FreeBSD$
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/bin/sh/tests/set-e/and1.0 b/bin/sh/tests/set-e/and1.0
new file mode 100644
index 000000000000..607b7c350020
--- /dev/null
+++ b/bin/sh/tests/set-e/and1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+true && true
diff --git a/bin/sh/tests/set-e/and2.1 b/bin/sh/tests/set-e/and2.1
new file mode 100644
index 000000000000..78e203ab0059
--- /dev/null
+++ b/bin/sh/tests/set-e/and2.1
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+true && false
+exit 0
diff --git a/bin/sh/tests/set-e/and3.0 b/bin/sh/tests/set-e/and3.0
new file mode 100644
index 000000000000..9fafb1c45dd8
--- /dev/null
+++ b/bin/sh/tests/set-e/and3.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+false && true
+exit 0
diff --git a/bin/sh/tests/set-e/and4.0 b/bin/sh/tests/set-e/and4.0
new file mode 100644
index 000000000000..25d0e6147dea
--- /dev/null
+++ b/bin/sh/tests/set-e/and4.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+false && false
+exit 0
diff --git a/bin/sh/tests/set-e/background1.0 b/bin/sh/tests/set-e/background1.0
new file mode 100644
index 000000000000..21577f4f78c7
--- /dev/null
+++ b/bin/sh/tests/set-e/background1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+false &
diff --git a/bin/sh/tests/set-e/cmd1.0 b/bin/sh/tests/set-e/cmd1.0
new file mode 100644
index 000000000000..67fdcbc3d83e
--- /dev/null
+++ b/bin/sh/tests/set-e/cmd1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+true
diff --git a/bin/sh/tests/set-e/cmd2.1 b/bin/sh/tests/set-e/cmd2.1
new file mode 100644
index 000000000000..7cd8b09d64a8
--- /dev/null
+++ b/bin/sh/tests/set-e/cmd2.1
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+false
+exit 0
diff --git a/bin/sh/tests/set-e/elif1.0 b/bin/sh/tests/set-e/elif1.0
new file mode 100644
index 000000000000..6a5937d496ae
--- /dev/null
+++ b/bin/sh/tests/set-e/elif1.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+set -e
+if false; then
+ :
+elif false; then
+ :
+fi
diff --git a/bin/sh/tests/set-e/elif2.0 b/bin/sh/tests/set-e/elif2.0
new file mode 100644
index 000000000000..9dbb4bf514af
--- /dev/null
+++ b/bin/sh/tests/set-e/elif2.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+set -e
+if false; then
+ :
+elif false; false; then
+ :
+fi
diff --git a/bin/sh/tests/set-e/eval1.0 b/bin/sh/tests/set-e/eval1.0
new file mode 100644
index 000000000000..9b7f67b6b0d4
--- /dev/null
+++ b/bin/sh/tests/set-e/eval1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+eval false || true
diff --git a/bin/sh/tests/set-e/eval2.1 b/bin/sh/tests/set-e/eval2.1
new file mode 100644
index 000000000000..8bb7f3a92fcb
--- /dev/null
+++ b/bin/sh/tests/set-e/eval2.1
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+eval false
+exit 0
diff --git a/bin/sh/tests/set-e/for1.0 b/bin/sh/tests/set-e/for1.0
new file mode 100644
index 000000000000..67eb718ee614
--- /dev/null
+++ b/bin/sh/tests/set-e/for1.0
@@ -0,0 +1,9 @@
+# $FreeBSD$
+set -e
+f() {
+ for i in a b c; do
+ false
+ true
+ done
+}
+f || true
diff --git a/bin/sh/tests/set-e/func1.0 b/bin/sh/tests/set-e/func1.0
new file mode 100644
index 000000000000..3c6b70492fa2
--- /dev/null
+++ b/bin/sh/tests/set-e/func1.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+set -e
+f() {
+ false
+ true
+}
+f || true
diff --git a/bin/sh/tests/set-e/func2.1 b/bin/sh/tests/set-e/func2.1
new file mode 100644
index 000000000000..cc76d6edfa53
--- /dev/null
+++ b/bin/sh/tests/set-e/func2.1
@@ -0,0 +1,7 @@
+# $FreeBSD$
+set -e
+f() {
+ false
+ exit 0
+}
+f
diff --git a/bin/sh/tests/set-e/if1.0 b/bin/sh/tests/set-e/if1.0
new file mode 100644
index 000000000000..36aa4bdcbcd3
--- /dev/null
+++ b/bin/sh/tests/set-e/if1.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+set -e
+if false; then
+ :
+fi
diff --git a/bin/sh/tests/set-e/if2.0 b/bin/sh/tests/set-e/if2.0
new file mode 100644
index 000000000000..495540854099
--- /dev/null
+++ b/bin/sh/tests/set-e/if2.0
@@ -0,0 +1,7 @@
+# $FreeBSD$
+set -e
+# PR 28852
+if true; then
+ false && true
+fi
+exit 0
diff --git a/bin/sh/tests/set-e/if3.0 b/bin/sh/tests/set-e/if3.0
new file mode 100644
index 000000000000..a4916a842664
--- /dev/null
+++ b/bin/sh/tests/set-e/if3.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+set -e
+if false; false; then
+ :
+fi
diff --git a/bin/sh/tests/set-e/not1.0 b/bin/sh/tests/set-e/not1.0
new file mode 100644
index 000000000000..21c089a23221
--- /dev/null
+++ b/bin/sh/tests/set-e/not1.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+! true
+exit 0
diff --git a/bin/sh/tests/set-e/not2.0 b/bin/sh/tests/set-e/not2.0
new file mode 100644
index 000000000000..7d93b4d09e85
--- /dev/null
+++ b/bin/sh/tests/set-e/not2.0
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+! false
+! eval false
diff --git a/bin/sh/tests/set-e/or1.0 b/bin/sh/tests/set-e/or1.0
new file mode 100644
index 000000000000..c2dcbe9b682a
--- /dev/null
+++ b/bin/sh/tests/set-e/or1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+true || false
diff --git a/bin/sh/tests/set-e/or2.0 b/bin/sh/tests/set-e/or2.0
new file mode 100644
index 000000000000..934e2a68de9e
--- /dev/null
+++ b/bin/sh/tests/set-e/or2.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+false || true
diff --git a/bin/sh/tests/set-e/or3.1 b/bin/sh/tests/set-e/or3.1
new file mode 100644
index 000000000000..7a617a14b4a4
--- /dev/null
+++ b/bin/sh/tests/set-e/or3.1
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+false || false
+exit 0
diff --git a/bin/sh/tests/set-e/pipe1.1 b/bin/sh/tests/set-e/pipe1.1
new file mode 100644
index 000000000000..c0bad0fa732d
--- /dev/null
+++ b/bin/sh/tests/set-e/pipe1.1
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+true | false
+exit 0
diff --git a/bin/sh/tests/set-e/pipe2.0 b/bin/sh/tests/set-e/pipe2.0
new file mode 100644
index 000000000000..1e25566a156b
--- /dev/null
+++ b/bin/sh/tests/set-e/pipe2.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+false | true
diff --git a/bin/sh/tests/set-e/return1.0 b/bin/sh/tests/set-e/return1.0
new file mode 100644
index 000000000000..961bd4120865
--- /dev/null
+++ b/bin/sh/tests/set-e/return1.0
@@ -0,0 +1,11 @@
+# $FreeBSD$
+set -e
+
+# PR 77067, 85267
+f() {
+ return 1
+ true
+}
+
+f || true
+exit 0
diff --git a/bin/sh/tests/set-e/semi1.1 b/bin/sh/tests/set-e/semi1.1
new file mode 100644
index 000000000000..90476a98f1ec
--- /dev/null
+++ b/bin/sh/tests/set-e/semi1.1
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+false; true
+exit 0
diff --git a/bin/sh/tests/set-e/semi2.1 b/bin/sh/tests/set-e/semi2.1
new file mode 100644
index 000000000000..8f510ac9c164
--- /dev/null
+++ b/bin/sh/tests/set-e/semi2.1
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+true; false
+exit 0
diff --git a/bin/sh/tests/set-e/subshell1.0 b/bin/sh/tests/set-e/subshell1.0
new file mode 100644
index 000000000000..8e5831bd1907
--- /dev/null
+++ b/bin/sh/tests/set-e/subshell1.0
@@ -0,0 +1,3 @@
+# $FreeBSD$
+set -e
+(true)
diff --git a/bin/sh/tests/set-e/subshell2.1 b/bin/sh/tests/set-e/subshell2.1
new file mode 100644
index 000000000000..619e98a7d2df
--- /dev/null
+++ b/bin/sh/tests/set-e/subshell2.1
@@ -0,0 +1,4 @@
+# $FreeBSD$
+set -e
+(false)
+exit 0
diff --git a/bin/sh/tests/set-e/until1.0 b/bin/sh/tests/set-e/until1.0
new file mode 100644
index 000000000000..71ea7f2cf704
--- /dev/null
+++ b/bin/sh/tests/set-e/until1.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+set -e
+until false; do
+ break
+done
diff --git a/bin/sh/tests/set-e/until2.0 b/bin/sh/tests/set-e/until2.0
new file mode 100644
index 000000000000..24ea2760400b
--- /dev/null
+++ b/bin/sh/tests/set-e/until2.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+set -e
+until false; false; do
+ break
+done
diff --git a/bin/sh/tests/set-e/until3.0 b/bin/sh/tests/set-e/until3.0
new file mode 100644
index 000000000000..597db593bdaf
--- /dev/null
+++ b/bin/sh/tests/set-e/until3.0
@@ -0,0 +1,9 @@
+# $FreeBSD$
+set -e
+f() {
+ until false; do
+ false
+ break
+ done
+}
+f || true
diff --git a/bin/sh/tests/set-e/while1.0 b/bin/sh/tests/set-e/while1.0
new file mode 100644
index 000000000000..371c94a0440d
--- /dev/null
+++ b/bin/sh/tests/set-e/while1.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+set -e
+while false; do
+ :
+done
diff --git a/bin/sh/tests/set-e/while2.0 b/bin/sh/tests/set-e/while2.0
new file mode 100644
index 000000000000..124966ca59e9
--- /dev/null
+++ b/bin/sh/tests/set-e/while2.0
@@ -0,0 +1,5 @@
+# $FreeBSD$
+set -e
+while false; false; do
+ :
+done
diff --git a/bin/sh/tests/set-e/while3.0 b/bin/sh/tests/set-e/while3.0
new file mode 100644
index 000000000000..dd3c79025236
--- /dev/null
+++ b/bin/sh/tests/set-e/while3.0
@@ -0,0 +1,9 @@
+# $FreeBSD$
+set -e
+f() {
+ while true; do
+ false
+ break
+ done
+}
+f || true
diff --git a/bin/sh/trap.c b/bin/sh/trap.c
new file mode 100644
index 000000000000..79444ad1bc10
--- /dev/null
+++ b/bin/sh/trap.c
@@ -0,0 +1,551 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#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"
+
+
+/*
+ * 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)
+{
+ struct stackmark smark;
+ 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;
+ setstackmark(&smark);
+ evalstring(stsavestr(trap[i]), 0);
+ popstackmark(&smark);
+
+ /*
+ * 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 based on iflag.
+ */
+void
+setinteractive(void)
+{
+ setsignal(SIGINT);
+ setsignal(SIGQUIT);
+ setsignal(SIGTERM);
+}
+
+
+/*
+ * 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;
+ FORCEINTON;
+ evalstring(p, 0);
+ }
+ }
+ if (!setjmp(loc2.loc)) {
+ handler = &loc2; /* probably unnecessary */
+ FORCEINTON;
+ 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/bin/sh/trap.h b/bin/sh/trap.h
new file mode 100644
index 000000000000..b9c43f336141
--- /dev/null
+++ b/bin/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.
+ * 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.
+ *
+ * @(#)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(void);
+void exitshell(int) __dead2;
+void exitshell_savedstatus(void) __dead2;
diff --git a/bin/sh/var.c b/bin/sh/var.c
new file mode 100644
index 000000000000..f533969a9b0a
--- /dev/null
+++ b/bin/sh/var.c
@@ -0,0 +1,967 @@
+/*-
+ * 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.
+ * 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.
+ */
+
+#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);
+ setvareq_const("IFS= \t\n", 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, vp->text);
+ }
+ 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 arglist *list, int flags)
+{
+ int i;
+
+ INTOFF;
+ for (i = 0; i < list->count; i++)
+ setvareq(savestr(list->args[i]), 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 var *v;
+ char *result;
+ int i;
+
+ result = NULL;
+ if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) {
+ if (varequal(cmdenviron->args[i], name))
+ result = strchr(cmdenviron->args[i], '=') + 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)
+{
+ int act = 0;
+ char *loc, *locdef;
+ int i;
+
+ if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) {
+ if (localevar(cmdenviron->args[i])) {
+ 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)
+{
+ int i;
+
+ INTOFF;
+ if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) {
+ if (localevar(cmdenviron->args[i])) {
+ setlocale(LC_ALL, "");
+ updatecharset();
+ break;
+ }
+ }
+ 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 optval);
+ memcpy(lvp->text, optval, sizeof optval);
+ 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;
+ int islocalevar;
+
+ INTOFF;
+ while ((lvp = localvars) != NULL) {
+ localvars = lvp->next;
+ vp = lvp->vp;
+ if (vp == NULL) { /* $- saved */
+ memcpy(optval, lvp->text, sizeof optval);
+ ckfree(lvp->text);
+ optschanged();
+ } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
+ vp->flags &= ~VREADONLY;
+ (void)unsetvar(vp->text);
+ } else {
+ islocalevar = (vp->flags | lvp->flags) & VEXPORT &&
+ localevar(lvp->text);
+ if ((vp->flags & VTEXTFIXED) == 0)
+ ckfree(vp->text);
+ vp->flags = lvp->flags;
+ vp->text = lvp->text;
+ if (vp->func)
+ (*vp->func)(vp->text + vp->name_len + 1);
+ if (islocalevar) {
+ change_env(vp->text, vp->flags & VEXPORT &&
+ (vp->flags & VUNSET) == 0);
+ setlocale(LC_ALL, "");
+ updatecharset();
+ }
+ }
+ 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/bin/sh/var.h b/bin/sh/var.h
new file mode 100644
index 000000000000..3f806beb3558
--- /dev/null
+++ b/bin/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.
+ * 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.
+ *
+ * @(#)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 arglist;
+void listsetvar(struct arglist *, 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);